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 98b1043acc..24119a78da 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 94d572e653..a06d31f285 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 cc9fd35732..2751302226 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 8659df14ba..05b9cf1df7 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 05b9cf1df7..5223a838fb 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 c32f1d8912..aa3b7e6989 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 5be8ba5437..eaea855b75 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 01beda3e83..bdaf02debe 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 17344350c9..f4e70f6c30 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 3b97853a6c..c1df58329c 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 0000000000..277359944a --- /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 0000000000..9cd88364cf --- /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 0000000000..cfc7b8475c --- /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 0000000000..cc9dd08570 --- /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 0000000000..5c79741294 --- /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 0000000000..e060e36a72 --- /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 0000000000..42e732eb17 --- /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 0000000000..512897e6f5 --- /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 0000000000..d645092432 --- /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 0000000000..1d93315716 --- /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 0000000000..eeea940d3b --- /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 0000000000..4e64c3fed1 --- /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 0000000000..7fbf37c8bf --- /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 0000000000..8b26a1658a --- /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 0000000000..80b40077cd --- /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 0000000000..ce9408547a --- /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 0000000000..2b15bc0bba --- /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 0000000000..f6b1c89805 --- /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 0000000000..8edbca5c3e --- /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 0000000000..1cf339991f --- /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 0000000000..1dd0e48e40 --- /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 0000000000..78bb362970 --- /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 0000000000..f437c1a636 --- /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 0000000000..b5451d4d54 --- /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 0000000000..046b0bc061 --- /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 0000000000..b9a986eb3b --- /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 0000000000..ca60766a53 --- /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 0000000000..7c4a7cd9d4 --- /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 0000000000..38e8ef08ec --- /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 0000000000..e05d228087 --- /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 0000000000..780b441006 --- /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 0000000000..d0bc70cba2 --- /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 0000000000..813db0e197 --- /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 0000000000..dfa7a7045d --- /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 0000000000..6e53995a3f --- /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 0000000000..8ad2df893a --- /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 0000000000..c05e9c2fa2 --- /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 0000000000..101bc51955 --- /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 0000000000..f065f44380 --- /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 0000000000..a6a2a9e724 --- /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 0000000000..8de1101b21 --- /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 0000000000..a82014d6b8 --- /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 0000000000..2feae003c8 --- /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 0000000000..9901afc5e0 --- /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 0000000000..44ec76d277 --- /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 0000000000..e4986f6a15 --- /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 0000000000..7fe48a1674 --- /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 0000000000..75fc63c839 --- /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 0000000000..6bf80dd1f3 --- /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 0000000000..d0af51b2de --- /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 0000000000..1e5d52a75c --- /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 0000000000..3105b22a11 --- /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 0000000000..b9765d88b4 --- /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 0000000000..a2f48c9d0c --- /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 0000000000..b3ded0f5b3 --- /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 0000000000..fe5065b314 --- /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 0000000000..0487e905ab --- /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 0000000000..82e3ff145e --- /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 0000000000..d37ca05eaa --- /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 0000000000..5465d75d69 --- /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 0000000000..1aca5089ae --- /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 0000000000..e5ac4cfbfb --- /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 0000000000..98fbe36cdf --- /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 0000000000..c992783044 --- /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 0000000000..aacb16ba20 --- /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 0000000000..a700b29014 --- /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 0000000000..173ffd4009 --- /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 0000000000..8f466c397e --- /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 0000000000..c0527fc5aa --- /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 0000000000..a9410f4b3f --- /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 0000000000..e63ade2240 --- /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 0000000000..2e863b4405 --- /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 0000000000..ed2ee31a59 --- /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 0000000000..9054fbf9df --- /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 0000000000..40a2aacc42 --- /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 0000000000..cc9dd08570 --- /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 0000000000..370c1d0a9c --- /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 0000000000..d63ba2a709 --- /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 0000000000..d9fb9a2c24 --- /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 0000000000..38df3fb06b --- /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 0000000000..61f6fb7763 --- /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 0000000000..0f1751ccc7 --- /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 0000000000..f493e78cdf --- /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 0000000000..cda3e454e0 --- /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 0000000000..7a3b4dfecc --- /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 0000000000..75e855afc2 --- /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 0000000000..584ea3cd7f --- /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 0000000000..8af3c5d880 --- /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 0000000000..bd3e735993 --- /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 0000000000..b9f0b73778 --- /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 0000000000..b526c1f7c7 --- /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 0000000000..e852f4e64c --- /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 0000000000..3e227669a3 --- /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 0000000000..5d04e1e90e --- /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 0000000000..8744aed115 --- /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 0000000000..d4a16aea6e --- /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 0000000000..2338020be9 --- /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 0000000000..f8fa59b4c1 --- /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 0000000000..2ceaf8bf31 --- /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 0000000000..03e245d648 --- /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 0000000000..73fdcfac09 --- /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 0000000000..f71141a676 --- /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 0000000000..951ee22c86 --- /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 0000000000..3a83b5975e --- /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 0000000000..f9a6567e86 --- /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 0000000000..dccfe7101e --- /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 0000000000..58011f3da6 --- /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 0000000000..9d8123aabb --- /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 0000000000..fbdd98a30f --- /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 0000000000..f76a6ea4b3 --- /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 0000000000..421e890764 --- /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 0000000000..e29515e7b4 --- /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 0000000000..db752aac16 --- /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 0000000000..fc3a13c258 --- /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 0000000000..432ce1a543 --- /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 0000000000..637b191ca8 --- /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 0000000000..a7f4c790b9 --- /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 0000000000..619ef35c74 --- /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 0000000000..5fe9e21db3 --- /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 0000000000..c7c127a848 --- /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 0000000000..a86685d9c3 --- /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 0000000000..56f84dc644 --- /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 0000000000..0807d170d8 --- /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 0000000000..2a8297fe4e --- /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 0000000000..fa15debda5 --- /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 0000000000..a5f9d89261 --- /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 0000000000..ceb9340f7c --- /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 0000000000..9345543a79 --- /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 0000000000..02fa039e1c --- /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 0000000000..0b3a031247 --- /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 0000000000..792e5aa94a --- /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 0000000000..fc11ca029a --- /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 0000000000..f92ee82d95 --- /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 0000000000..031cb5e745 --- /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 0000000000..2a42e2f5f2 --- /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 0000000000..2f2a982c42 --- /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 0000000000..9652057c35 --- /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 0000000000..54cde4c2a3 --- /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 0000000000..5ca8184444 --- /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 0000000000..34c72d543f --- /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 0000000000..1aa4370a60 --- /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 0000000000..bb9244fb18 --- /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 0000000000..7acf5b439b --- /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 0000000000..2ad5d131dd --- /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 0000000000..6e6ea3134e --- /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 0000000000..6041b4fdf8 --- /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 0000000000..a0097f5436 --- /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 0000000000..cc9dd08570 --- /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 5223a838fb..767cf9724a 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 aa3b7e6989..4b721dec98 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 7f19a295a9..a211879473 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 e6ca10fc17..bbeecebd76 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 277359944a..6652acb148 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 9cd88364cf..16b133ff40 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 cfc7b8475c..6426f80a3f 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 c6b6e37dba..4bbba454a9 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 370c1d0a9c..a9ac5dea73 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 d63ba2a709..4d9681a96e 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 d9fb9a2c24..30ecd8e035 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 38df3fb06b..7eac2fdb18 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 61f6fb7763..566a8c3444 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 0f1751ccc7..eec1735fa6 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 f493e78cdf..b4b3521353 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 cda3e454e0..93d0a61ee4 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 7a3b4dfecc..2a20f588d5 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 75e855afc2..d7a24c240c 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 584ea3cd7f..a22c3d867b 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 8af3c5d880..9b015616c3 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 bd3e735993..7e1619e1c7 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 b9f0b73778..8a28a67c0d 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 b526c1f7c7..fd1ca9ef85 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 e852f4e64c..166e31de69 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 3e227669a3..cb306d308a 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 5d04e1e90e..7f1a6a935f 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 8744aed115..1efafc6d7b 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 d4a16aea6e..0a5a0c9cfd 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 2338020be9..51e42550bd 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 f8fa59b4c1..545044d750 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 2ceaf8bf31..5ca8a37dff 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 03e245d648..6766038f8e 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 73fdcfac09..8211e80ed5 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 f71141a676..58f4dac893 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 951ee22c86..0dcf77983a 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 3a83b5975e..b8c75d36ec 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 f9a6567e86..c04eb8d8be 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 dccfe7101e..bdb88e4ad1 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 58011f3da6..df9f9487e4 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 9d8123aabb..a652de6b38 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 fbdd98a30f..210c29f09b 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 f76a6ea4b3..c889302b90 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 421e890764..85c26f0e65 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 e29515e7b4..ef14dfeb12 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 db752aac16..1a7eb48150 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 fc3a13c258..3da3b93d89 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 432ce1a543..2b3c27170f 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 637b191ca8..af9ca3957c 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 a7f4c790b9..1af348fd34 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 619ef35c74..973f7fbcd8 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 5fe9e21db3..54df5dfd09 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 c7c127a848..a98f6b96dc 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 a86685d9c3..6fe9254d03 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 56f84dc644..cdc5d2d3b2 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 0807d170d8..d40dd470ab 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 2a8297fe4e..3221e1d310 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 fa15debda5..67556e7bc6 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 a5f9d89261..05d130404e 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 ceb9340f7c..7726f666a8 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 9345543a79..6b76841067 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 02fa039e1c..3a84e730f9 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 0b3a031247..9162eb6438 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 792e5aa94a..cd91e91898 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 fc11ca029a..44014c031f 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 f92ee82d95..dad48c79ac 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 031cb5e745..030989d3ed 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 2a42e2f5f2..7c97e27c99 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 2f2a982c42..698486b4c0 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 9652057c35..9648283117 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 54cde4c2a3..b77f723f21 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 5ca8184444..928a8b23b0 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 34c72d543f..b8829da8c6 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 1aa4370a60..3746a99c7f 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 bb9244fb18..8f0ead5511 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 7acf5b439b..6de9a6192d 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 2ad5d131dd..7f4f9cfb4d 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 6e6ea3134e..333dd51a2f 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 6041b4fdf8..f66f52e1f6 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 a0097f5436..157dd2a14f 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 bdaf02debe..3c3801a862 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 8a3ebb8ecf..ff1fc8aaad 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 0000000000..1f8fd1fd3e --- /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 0000000000..b1e1e19b4b --- /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 0000000000..b5363a7e20 --- /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 0000000000..6125001e5b --- /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 0000000000..f0551b0ac0 --- /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 0000000000..5883b073e5 --- /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 0000000000..016f9c2e9c --- /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 f4e70f6c30..d57de9537b 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 3c3801a862..525b980173 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 0000000000..7ff111b7f9 --- /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 0000000000..ee0549de33 --- /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 0000000000..900fb885d6 --- /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 0000000000..cc41716e3a --- /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 0000000000..ee0549de33 --- /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 0000000000..621dda3c53 --- /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 0000000000..40b699ceb2 --- /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 0000000000..7c11497d20 --- /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 0000000000..30407c3c2d --- /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 0000000000..74980ee777 --- /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 0000000000..c5a30aca25 --- /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 0000000000..bbb03dac70 --- /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 0000000000..7f857debd8 --- /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 0000000000..f24fcc5161 --- /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 0000000000..0108a1d2fe --- /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 0000000000..43b1568b12 --- /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 0000000000..ce97512d89 --- /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 0000000000..da69df1bcd --- /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 0000000000..d43170a220 --- /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 0000000000..5aa919be2a --- /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 0000000000..9b6da89bd0 --- /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 0000000000..6f949d88de --- /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 0000000000..f95b2e4dc9 --- /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 0000000000..ab60e7368b --- /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 0000000000..206a59210b --- /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 0000000000..be65df76a4 --- /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 0000000000..8dc37eadd7 --- /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 0000000000..8df8676cc3 --- /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 0000000000..a982ca5130 --- /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 0000000000..2692dab045 --- /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 0000000000..477ff3a4c5 --- /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 0000000000..23d0bfcedf --- /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 0000000000..8932a6ca05 --- /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 0000000000..b79067f234 --- /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 0000000000..1d2229925c --- /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 0000000000..a31adf249c --- /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 0000000000..436d64cd13 --- /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 0000000000..39edd44ca5 --- /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 0000000000..7623e34f64 --- /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 0000000000..5fa274b14a --- /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 0000000000..5a376620f1 --- /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 0000000000..03e6a3ea7b --- /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 0000000000..ff0fc07434 --- /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 0000000000..d4b21b40d0 --- /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 0000000000..01d593e0f8 --- /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 0000000000..c15e93c392 --- /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 0000000000..64c8943fd2 --- /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 0000000000..b576ea0150 --- /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 0000000000..b5d1e5e2e5 --- /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 0000000000..b6ead259a5 --- /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 0000000000..65392005d5 --- /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 0000000000..a412a14931 --- /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 0000000000..0d6d75eb66 --- /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 0000000000..1ca9a51a33 --- /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 0000000000..3bd0f8db57 --- /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 0000000000..bb876d8515 --- /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 0000000000..b995b20f86 --- /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 0000000000..e1eab2c613 --- /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 0000000000..22722be405 --- /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 0000000000..81d5c7eb1b --- /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 0000000000..436d3f89db --- /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 0000000000..63780c924e --- /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 0000000000..c313ab2218 --- /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 0000000000..11000c2670 --- /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 0000000000..9aa9e4b2aa --- /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 0000000000..e6a8d6f2c4 --- /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 0000000000..7de23acfe4 --- /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 0000000000..ae7e01a575 --- /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 0000000000..a740b98add --- /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 0000000000..bc9d4675dc --- /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 0000000000..eade43470f --- /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 0000000000..e5363679e5 --- /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 0000000000..e36095760d --- /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 0000000000..e1225d17c1 --- /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 0000000000..fd9c127298 --- /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 0000000000..30d8927c2a --- /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 0000000000..84edadf3bc --- /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 0000000000..9644defd3d --- /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 0000000000..b45bac3b6f --- /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 0000000000..3e0842900c --- /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 0000000000..99bbe725ed --- /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 0000000000..b49a85c7f5 --- /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 0000000000..e9f65e20b8 --- /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 0000000000..ebe1947e6e --- /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 0000000000..568f9fe471 --- /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 0000000000..6c10953fe0 --- /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 0000000000..373fd32304 --- /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 0000000000..f632d7ff1e --- /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 0000000000..23db83b95e --- /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 0000000000..9ca5322373 --- /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 0000000000..21269cf184 --- /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 0000000000..46a2e8324b --- /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 0000000000..f8fcaf63e7 --- /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 0000000000..ee0549de33 --- /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 0000000000..b6224f25c5 --- /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 0000000000..5d904c573d --- /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 0000000000..131feb6d31 --- /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 0000000000..b3b672b60e --- /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 0000000000..604bda94e1 --- /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 0000000000..0d3ed4bd7a --- /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 0000000000..dbcc3a58f9 --- /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 0000000000..637ee96481 --- /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 0000000000..379d9b9656 --- /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 0000000000..c01759a1d5 --- /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 0000000000..977db3f96e --- /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 0000000000..ee0549de33 --- /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 0000000000..6ab9618342 --- /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 0000000000..f8c2ba8d48 --- /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 5883b073e5..039e9e05ab 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 0000000000..664085d1f4 --- /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 016f9c2e9c..21dacdf6e1 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 cd58ebeb8a..0000000000 --- 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 c1df58329c..7f5ee03532 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 c1fbb1e4ef..1d757cde6d 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 181f2c2b00..9f68ce77ce 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 ee0549de33..cc9dd08570 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 767cf9724a..cc1cbb1b7f 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 cc1cbb1b7f..38f83773d4 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 f199023e41..1a40751f4f 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 71b7d60c15..9ed4ffb881 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 6652acb148..0fd5b58301 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 16b133ff40..527f58445f 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 6426f80a3f..931ae3b734 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 439c8edf69..077065e9a1 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 5c79741294..e9a707cb04 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 e060e36a72..9e82b01e13 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 42e732eb17..88b378b691 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 512897e6f5..6efdad9400 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 d645092432..df2e379fce 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 1d93315716..8debc34dcb 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 eeea940d3b..ed537c947e 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 4e64c3fed1..1a208541f9 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 7fbf37c8bf..2cf9e1a2a3 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 8b26a1658a..0a6e9d501f 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 80b40077cd..8bf396d58a 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 ce9408547a..be1b91007c 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 2b15bc0bba..e1062d575f 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 f6b1c89805..9489c8f202 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 8edbca5c3e..ecbbfd8ff9 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 1cf339991f..c1aa24c910 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 1dd0e48e40..1e91c60a49 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 78bb362970..9b71b8a561 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 f437c1a636..fed9fa90a2 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 b5451d4d54..d627d59ce6 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 046b0bc061..e98d1685c0 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 b9a986eb3b..0d0bab37d0 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 ca60766a53..64f8dcd71e 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 7c4a7cd9d4..95663d3c3b 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 38e8ef08ec..dd88c7302d 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 e05d228087..392b92c6dc 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 780b441006..6fae24ecc7 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 d0bc70cba2..3e10a7b7d1 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 813db0e197..46b60ff561 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 dfa7a7045d..5c398234e3 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 6e53995a3f..41881ac23e 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 8ad2df893a..46d700580f 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 c05e9c2fa2..3b566e3a44 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 101bc51955..10950311b7 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 f065f44380..b4dc1c008e 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 a6a2a9e724..0a72b9104d 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 8de1101b21..b0537c9f03 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 a82014d6b8..3eeaedfec3 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 2feae003c8..77a099e0de 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 9901afc5e0..1960862a94 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 44ec76d277..41aa9cfb47 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 e4986f6a15..21134fe67c 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 7fe48a1674..3193486360 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 75fc63c839..17028a4939 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 6bf80dd1f3..c9c48ba2ea 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 d0af51b2de..61f9db59a4 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 1e5d52a75c..2e417fb4b3 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 3105b22a11..7c0057f88b 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 b9765d88b4..e19ac1cc4f 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 a2f48c9d0c..8a6e9c144c 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 b3ded0f5b3..00a2e7d0e9 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 fe5065b314..594253d02d 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 0487e905ab..c774df0bc0 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 82e3ff145e..982d979214 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 d37ca05eaa..ff0c34b1e0 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 5465d75d69..4c6ca1b0c2 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 1aca5089ae..ebeaa4fcff 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 e5ac4cfbfb..62a71cc2e1 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 98fbe36cdf..ce833830fa 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 c992783044..85a588762a 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 aacb16ba20..d9305282d8 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 a700b29014..4e47446b85 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 173ffd4009..a94ce2d449 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 8f466c397e..57c4490456 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 c0527fc5aa..e6eebace7b 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 a9410f4b3f..1e4531b8a0 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 e63ade2240..1c39648acc 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 2e863b4405..169e96a38b 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 ed2ee31a59..f90147aaca 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 9054fbf9df..b0f6d82602 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 40a2aacc42..426f1d2131 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 4bbba454a9..fa53723c55 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 a9ac5dea73..e46acc2dea 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 4d9681a96e..d39a553edd 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 30ecd8e035..08a01b4ce1 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 7eac2fdb18..0b7f0637b5 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 566a8c3444..2d6666ef05 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 eec1735fa6..6f5417fd9d 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 b4b3521353..3b59bd461b 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 93d0a61ee4..0f5917f5ee 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 2a20f588d5..fb4dfc6d3f 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 d7a24c240c..79fe5389db 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 a22c3d867b..93b6a1e652 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 9b015616c3..e96bc10bf2 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 7e1619e1c7..8ca5506251 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 8a28a67c0d..3ea52bbb2f 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 fd1ca9ef85..f025718d20 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 166e31de69..80800ab9fc 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 cb306d308a..4da1ab617b 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 7f1a6a935f..4224c88a80 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 1efafc6d7b..5f5d7730b9 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 0a5a0c9cfd..f2b431a9c4 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 51e42550bd..c3c580587e 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 545044d750..78e6b8f70a 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 5ca8a37dff..3f5848b27e 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 6766038f8e..73be311feb 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 8211e80ed5..d647e0e7db 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 58f4dac893..9fe146588d 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 0dcf77983a..584f456d8e 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 b8c75d36ec..1d2e4fdb96 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 c04eb8d8be..8fb8d7f1f7 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 bdb88e4ad1..35392797a2 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 df9f9487e4..3b30abd1ff 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 a652de6b38..313e656252 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 210c29f09b..6f9a59d1ac 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 c889302b90..77eb592701 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 85c26f0e65..5b3308a9f0 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 ef14dfeb12..0cd34bdcf0 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 1a7eb48150..1856e0634c 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 3da3b93d89..cc93008b4b 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 2b3c27170f..24449c6917 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 af9ca3957c..6745ce3ed7 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 1af348fd34..e4677f1ed3 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 973f7fbcd8..2a8a5d4337 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 54df5dfd09..95474fb585 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 a98f6b96dc..5157db1d10 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 6fe9254d03..091994cff5 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 cdc5d2d3b2..be4740c861 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 d40dd470ab..fb979ba714 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 3221e1d310..c460d59f0d 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 67556e7bc6..e2f69d5dcd 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 05d130404e..bb982da802 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 7726f666a8..7c59616579 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 6b76841067..4612d0cfc0 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 3a84e730f9..674ba7bcc4 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 9162eb6438..7504c25ba5 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 cd91e91898..106953b3e8 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 44014c031f..397e87dd73 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 dad48c79ac..56874ecf54 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 030989d3ed..fd2135c4d9 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 7c97e27c99..3687b0f62d 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 698486b4c0..72ca90c373 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 9648283117..5b69002c22 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 b77f723f21..8792f89d22 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 928a8b23b0..21fee938a1 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 b8829da8c6..f0a02cd8b1 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 3746a99c7f..1ec513484c 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 8f0ead5511..9bb685b940 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 6de9a6192d..d7d4777d12 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 7f4f9cfb4d..734d990cf0 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 333dd51a2f..2dd589b58c 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 f66f52e1f6..64c6b69b14 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 157dd2a14f..76f1aff4c3 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 3cbf4ea186..bb3cf92d18 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 dbcc3a58f9..e37c70eacd 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 10c2290309..3f3d15c60e 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 b956f96fa4..c261582c1c 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 931ae3b734..4d41dc3fa7 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 e480f4a176..d5c87eadb7 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 f8c2ba8d48..8e37f47e7f 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 8e37f47e7f..1e45e06a93 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 900fb885d6..a90f131b01 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 b5363a7e20..d018d2f1c2 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 6ab9618342..69e623a6bb 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 b9ce5090e0..a6855581bc 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 1f8fd1fd3e..d67bcdac10 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 a6855581bc..e02db7132b 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 e02db7132b..acb76164ca 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 d67bcdac10..29895aec2f 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 d018d2f1c2..fe28f99691 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 6125001e5b..4ad750d8db 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 f0551b0ac0..fafd6021ad 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 acb76164ca..43d83dc080 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 fe28f99691..f59a38194d 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 4ad750d8db..01862aebc8 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 fafd6021ad..4be82e5de9 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 43d83dc080..3dd3fae7a4 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 e6f16a2e2c..de421d5eb2 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 4be82e5de9..6588f1fa72 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 3dd3fae7a4..1f18d82349 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 69e623a6bb..9ae5e4d33a 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 de421d5eb2..7bad5c7408 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 039e9e05ab..f8288f3bfd 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 '