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 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/.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/.gitignore b/.gitignore index a7a975ea6f..d6e3ffc4d4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ Settings.php Settings_bak.php Settings_org.php +!Sources/Db/Schema/*/Settings.php db_last_error.php cache/* Packages/backups/* @@ -81,4 +82,4 @@ upgrade-helper.php .php-cs-fixer.cache vendor/ .phplint-cache -.phplint.cache \ No newline at end of file +.phplint.cache 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/Languages/en_US/Install.php b/Languages/en_US/Maintenance.php similarity index 80% rename from Languages/en_US/Install.php rename to Languages/en_US/Maintenance.php index 01beda3e83..26720fb56d 100644 --- a/Languages/en_US/Install.php +++ b/Languages/en_US/Maintenance.php @@ -1,90 +1,162 @@ (does not work on all servers.)'; +$txt['error_message_click'] = 'Click here'; +$txt['error_message_try_again'] = 'Try again.'; +$txt['cli_please_delete_file'] = 'Please delete {file} as soon as possible for security reasons.'; + +// Log entries. +$txt['show_log'] = 'Show log'; +$txt['log_done'] = 'done.'; +$txt['log_skipped'] = 'skipped.'; +$txt['log_failed'] = 'failed.'; +$txt['log_failed_with_error'] = 'failed with error: "{error}"'; +$txt['log_starting_step'] = 'Step {num}: {step}'; +$txt['log_paused_step'] = 'Paused step {num}'; +$txt['log_ensuring_file_writable'] = 'Making sure SMF can write to "{file}"'; +$txt['log_ensuring_file_writable_ftp'] = 'Using FTP to make "{file}" writable'; +$txt['log_settings_file_save'] = 'Saving the following settings in Settings.php: {setting_names}'; +$txt['log_modsettings_save'] = 'Saving the following settings in the settings table: {setting_names}'; +$txt['log_table_create'] = 'Creating "{table}"'; +$txt['log_table_populate'] = 'Populating "{table}"'; +$txt['log_table_backup'] = 'Backing up "{table}"'; +$txt['log_table_convertutf8'] = 'Converting "{table}" to utf8mb4'; +$txt['log_install_complete'] = 'Installation complete!'; +$txt['log_upgrade_complete'] = 'Upgrade complete!'; + +// Errors and warnings. +$txt['critical_error'] = 'Critical Error!'; +$txt['warning'] = 'Warning!'; +$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_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_files_not_writable'] = 'The following files must be writable to continue. Please ensure the file permissions are correctly set to allow this:'; +$txt['error_dir_not_writable'] = 'The directory "{dir}" must to be writable to continue. Please make sure the file permissions are correctly set to allow this.'; +$txt['settings_error'] = 'Your settings could not be saved to Settings.php.'; +$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 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):'; +$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'; $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'; -$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_convertutf8'] = 'Convert to UTF-8'; +$txt['upgrade_step_cleanup'] = 'Cleanup'; +$txt['upgrade_step_finalize'] = '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['user_refresh_install'] = 'Forum Refreshed'; -$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['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'; -$txt['default_board_description'] = 'Feel free to talk about anything and everything in this board.'; -$txt['default_category_name'] = 'General Category'; -$txt['default_time_format'] = '%b %d, %Y, %I:%M %p'; -$txt['default_news'] = 'SMF - Just Installed!'; -$txt['default_reserved_names'] = 'Admin\nWebmaster\nGuest\nroot'; -$txt['default_fugue_smileyset_name'] = 'Fugue’s Set'; -$txt['default_alienine_smileyset_name'] = 'Alienine’s Set'; -$txt['default_aaron_smileyset_name'] = 'Aaron’s Set'; -$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'; -$txt['default_newbie_group'] = 'Newbie'; -$txt['default_junior_group'] = 'Jr. Member'; -$txt['default_full_group'] = 'Full Member'; -$txt['default_senior_group'] = 'Sr. Member'; -$txt['default_hero_group'] = 'Hero Member'; +$txt['install_no_https'] = 'Your environment does not support https streams. Certain functions, e.g., receiving updates from simplemachines.org, will not work.'; -$txt['default_smiley_smiley'] = 'Smiley'; -$txt['default_wink_smiley'] = 'Wink'; -$txt['default_cheesy_smiley'] = 'Cheesy'; -$txt['default_grin_smiley'] = 'Grin'; -$txt['default_angry_smiley'] = 'Angry'; -$txt['default_sad_smiley'] = 'Sad'; -$txt['default_shocked_smiley'] = 'Shocked'; -$txt['default_cool_smiley'] = 'Cool'; -$txt['default_huh_smiley'] = 'Huh?'; -$txt['default_roll_eyes_smiley'] = 'Roll Eyes'; -$txt['default_tongue_smiley'] = 'Tongue'; -$txt['default_embarrassed_smiley'] = 'Embarrassed'; -$txt['default_lips_sealed_smiley'] = 'Lips Sealed'; -$txt['default_undecided_smiley'] = 'Undecided'; -$txt['default_kiss_smiley'] = 'Kiss'; -$txt['default_cry_smiley'] = 'Cry'; -$txt['default_evil_smiley'] = 'Evil'; -$txt['default_azn_smiley'] = 'Azn'; -$txt['default_afro_smiley'] = 'Afro'; -$txt['default_laugh_smiley'] = 'Laugh'; -$txt['default_police_smiley'] = 'Police'; -$txt['default_angel_smiley'] = 'Angel'; +// Installer - Check Files Writable. -$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.'; +// 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.'; +$txt['error_db_prefix_invalid'] = 'The prefix "{prefix}" is invalid. Please try another using only lower case Latin letters with an underscore at the end, such as "smf_".'; +// 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'; @@ -110,26 +182,15 @@ $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'; +$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.'; -$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.'; +// Installer - Database Population. $txt['db_populate'] = 'Populated Database'; +$txt['user_refresh_install_desc'] = 'Some of the tables this installer would create already existed in your database.
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.'; $txt['db_populate_inserts'] = 'Inserted {0, number, integer} rows.'; @@ -137,6 +198,7 @@ $txt['db_populate_insert_dups'] = 'Ignored {0, number, integer} duplicated inserts.'; $txt['db_populate_table_dups'] = 'Ignored {0, number, integer} duplicated tables.'; +// 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'; @@ -151,31 +213,169 @@ $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'; +$txt['default_board_description'] = 'Feel free to talk about anything and everything in this board.'; +$txt['default_category_name'] = 'General Category'; +$txt['default_time_format'] = '%b %d, %Y, %I:%M %p'; +$txt['default_news'] = 'SMF - Just Installed!'; +$txt['default_reserved_names'] = 'Admin\nWebmaster\nGuest\nroot'; +$txt['default_fugue_smileyset_name'] = 'Fugue’s Set'; +$txt['default_alienine_smileyset_name'] = 'Alienine’s Set'; +$txt['default_aaron_smileyset_name'] = 'Aaron’s Set'; +$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'; +$txt['default_newbie_group'] = 'Newbie'; +$txt['default_junior_group'] = 'Jr. Member'; +$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'; +$txt['default_grin_smiley'] = 'Grin'; +$txt['default_angry_smiley'] = 'Angry'; +$txt['default_sad_smiley'] = 'Sad'; +$txt['default_shocked_smiley'] = 'Shocked'; +$txt['default_cool_smiley'] = 'Cool'; +$txt['default_huh_smiley'] = 'Huh?'; +$txt['default_roll_eyes_smiley'] = 'Roll Eyes'; +$txt['default_tongue_smiley'] = 'Tongue'; +$txt['default_embarrassed_smiley'] = 'Embarrassed'; +$txt['default_lips_sealed_smiley'] = 'Lips Sealed'; +$txt['default_undecided_smiley'] = 'Undecided'; +$txt['default_kiss_smiley'] = 'Kiss'; +$txt['default_cry_smiley'] = 'Cry'; +$txt['default_evil_smiley'] = 'Evil'; +$txt['default_azn_smiley'] = 'Azn'; +$txt['default_afro_smiley'] = 'Afro'; +$txt['default_laugh_smiley'] = 'Laugh'; +$txt['default_police_smiley'] = 'Police'; +$txt['default_angel_smiley'] = 'Angel'; + +// 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.'; + +// 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.'; + +// Upgrade - steps and substeps +$txt['upgrade_steps'] = 'Steps'; +$txt['upgrade_substeps'] = '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 Substep: {substep}'; +$txt['upgrade_performing_substeps'] = '{type, select, + backup {Backing up database} + migration {Performing database changes} + convertutf8 {Converting database to UTF-8} + cleanup {Performing cleanup steps} + other {Performing substeps} +}'; +$txt['upgrade_substep_progress'] = 'Completed {substep_num} {type, select, + backup {{total_substeps, plural, + one {out of # table} + other {out of # tables} + }} + migration {{total_substeps, plural, + one {out of # database change} + other {out of # database changes} + }} + convertutf8 {{total_substeps, plural, + one {out of # table} + other {out of # tables} + }} + cleanup {{total_substeps, plural, + one {out of # cleanup step} + other {out of # cleanup steps} + }} + other {{total_substeps, plural, + one {out of # substep} + other {out of # substeps} + }} +}.'; +$txt['upgrade_completed_substep'] = ' Completed Substep:'; +$txt['upgrade_step_complete'] = 'The "{step}" step is complete! Click Continue to proceed.'; +$txt['upgrade_normalizing_table'] = 'Ensuring "{table}" is structured correctly'; + + + + + + + + + +// 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['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 +383,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'; @@ -226,7 +418,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'; @@ -239,10 +430,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'; @@ -269,9 +457,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:'; @@ -283,16 +472,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 +484,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 +507,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.'; @@ -335,52 +518,8 @@ $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_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 +555,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 +584,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/Sources/Config.php b/Sources/Config.php index 55a6d18fa8..5710313486 100644 --- a/Sources/Config.php +++ b/Sources/Config.php @@ -808,12 +808,18 @@ class Config 'auto_delete' => 2, 'type' => 'boolean', ], - // Temporary variable used during the upgrade process. - 'upgradeData' => [ + // Temporary variable used during install, upgrade, etc. + 'maintenance_tool_progress' => [ 'default' => '', 'auto_delete' => 1, 'type' => 'string', ], + // Temporary variable used while installing or uninstalling packages. + 'package_installing' => [ + 'default' => false, + 'auto_delete' => 1, + 'type' => 'boolean', + ], // These should be removed if found. 'tasksdir' => [ 'default' => '', @@ -835,6 +841,11 @@ class Config 'auto_delete' => 3, 'type' => 'integer', ], + 'upgradeData' => [ + 'default' => '', + 'auto_delete' => 3, + 'type' => 'string', + ], ]; /** @@ -1417,11 +1428,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']); /***************** @@ -1454,6 +1460,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. @@ -1553,7 +1560,7 @@ public static function updateSettingsFile(array $config_vars, ?bool $keep_quotes ], // Remove the code that redirects to the installer. $neg_index-- => [ - 'search_pattern' => '~^if\s*\(file_exists\(dirname\(__FILE__\)\s*\.\s*\'/install\.php\'\)\)\s*(?:({(?' . '>[^{}]|(?1))*})\h*|header(\((?' . '>[^()]|(?2))*\));\n)~m', + 'search_pattern' => '~^if\s*\(file_exists\((?:dirname\(__FILE__\)|__DIR__)\s*\.\s*\'/install\.php\'\)(?:\s+&&\s+basename\(\$_SERVER\[\'PHP_SELF\'\]\) != \'install.php\')?\)\s*(?:({(?' . '>[^{}]|(?1))*})\h*|header(\((?' . '>[^()]|(?2))*\));\n)~m', 'placeholder' => '', ], // Remove the old path correction code. Config::set() now handles that. @@ -1919,7 +1926,7 @@ function ($a, $b) { } // Admin is explicitly trying to set this one, so we'll handle // it as if it were a new custom setting being added. - elseif ($in_c) { + elseif ($in_c && !isset($settings_defs[$var])) { $new_settings_vars[$var] = $config_vars[$var]; } @@ -2230,7 +2237,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; } 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/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 1b170b637c..fd183580a1 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -882,10 +882,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 @@ -903,11 +907,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'], ], ); @@ -1228,7 +1230,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; } @@ -1436,6 +1438,10 @@ public function add_index(string $table_name, array $index_info, array $paramete $cols = $this->list_columns($table_name, true); foreach ($index_info['columns'] as &$c) { + if (is_array($c)) { + $c = $c['name'] . (isset($c['size']) ? '(' . $c['size'] . ')' : ''); + } + $c = trim($c); $cols[$c]['size'] = isset($cols[$c]['size']) && is_numeric($cols[$c]['size']) ? $cols[$c]['size'] : null; list($type, $size) = $this->calculate_type($cols[$c]['type'], (int) $cols[$c]['size']); @@ -1467,21 +1473,38 @@ public function add_index(string $table_name, array $index_info, array $paramete } } - // Log that we are going to want to remove this! + // Log that we are going to want to remove this on uninstall! self::$package_log[] = ['remove_index', $short_table_name, $index_info['name']]; - // Let's get all our indexes. - $indexes = $this->list_indexes($table_name, true); - - // Do we already have it? - foreach ($indexes as $index) { - if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary')) { - // If we want to overwrite simply remove the current one then continue. - if ($if_exists != 'update' || $index['type'] == 'primary') { - return false; + // Let's get all our existing indexes. + $existing_indexes = $this->list_indexes($table_name, true); + + // Special handling is needed if we are trying to replace the primary + // key on a table where the current primary key refers to an + // auto-increment column. + if ( + ($index_info['type'] ?? null) == 'primary' + && array_filter($existing_indexes, fn($idx) => $idx['type'] === 'primary') !== [] + && array_filter($cols, fn($col) => !empty($col['auto'])) !== [] + ) { + $auto_col = current(array_filter($cols, fn($col) => !empty($col['auto']))); + $auto_col['auto'] = false; + $this->change_column($table_name, $auto_col['name'], $auto_col); + } + + // If we want to overwrite simply remove the current one then continue. + if ($if_exists == 'update') { + // Do we already have it? + foreach ($existing_indexes as $existing_index) { + if ( + $existing_index['name'] == $index_info['name'] + || ( + $existing_index['type'] == 'primary' + && ($index_info['type'] ?? null) == 'primary' + ) + ) { + $this->remove_index($table_name, $index_info['name']); } - - $this->remove_index($table_name, $index_info['name']); } } @@ -1504,6 +1527,12 @@ public function add_index(string $table_name, array $index_info, array $paramete ); } + // If necessary, restore the auto_increment status to the PK column. + if (isset($auto_col)) { + $auto_col['auto'] = true; + $this->change_column($table_name, $auto_col['name'], $auto_col); + } + // Query returns a result or true if successful, false otherwise. return $result !== false; } @@ -1680,6 +1709,28 @@ public function change_column(string $table_name, string $old_column, array $col return $result !== false; } + /** + * + */ + public function rename_index(string $table_name, string $old_name, string $new_name): bool + { + $result = false; + + $indexes = $this->list_indexes($table_name, false); + + if (in_array($old_name, $indexes) && !in_array($new_name, $indexes)) { + $result = $this->query( + 'ALTER TABLE ' . str_replace('{db_prefix}', $this->prefix, $table_name) . ' + RENAME INDEX `' . $old_name . '` TO `' . $new_name . '`', + [ + 'security_override' => true, + ], + ); + } + + return $result !== false; + } + /** * */ @@ -1697,7 +1748,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; } @@ -1738,6 +1789,10 @@ public function create_table(string $table_name, array $columns, array $indexes foreach ($indexes as $index) { // MySQL If it's a text column, we need to add a size. foreach ($index['columns'] as &$c) { + if (is_array($c)) { + $c = $c['name'] . (isset($c['size']) ? '(' . $c['size'] . ')' : ''); + } + $c = trim($c); // If a size was already specified, we won't be able to match it anyways. @@ -1798,6 +1853,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( $table_query, @@ -1945,13 +2023,13 @@ public function list_columns(string $table_name, bool $detail = false, array $pa $auto = str_contains($row['Extra'], 'auto_increment') ? true : false; // Can we split out the size? - if (preg_match('~(.+?)\s*\((\d+)\)(?:(?:\s*)?(unsigned))?~i', $row['Type'], $matches) === 1) { + if (preg_match('~^(.+?)\s*\((\d+)\)$~', $row['Type'], $matches)) { $type = $matches[1]; $size = $matches[2]; - - if (!empty($matches[3]) && $matches[3] == 'unsigned') { - $unsigned = true; - } + } elseif (preg_match('~^(.+?)\s+unsigned$~', $row['Type'], $matches)) { + $type = $matches[1]; + $size = null; + $unsigned = true; } else { $type = $row['Type']; $size = null; @@ -2106,20 +2184,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']); @@ -2130,7 +2418,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. @@ -2139,7 +2427,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. @@ -2192,7 +2480,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 38783a55f6..f2477cf811 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -878,10 +878,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( @@ -1391,7 +1395,11 @@ public function add_index(string $table_name, array $index_info, array $paramete $cols = $this->list_columns($table_name, true); foreach ($index_info['columns'] as &$c) { - $c = preg_replace('~\s+(\(\d+\))~', '', $c); + if (is_array($c)) { + $c = $c['name'] . (isset($c['opclass']) ? ' ' . $c['opclass'] : ''); + } + + $c = preg_replace('~\s*\(\d+\)~', '', $c); } $columns = implode(',', $index_info['columns']); @@ -1674,6 +1682,30 @@ public function change_column(string $table_name, string $old_column, array $col return true; } + /** + * + */ + public function rename_index(string $table_name, string $old_name, string $new_name): bool + { + $parsed_table_name = str_replace('{db_prefix}', $this->prefix, $table_name); + $real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name; + + $result = false; + + $indexes = $this->list_indexes($table_name, false); + + if (in_array($old_name, $indexes) && !in_array($new_name, $indexes)) { + $result = $this->query( + 'ALTER INDEX ' . $real_table_name . '_' . $old_name . ' RENAME TO ' . $real_table_name . '_' . $new_name, + [ + 'security_override' => true, + ], + ); + } + + return $result !== false; + } + /** * */ @@ -1691,7 +1723,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; } @@ -1777,6 +1809,10 @@ public function create_table(string $table_name, array $columns, array $indexes $index_queries = []; foreach ($indexes as $index) { + if (is_array($c)) { + $c = $c['name'] . (isset($c['opclass']) ? ' ' . $c['opclass'] : ''); + } + // MySQL you can do a "column_name (length)", postgresql does not allow this. Strip it. foreach ($index['columns'] as &$c) { $c = preg_replace('~\s+(\(\d+\))~', '', $c); @@ -2137,20 +2173,173 @@ 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']); @@ -2161,7 +2350,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. @@ -2170,7 +2359,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. @@ -2209,7 +2398,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 783d2d0df2..8282d8a6a0 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 9d6f7f9514..6448a965c7 100644 --- a/Sources/Db/DatabaseApiInterface.php +++ b/Sources/Db/DatabaseApiInterface.php @@ -444,7 +444,7 @@ public function search_language(): ?string; * This function adds a column. * * @param string $table_name The name of the table to add the column to - * @param array $column_info An array of column info ({@see smf_db_create_table}) + * @param array $column_info An array of column info ({@see create_table}) * @param array $parameters Not used? * @param string $if_exists What to do if the column exists. If 'update', column is updated. * @param string $error @@ -456,7 +456,7 @@ public function add_column(string $table_name, array $column_info, array $parame * Add an index. * * @param string $table_name The name of the table to add the index to - * @param array $index_info An array of index info (see {@link smf_db_create_table()}) + * @param array $index_info An array of index info (see {@link create_table()}) * @param array $parameters Not used? * @param string $if_exists What to do if the index exists. If 'update', the definition will be updated. * @param string $error @@ -479,42 +479,90 @@ public function calculate_type(string $type_name, ?int $type_size = null, bool $ * * @param string $table_name The name of the table this column is in * @param string $old_column The name of the column we want to change - * @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()}) + * @param array $column_info An array of info about the "new" column definition (see {@link create_table()}) * Note that $column_info also supports two additional parameters that only make sense when changing columns: * - drop_default - to drop a default that was previously specified * @return bool */ public function change_column(string $table_name, string $old_column, array $column_info): bool; + /** + * Renames an index. + * + * If an index named $old_name does not exist, will return false. + * If an index named $new_name already exists, will return false. + * Otherwise, returns whether the index was renamed successfully. + * + * @param string $table_name The name of the table the index is in. + * @param string $old_name The current name of the index. + * @param string $old_name The new name to set for the index. + * @return bool Whether the operation was successful. + */ + public function rename_index(string $table_name, string $old_name, string $new_name): bool; + /** * This function can be used to create a table without worrying about schema - * compatibilities across supported database systems. - * - If the table exists will, by default, do nothing. - * - Builds table with columns as passed to it - at least one column must be sent. - * The columns array should have one sub-array for each column - these sub arrays contain: - * 'name' = Column name - * 'type' = Type of column - values from (smallint, mediumint, int, text, varchar, char, tinytext, mediumtext, largetext) - * 'size' => Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc. - * If not set SMF will pick a size. - * - 'default' = Default value - do not set if no default required. - * - 'not_null' => Can it be null (true or false) - if not set default will be false. - * - 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set from what - * it should begin counting. - * - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are: - * - 'name' => Index name (If left empty SMF will generate). - * - 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'. - * - 'columns' => Array containing columns that form part of key - in the order the index is to be created. - * - parameters: (None yet) - * - if_exists values: - * - 'ignore' will do nothing if the table exists. (And will return true) - * - 'overwrite' will drop any existing table of the same name. - * - 'error' will return false if the table already exists. - * - 'update' will update the table if the table already exists (no change of ai field and only columns with the same name keep the data) + * compatibilities across supported database systems. + * + * If the table exists will, by default, do nothing. + * + * Builds table with columns as passed to it. + * At least one column must be sent. + * + * The columns array should have one sub-array for each column. + * These sub arrays contain: + * 'name': + * Column name + * 'type': + * Type of column: smallint, mediumint, int, text, varchar, + * char, tinytext, mediumtext, or largetext. + * 'size': + * Size of column, if applicable. + * For example 255 for a large varchar, 10 for an int etc. + * If not set SMF will pick a size. + * 'default': + * Default value. Do not set if no default required. + * 'not_null': + * Whether the column can have a null value. + * If not set default will be false. + * 'auto': + * Set to true to make it an auto incrementing column. + * Set to a numerical value to set the value it should begin + * counting from. + * + * Adds indexes as specified within $indexes parameter. + * Each index should be a member of $indexes. Values are: + * 'name': + * Index name (If left empty SMF will generate). + * 'type': + * Type of index. Choose from 'primary', 'unique' or 'index'. + * If not set will default to 'index'. + * 'columns': + * Array containing columns that form part of key, in the + * order the index is to be created. + * Values of 'columns' can either be simple strings or arrays + * containing a 'name' element and optional 'size' or + * 'opclass' elements. The 'size' element is used by MySQL, + * and the 'opclass' element is used by PostgreSQL. + * + * $if_exists values: + * 'ignore': + * Do nothing if the table exists and return true. + * 'overwrite': + * Drop any existing table of the same name and then create + * a new table. + * 'error': + * Return false if the table already exists. + * 'update': + * Update the table if the table already exists. This will + * not change the auto-increment value and only columns with + * the same name keep the data. * * @param string $table_name The name of the table to create * @param array $columns An array of column info in the specified format * @param array $indexes An array of index info in the specified format - * @param array $parameters Extra parameters. Currently only 'engine', the desired MySQL storage engine, is used. + * @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. * @param string $error * @return bool Whether or not the operation was successful @@ -535,7 +583,7 @@ public function drop_table(string $table_name, array $parameters = [], string $e * Get table structure. * * @param string $table_name The name of the table - * @return array An array of table structure - the name, the column info from {@link smf_db_list_columns()} and the index info from {@link smf_db_list_indexes()} + * @return array An array of table structure - the name, the column info from {@link list_columns()} and the index info from {@link list_indexes()} */ public function table_structure(string $table_name): array; @@ -580,4 +628,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 new file mode 100644 index 0000000000..b41af73311 --- /dev/null +++ b/Sources/Db/Schema/Column.php @@ -0,0 +1,169 @@ +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}; + } + } + + // Adjust the type and size to match the database's supported types. + list($this->type, $this->size) = Db::$db->calculate_type($this->type, $this->size); + + // String columns need a character set. + if (isset($charset)) { + $this->charset = strtolower($charset); + } elseif ( + in_array( + $this->type, + [ + 'character varying', + 'character', + '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..a5adbf67f6 --- /dev/null +++ b/Sources/Db/Schema/DbIndex.php @@ -0,0 +1,95 @@ + 'id_msg'], + * ['name' => 'member_groups', 'size' => 48], + * ] + */ + public array $columns = []; + + /** + * @var ?string + * + * Allowed values: 'primary', 'unique', or null for a normal index. + */ + public ?string $type = null; + + /** + * @var string + * + * The name of the index. + */ + public string $name; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + * + * @param array $columns Columns to include in the index. + * Values can be simple strings or arrays containing a 'name' element and + * optional 'size' or 'opclass' elements. The 'size' element is used by + * MySQL, and the 'opclass' element is used by PostgreSQL. + * @param ?string $type The type of index. Either 'primary' for the PRIMARY + * KEY index, 'unique' for a UNIQUE index, or null for a normal index. + * @param ?string $name The name of the index. If this is left null, a name + * will be generated automatically. + */ + public function __construct( + array $columns, + ?string $type = null, + ?string $name = null, + ) { + foreach ($columns as $key => $column) { + if (is_string($column)) { + $this->columns[$key]['name'] = strtolower($column); + } elseif (is_array($column) && isset($column['name'])) { + $column['name'] = strtolower($column['name']); + $this->columns[$key] = $column; + } + } + + $this->type = isset($type) ? strtolower((string) $type) : null; + + if ($this->type !== '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..16f6ad2f21 --- /dev/null +++ b/Sources/Db/Schema/Table.php @@ -0,0 +1,579 @@ +=') + ) { + $this->default_charset = Db::$db->title === MYSQL_TITLE ? 'utf8mb4' : 'utf8'; + } + } + + /** + * Checks all the columns and indexes in this table to make sure they + * are defined the way they should be, and fixes any that aren't. + * + * @return bool Whether or not the operation was successful. + */ + public function normalize(): bool + { + if (count($this->columns) === 0) { + return false; + } + + if (empty(Db::$db->list_tables(false, Db::$db->prefix . $this->name))) { + return $this->create(); + } + + $structure = $this->getCurrentStructure(); + + $structure['columns'] = array_change_key_case($structure['columns'], CASE_LOWER); + $structure['indexes'] = array_change_key_case($structure['indexes'], CASE_LOWER); + + // Adjust the values as needed. + foreach ($structure['columns'] as $name => $column) { + foreach ($column as $prop => $value) { + // Easy way to cast numeric strings to int or float. + if (is_numeric($value)) { + $structure['columns'][$name][$prop] = $value + 0; + } + + // Adjust the type if needed. + if ($prop === 'type') { + // Find the corresponding Column object. + foreach ($this->columns as $col) { + if ($col->name === $name) { + break; + } + } + + // Just in case there was no matching Column object. + if ($col->name !== $name) { + continue; + } + + $int_types = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint']; + $text_types = ['tinytext', 'text', 'mediumtext', 'longtext']; + $blob_types = ['tinyblob', 'blob', 'mediumblob', 'longblob']; + + if ( + // If an existing integer column upgraded its type to a + // larger one, we want to keep that larger type. + ( + in_array($value, $int_types) + && in_array($col->type, $int_types) + && array_search($value, $int_types) > array_search($col->type, $int_types) + ) + // If an existing string column upgraded its type to a + // larger one, we want to keep that larger type. + // This is only applicable to MySQL. + || ( + Db::$db->title === MYSQL_TITLE + && ( + in_array($value, $text_types) + && in_array($col->type, $text_types) + && array_search($value, $text_types) > array_search($col->type, $text_types) + ) || ( + in_array($value, $blob_types) + && in_array($col->type, $blob_types) + && array_search($value, $blob_types) > array_search($col->type, $blob_types) + ) + ) + ) { + $col->type = $value; + } + } + } + } + + // Do we need to change the engine or row format? + // This is only applicable to MySQL. + if (Db::$db->title === MYSQL_TITLE) { + if ($structure['engine'] !== 'InnoDB') { + Db::$db->query( + 'ALTER TABLE {raw:table} + ENGINE {literal:InnoDB} + ROW_FORMAT=DYNAMIC', + [ + 'table' => Db::$db->prefix . $this->name, + ], + ); + } elseif ($structure['row_format'] !== 'Dynamic') { + Db::$db->query( + 'ALTER TABLE {raw:table} + ROW_FORMAT=DYNAMIC', + [ + 'table' => Db::$db->prefix . $this->name, + ], + ); + } + } + + // Do we need to change any columns or indexes? + $columns_to_change = []; + $indexes_to_change = []; + + foreach ($this->columns as $col) { + if (!isset($structure['columns'][$col->name])) { + $columns_to_change[$col->name] = $col; + continue; + } + + list($expected_type) = Db::$db->calculate_type($col->type); + list($existing_type) = Db::$db->calculate_type($structure['columns'][$col->name]['type']); + + if ($expected_type != $existing_type) { + $columns_to_change[$col->name] = $col; + continue; + } + + foreach (['name', 'size', 'unsigned', 'not_null', 'default', 'auto'] as $prop) { + if (($structure['columns'][$col->name][$prop] ?? null) != ($col->{$prop} ?? null)) { + $columns_to_change[$col->name] = $col; + continue 2; + } + } + } + + foreach ($this->indexes as $index) { + if ( + !isset($structure['indexes'][$index->name]) + && !$this->fixIndexName($index) + ) { + $indexes_to_change[$index->name] = $index; + continue; + } + + if (($index->type ?? 'index') != $structure['indexes'][$index->name]['type']) { + $indexes_to_change[$index->name] = $index; + continue; + } + + // If we need to change any columns in this index, rebuild the index too. + foreach ([$index->columns, $structure['indexes'][$index->name]['columns']] as $cols) { + if (array_intersect($cols, array_keys($columns_to_change)) !== []) { + $indexes_to_change[$index->name] = $index; + continue 2; + } + } + } + + // Change the columns. + foreach ($columns_to_change as $col) { + if (!isset($structure['columns'][$col->name])) { + $this->addColumn($col); + } else { + $this->alterColumn($col, $structure['columns'][$col->name]['name']); + } + } + + // Special case if the table has a primary key index, but shouldn't. + if (isset($structure['indexes']['primary']) && !isset($this->indexes['primary'])) { + Db::$db->query( + 'ALTER TABLE {raw:table} + DROP PRIMARY KEY', + [ + 'table' => Db::$db->prefix . $this->name, + ], + ); + } + + // Rebuild the indexes. + foreach ($indexes_to_change as $index) { + $this->addIndex($index); + } + + return true; + } + + /** + * 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 (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 string|Column $col The column to drop. May be either an instance + * of the Column class, or just the name of the column. + * @return bool Whether or not the operation was successful. + */ + public function dropColumn(string|Column $col): bool + { + return Db::$db->remove_column( + '{db_prefix}' . $this->name, + $col instanceof Column ? $col->name : $col, + ); + } + + /** + * Adds an index to this table in the database. + * + * If the index already exists in the table, it will be updated. + * + * @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, + ); + } + + /** + * Ensures an existing index in the database is using the correct name. + * + * Searches all the existing indexes for this table for the one that has the + * same type and columns as the passed DbIndex object. If no matching index + * is found, returns false. Otherwise, if the matching index is not using + * the name defined in the DbIndex object, renames the matching index. + * + * @param DbIndex $index The index to set the name of. + * @return bool Whether the existing index now has the correct name. + */ + public function fixIndexName(DbIndex $index): bool + { + foreach ($this->list_indexes($this->name, true) as $existing_index) { + // Must be the same type. + if ($index->type !== $existing_index['type']) { + continue; + } + + // Must index the same columns. + if (array_map(fn($col) => $col['name'], $index->columns) !== $existing_index['columns']) { + continue; + } + + // If the name is already the same, there's nothing to do. + if ($index->name === $existing_index['name']) { + return true; + } + + // Do the rename. + return Db::$db->rename_index($this->name, $existing_index['name'], $index->name); + } + + // No matching index was found. + return false; + } + + /** + * Drops an index from this table in the database. + * + * @see SMF\Db\DatabaseApi::remove_index + * + * @param string|DbIndex $index The index to drop. May be either an instance + * of the DbIndex class, or just the name of the index. + * @return bool Whether or not the operation was successful. + */ + public function dropIndex(string|DbIndex $index): bool + { + return Db::$db->remove_index( + '{db_prefix}' . $this->name, + $index instanceof DbIndex ? $index->name : $index, + ); + } + + /** + * Inserts initial data into the table. + * + * @param bool $replace Whether to replace rows that have ID conflicts. + * Default: false. + * @return int Number of inserted rows. + */ + public function populate(bool $replace = false): int + { + if (empty($this->initial_data)) { + return 0; + } + + // Does this table auto-increment? + $auto_col = null; + + foreach ($this->columns as $column) { + if (!empty($column->auto)) { + $auto_col = $column->name; + break; + } + } + + $method = $replace ? 'replace' : 'ignore'; + $returnmode = isset($auto_col) ? 2 : 0; + + // Only do this if we are replacing data or the table is empty. + if ($method !== 'replace') { + $request = Db::$db->query( + 'SELECT COUNT(*) + FROM {db_prefix}{raw:table}', + [ + 'table' => $this->name, + ], + ); + list($num_rows) = Db::$db->fetch_row($request); + Db::$db->free_result($request); + + if ($num_rows > 0) { + return 0; + } + } + + // Get the correct values for any placeholders. + Lang::load('General+Maintenance', Config::$language); + + $replacements = [ + '{$db_prefix}' => Db::$db->prefix, + '{$attachdir}' => Config::$modSettings['attachmentUploadDir'] ?? json_encode([1 => Db::$db->escape_string(Config::$boarddir . '/attachments')]), + '{$boarddir}' => Db::$db->escape_string(Config::$boarddir), + '{$boardurl}' => Config::$boardurl, + '{$enableCompressedOutput}' => defined('SMF_INSTALLING') ? ((int) !empty($_POST['compress'])) : (Config::$modSettings['enableCompressedOutput'] ?? 0), + '{$databaseSession_enable}' => defined('SMF_INSTALLING') ? ((int) !empty($_POST['dbsession'])) : (Config::$modSettings['databaseSession_enable'] ?? 0), + '{$smf_version}' => SMF_VERSION, + '{$current_time}' => time(), + '{$sched_task_offset}' => 82800 + mt_rand(0, 86399), + '{$registration_method}' => defined('SMF_INSTALLING') ? ((int) !empty($_POST['reg_mode'])) : (Config::$modSettings['registration_method'] ?? 0), + ]; + + foreach (Lang::$txt as $key => $value) { + if (substr($key, 0, 8) == 'default_') { + $replacements['{$' . $key . '}'] = Db::$db->escape_string($value); + } + } + + $replacements['{$default_reserved_names}'] = strtr($replacements['{$default_reserved_names}'], ['\\\\n' => '\\n']); + + // Replace any placeholders in the initial data. + foreach ($this->initial_data as $row_num => $row) { + $this->initial_data[$row_num] = array_map( + fn($v) => $replacements[$v] ?? $v, + $row, + ); + } + + // Insert the initial data. + $ids = Db::$db->insert( + method: $method, + table: '{db_prefix}' . $this->name, + columns: Db::$db->getTypeIndicators('{db_prefix}' . $this->name, reset($this->initial_data)), + data: array_map(fn($row) => array_values($row), $this->initial_data), + keys: isset($auto_col) ? [$auto_col] : [], + returnmode: $returnmode, + ); + + $num_inserts = count($ids ?? $this->initial_data); + + return $num_inserts; + } + + /*********************** + * 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__ . '/' . $schema_version . '/*.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, + not_null: 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: [ + [ + 'name' => 'id_file', + ], + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + [ + 'name' => 'filename', + 'size' => 30, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/ApprovalQueue.php b/Sources/Db/Schema/v2_1/ApprovalQueue.php new file mode 100644 index 0000000000..c928c300c3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/ApprovalQueue.php @@ -0,0 +1,63 @@ +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/v2_1/Attachments.php b/Sources/Db/Schema/v2_1/Attachments.php new file mode 100644 index 0000000000..be82f2e223 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Attachments.php @@ -0,0 +1,193 @@ +name = 'attachments'; + + $this->columns = [ + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_attach', + ], + ], + ), + 'idx_id_member' => new DbIndex( + type: 'unique', + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_attach', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_attachment_type' => new DbIndex( + name: 'idx_attachment_type', + columns: [ + [ + 'name' => 'attachment_type', + ], + ], + ), + 'idx_id_thumb' => new DbIndex( + name: 'idx_id_thumb', + columns: [ + [ + 'name' => 'id_thumb', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/BackgroundTasks.php b/Sources/Db/Schema/v2_1/BackgroundTasks.php new file mode 100644 index 0000000000..085a1aa5bf --- /dev/null +++ b/Sources/Db/Schema/v2_1/BackgroundTasks.php @@ -0,0 +1,87 @@ +name = 'background_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_task', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php new file mode 100644 index 0000000000..4126e9be85 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BanGroups.php @@ -0,0 +1,120 @@ +name = 'ban_groups'; + + $this->columns = [ + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_ban_group', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/BanItems.php b/Sources/Db/Schema/v2_1/BanItems.php new file mode 100644 index 0000000000..97ac6baa47 --- /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, + not_null: 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: [ + [ + 'name' => 'id_ban', + ], + ], + ), + 'idx_id_ban_group' => new DbIndex( + name: 'idx_id_ban_group', + columns: [ + [ + 'name' => 'id_ban_group', + ], + ], + ), + 'idx_id_ban_ip' => new DbIndex( + name: 'idx_id_ban_ip', + columns: [ + [ + 'name' => 'ip_low', + ], + [ + 'name' => 'ip_high', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/BoardPermissions.php b/Sources/Db/Schema/v2_1/BoardPermissions.php new file mode 100644 index 0000000000..0e281f8e27 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BoardPermissions.php @@ -0,0 +1,1635 @@ + -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: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'id_profile', + ], + [ + 'name' => 'permission', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/BoardPermissionsView.php b/Sources/Db/Schema/v2_1/BoardPermissionsView.php new file mode 100644 index 0000000000..ba9cd73f32 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BoardPermissionsView.php @@ -0,0 +1,104 @@ + -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: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'id_board', + ], + [ + 'name' => 'deny', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Boards.php b/Sources/Db/Schema/v2_1/Boards.php new file mode 100644 index 0000000000..f4e20d810d --- /dev/null +++ b/Sources/Db/Schema/v2_1/Boards.php @@ -0,0 +1,250 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_categories' => new DbIndex( + type: 'unique', + name: 'idx_categories', + columns: [ + [ + 'name' => 'id_cat', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_parent' => new DbIndex( + name: 'idx_id_parent', + columns: [ + [ + 'name' => 'id_parent', + ], + ], + ), + 'idx_id_msg_updated' => new DbIndex( + name: 'idx_id_msg_updated', + columns: [ + [ + 'name' => 'id_msg_updated', + ], + ], + ), + 'idx_member_groups' => new DbIndex( + name: 'idx_member_groups', + columns: [ + [ + 'name' => 'member_groups', + 'size' => 48, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Calendar.php b/Sources/Db/Schema/v2_1/Calendar.php new file mode 100644 index 0000000000..0c780ef800 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Calendar.php @@ -0,0 +1,148 @@ +name = 'calendar'; + + $this->columns = [ + 'id_event' => new Column( + name: 'id_event', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_event', + ], + ], + ), + 'idx_start_date' => new DbIndex( + name: 'idx_start_date', + columns: [ + [ + 'name' => 'start_date', + ], + ], + ), + 'idx_end_date' => new DbIndex( + name: 'idx_end_date', + columns: [ + [ + 'name' => 'end_date', + ], + ], + ), + 'idx_topic' => new DbIndex( + name: 'idx_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/CalendarHolidays.php b/Sources/Db/Schema/v2_1/CalendarHolidays.php new file mode 100644 index 0000000000..77aa7ee58d --- /dev/null +++ b/Sources/Db/Schema/v2_1/CalendarHolidays.php @@ -0,0 +1,906 @@ + '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', + ], + [ + 'title' => 'Independence Day', + 'event_date' => '1004-07-04', + ], + [ + 'title' => 'Cinco de Mayo', + 'event_date' => '1004-05-05', + ], + [ + 'title' => 'Flag Day', + 'event_date' => '1004-06-14', + ], + [ + 'title' => 'Veterans Day', + 'event_date' => '1004-11-11', + ], + [ + 'title' => 'Groundhog Day', + 'event_date' => '1004-02-02', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2010-11-25', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2011-11-24', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2012-11-22', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2013-11-28', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2014-11-27', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2015-11-26', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2016-11-24', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2017-11-23', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2018-11-22', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2019-11-28', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2020-11-26', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2021-11-25', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2022-11-24', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2023-11-23', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2024-11-28', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2025-11-27', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2026-11-26', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2027-11-25', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2028-11-23', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2029-11-22', + ], + [ + 'title' => 'Thanksgiving', + 'event_date' => '2030-11-28', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2010-05-31', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2011-05-30', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2012-05-28', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2013-05-27', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2014-05-26', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2015-05-25', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2016-05-30', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2017-05-29', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2018-05-28', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2019-05-27', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2020-05-25', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2021-05-31', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2022-05-30', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2023-05-29', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2024-05-27', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2025-05-26', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2026-05-25', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2027-05-31', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2028-05-29', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2029-05-28', + ], + [ + 'title' => 'Memorial Day', + 'event_date' => '2030-05-27', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2010-09-06', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2011-09-05', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2012-09-03', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2013-09-02', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2014-09-01', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2015-09-07', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2016-09-05', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2017-09-04', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2018-09-03', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2019-09-02', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2020-09-07', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2021-09-06', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2022-09-05', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2023-09-04', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2024-09-02', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2025-09-01', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2026-09-07', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2027-09-06', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2028-09-04', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2029-09-03', + ], + [ + 'title' => 'Labor Day', + 'event_date' => '2030-09-02', + ], + [ + 'title' => 'D-Day', + 'event_date' => '1004-06-06', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'calendar_holidays'; + + $this->columns = [ + 'id_holiday' => new Column( + name: 'id_holiday', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_holiday', + ], + ], + ), + 'idx_event_date' => new DbIndex( + name: 'idx_event_date', + columns: [ + [ + 'name' => 'event_date', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Categories.php b/Sources/Db/Schema/v2_1/Categories.php new file mode 100644 index 0000000000..8c49525245 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Categories.php @@ -0,0 +1,104 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_cat', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/CustomFields.php b/Sources/Db/Schema/v2_1/CustomFields.php new file mode 100644 index 0000000000..be38c4a6a5 --- /dev/null +++ b/Sources/Db/Schema/v2_1/CustomFields.php @@ -0,0 +1,284 @@ + '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', + not_null: true, + 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: [ + [ + 'name' => 'id_field', + ], + ], + ), + 'idx_col_name' => new DbIndex( + type: 'unique', + name: 'idx_col_name', + columns: [ + [ + 'name' => 'col_name', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/GroupModerators.php b/Sources/Db/Schema/v2_1/GroupModerators.php new file mode 100644 index 0000000000..7bb1390a11 --- /dev/null +++ b/Sources/Db/Schema/v2_1/GroupModerators.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogActions.php b/Sources/Db/Schema/v2_1/LogActions.php new file mode 100644 index 0000000000..b93647e611 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogActions.php @@ -0,0 +1,171 @@ +name = 'log_actions'; + + $this->columns = [ + 'id_action' => new Column( + name: 'id_action', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_action', + ], + ], + ), + 'idx_id_log' => new DbIndex( + name: 'idx_id_log', + columns: [ + [ + 'name' => 'id_log', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_id_topic_id_log' => new DbIndex( + name: 'idx_id_topic_id_log', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_log', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogActivity.php b/Sources/Db/Schema/v2_1/LogActivity.php new file mode 100644 index 0000000000..7169f1aa1f --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogActivity.php @@ -0,0 +1,94 @@ +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: [ + [ + 'name' => 'date', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogBanned.php b/Sources/Db/Schema/v2_1/LogBanned.php new file mode 100644 index 0000000000..c569eb93e3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogBanned.php @@ -0,0 +1,95 @@ +name = 'log_banned'; + + $this->columns = [ + 'id_ban_log' => new Column( + name: 'id_ban_log', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_ban_log', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogBoards.php b/Sources/Db/Schema/v2_1/LogBoards.php new file mode 100644 index 0000000000..06c53bdfd9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogBoards.php @@ -0,0 +1,76 @@ +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: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_board', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogComments.php b/Sources/Db/Schema/v2_1/LogComments.php new file mode 100644 index 0000000000..4309201614 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogComments.php @@ -0,0 +1,146 @@ +name = 'log_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_comment', + ], + ], + ), + 'idx_id_recipient' => new DbIndex( + name: 'idx_id_recipient', + columns: [ + [ + 'name' => 'id_recipient', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_comment_type' => new DbIndex( + name: 'idx_comment_type', + columns: [ + [ + 'name' => 'comment_type', + 'size' => 8, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogDigest.php b/Sources/Db/Schema/v2_1/LogDigest.php new file mode 100644 index 0000000000..5ab0b8861a --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogDigest.php @@ -0,0 +1,77 @@ +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/v2_1/LogErrors.php b/Sources/Db/Schema/v2_1/LogErrors.php new file mode 100644 index 0000000000..86826c9047 --- /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, + not_null: 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: [ + [ + 'name' => 'id_error', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_ip' => new DbIndex( + name: 'idx_ip', + columns: [ + [ + 'name' => 'ip', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogFloodcontrol.php b/Sources/Db/Schema/v2_1/LogFloodcontrol.php new file mode 100644 index 0000000000..52612d9ebc --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogFloodcontrol.php @@ -0,0 +1,75 @@ +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: [ + [ + 'name' => 'ip', + ], + [ + 'name' => 'log_type', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogGroupRequests.php b/Sources/Db/Schema/v2_1/LogGroupRequests.php new file mode 100644 index 0000000000..0347758a1b --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogGroupRequests.php @@ -0,0 +1,131 @@ +name = 'log_group_requests'; + + $this->columns = [ + 'id_request' => new Column( + name: 'id_request', + type: 'mediumint', + unsigned: true, + not_null: 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, + ), + '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: '', + ), + '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: [ + [ + 'name' => 'id_request', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_group', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogMarkRead.php b/Sources/Db/Schema/v2_1/LogMarkRead.php new file mode 100644 index 0000000000..52a6a66b7a --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogMarkRead.php @@ -0,0 +1,76 @@ +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: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_board', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogMemberNotices.php b/Sources/Db/Schema/v2_1/LogMemberNotices.php new file mode 100644 index 0000000000..b15438f2c1 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogMemberNotices.php @@ -0,0 +1,73 @@ +name = 'log_member_notices'; + + $this->columns = [ + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_notice', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogNotify.php b/Sources/Db/Schema/v2_1/LogNotify.php new file mode 100644 index 0000000000..2a3b197c68 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogNotify.php @@ -0,0 +1,104 @@ +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: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'id_board' => new DbIndex( + name: 'id_board', + columns: [ + [ + 'name' => 'id_board', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogOnline.php b/Sources/Db/Schema/v2_1/LogOnline.php new file mode 100644 index 0000000000..9360a055a9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogOnline.php @@ -0,0 +1,108 @@ +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: [ + [ + 'name' => 'session', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogPackages.php b/Sources/Db/Schema/v2_1/LogPackages.php new file mode 100644 index 0000000000..53b2a5148e --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogPackages.php @@ -0,0 +1,168 @@ +name = 'log_packages'; + + $this->columns = [ + 'id_install' => new Column( + name: 'id_install', + type: 'int', + not_null: 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: 'int', + 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: 'int', + not_null: true, + default: 0, + ), + 'install_state' => new Column( + name: 'install_state', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'failed_steps' => new Column( + name: 'failed_steps', + type: 'text', + not_null: true, + ), + '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, + ), + 'credits' => new Column( + name: 'credits', + type: 'text', + not_null: true, + ), + 'sha256_hash' => new Column( + name: 'sha256_hash', + type: 'text', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_install', + ], + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + [ + 'name' => 'filename', + 'size' => 15, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogPolls.php b/Sources/Db/Schema/v2_1/LogPolls.php new file mode 100644 index 0000000000..b0120ac9c9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogPolls.php @@ -0,0 +1,81 @@ +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: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_choice', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogReported.php b/Sources/Db/Schema/v2_1/LogReported.php new file mode 100644 index 0000000000..3d45ae66dd --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogReported.php @@ -0,0 +1,178 @@ +name = 'log_reported'; + + $this->columns = [ + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_report', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_closed' => new DbIndex( + name: 'idx_closed', + columns: [ + [ + 'name' => 'closed', + ], + ], + ), + 'idx_time_started' => new DbIndex( + name: 'idx_time_started', + columns: [ + [ + 'name' => 'time_started', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => 'id_msg', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogReportedComments.php b/Sources/Db/Schema/v2_1/LogReportedComments.php new file mode 100644 index 0000000000..8ca14ba733 --- /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, + not_null: 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: [ + [ + 'name' => 'id_comment', + ], + ], + ), + 'idx_id_report' => new DbIndex( + name: 'idx_id_report', + columns: [ + [ + 'name' => 'id_report', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + [ + 'name' => 'time_sent', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogScheduledTasks.php b/Sources/Db/Schema/v2_1/LogScheduledTasks.php new file mode 100644 index 0000000000..cd718a2ea6 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogScheduledTasks.php @@ -0,0 +1,78 @@ +name = 'log_scheduled_tasks'; + + $this->columns = [ + 'id_log' => new Column( + name: 'id_log', + type: 'mediumint', + not_null: true, + 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: [ + [ + 'name' => 'id_log', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchMessages.php b/Sources/Db/Schema/v2_1/LogSearchMessages.php new file mode 100644 index 0000000000..3b22c7bc9c --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchMessages.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchResults.php b/Sources/Db/Schema/v2_1/LogSearchResults.php new file mode 100644 index 0000000000..535368de98 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchResults.php @@ -0,0 +1,93 @@ +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: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchSubjects.php b/Sources/Db/Schema/v2_1/LogSearchSubjects.php new file mode 100644 index 0000000000..bfcfaf6925 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchSubjects.php @@ -0,0 +1,77 @@ +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: [ + [ + 'name' => 'word', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchTopics.php b/Sources/Db/Schema/v2_1/LogSearchTopics.php new file mode 100644 index 0000000000..670b710476 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchTopics.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSpiderHits.php b/Sources/Db/Schema/v2_1/LogSpiderHits.php new file mode 100644 index 0000000000..3bd4cbbd33 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSpiderHits.php @@ -0,0 +1,112 @@ +name = 'log_spider_hits'; + + $this->columns = [ + 'id_hit' => new Column( + name: 'id_hit', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_hit', + ], + ], + ), + 'idx_id_spider' => new DbIndex( + name: 'idx_id_spider', + columns: [ + [ + 'name' => 'id_spider', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_processed' => new DbIndex( + name: 'idx_processed', + columns: [ + [ + 'name' => 'processed', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSpiderStats.php b/Sources/Db/Schema/v2_1/LogSpiderStats.php new file mode 100644 index 0000000000..44138eb76d --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSpiderStats.php @@ -0,0 +1,81 @@ +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: [ + [ + 'name' => 'stat_date', + ], + [ + 'name' => 'id_spider', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogSubscribed.php b/Sources/Db/Schema/v2_1/LogSubscribed.php new file mode 100644 index 0000000000..c6abd50fa2 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSubscribed.php @@ -0,0 +1,174 @@ +name = 'log_subscribed'; + + $this->columns = [ + 'id_sublog' => new Column( + name: 'id_sublog', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_sublog', + ], + ], + ), + 'id_subscribe' => new DbIndex( + type: 'unique', + name: 'id_subscribe', + columns: [ + [ + 'name' => 'id_subscribe', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_end_time' => new DbIndex( + name: 'idx_end_time', + columns: [ + [ + 'name' => 'end_time', + ], + ], + ), + 'idx_reminder_sent' => new DbIndex( + name: 'idx_reminder_sent', + columns: [ + [ + 'name' => 'reminder_sent', + ], + ], + ), + 'idx_payments_pending' => new DbIndex( + name: 'idx_payments_pending', + columns: [ + [ + 'name' => 'payments_pending', + ], + ], + ), + 'idx_status' => new DbIndex( + name: 'idx_status', + columns: [ + [ + 'name' => 'status', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/LogTopics.php b/Sources/Db/Schema/v2_1/LogTopics.php new file mode 100644 index 0000000000..2b031f300f --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogTopics.php @@ -0,0 +1,90 @@ +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: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/MailQueue.php b/Sources/Db/Schema/v2_1/MailQueue.php new file mode 100644 index 0000000000..a75f895c5f --- /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, + not_null: 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: [ + [ + 'name' => 'id_mail', + ], + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + [ + 'name' => 'time_sent', + ], + ], + ), + 'idx_mail_priority' => new DbIndex( + name: 'idx_mail_priority', + columns: [ + [ + 'name' => 'priority', + ], + [ + 'name' => 'id_mail', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/MemberLogins.php b/Sources/Db/Schema/v2_1/MemberLogins.php new file mode 100644 index 0000000000..fbf12846c0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/MemberLogins.php @@ -0,0 +1,98 @@ +name = 'member_logins'; + + $this->columns = [ + 'id_login' => new Column( + name: 'id_login', + type: 'int', + not_null: true, + 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: [ + [ + 'name' => 'id_login', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_time' => new DbIndex( + name: 'idx_time', + columns: [ + [ + 'name' => 'time', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Membergroups.php b/Sources/Db/Schema/v2_1/Membergroups.php new file mode 100644 index 0000000000..618f13a6da --- /dev/null +++ b/Sources/Db/Schema/v2_1/Membergroups.php @@ -0,0 +1,216 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_group', + ], + ], + ), + 'idx_min_posts' => new DbIndex( + name: 'idx_min_posts', + columns: [ + [ + 'name' => 'min_posts', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Members.php b/Sources/Db/Schema/v2_1/Members.php new file mode 100644 index 0000000000..0a2fff9050 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Members.php @@ -0,0 +1,519 @@ +name = 'members'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: 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: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_member_name' => new DbIndex( + name: 'idx_member_name', + columns: [ + [ + 'name' => 'member_name', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_real_name' => new DbIndex( + name: 'idx_real_name', + columns: [ + [ + 'name' => 'real_name', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_email_address' => new DbIndex( + name: 'idx_email_address', + columns: [ + [ + 'name' => 'email_address', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_date_registered' => new DbIndex( + name: 'idx_date_registered', + columns: [ + [ + 'name' => 'date_registered', + ], + ], + ), + 'idx_id_group' => new DbIndex( + name: 'idx_id_group', + columns: [ + [ + 'name' => 'id_group', + ], + ], + ), + 'idx_birthdate' => new DbIndex( + name: 'idx_birthdate', + columns: [ + [ + 'name' => 'birthdate', + ], + ], + ), + 'idx_posts' => new DbIndex( + name: 'idx_posts', + columns: [ + [ + 'name' => 'posts', + ], + ], + ), + 'idx_last_login' => new DbIndex( + name: 'idx_last_login', + columns: [ + [ + 'name' => 'last_login', + ], + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + [ + 'name' => 'lngfile', + 'size' => 30, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_id_post_group' => new DbIndex( + name: 'idx_id_post_group', + columns: [ + [ + 'name' => 'id_post_group', + ], + ], + ), + 'idx_warning' => new DbIndex( + name: 'idx_warning', + columns: [ + [ + 'name' => 'warning', + ], + ], + ), + 'idx_total_time_logged_in' => new DbIndex( + name: 'idx_total_time_logged_in', + columns: [ + [ + 'name' => 'total_time_logged_in', + ], + ], + ), + 'idx_id_theme' => new DbIndex( + name: 'idx_id_theme', + columns: [ + [ + 'name' => 'id_theme', + ], + ], + ), + 'idx_active_real_name' => new DbIndex( + name: 'idx_active_real_name', + columns: [ + [ + 'name' => 'is_activated', + ], + [ + 'name' => 'real_name', + ], + ], + ), + ]; + + if (Db::$db->title === POSTGRE_TITLE) { + $this->indexes['idx_birthdate2'] = new DbIndex( + name: 'idx_birthdate2', + columns: [ + [ + 'name' => 'indexable_month_day(birthdate)', + ], + ], + ); + $this->indexes['idx_member_name_low'] = new DbIndex( + name: 'idx_member_name_low', + columns: [ + [ + 'name' => 'LOWER(member_name)', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ); + $this->indexes['idx_real_name_low'] = new DbIndex( + name: 'idx_real_name_low', + columns: [ + [ + 'name' => 'LOWER(real_name)', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ); + } + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Mentions.php b/Sources/Db/Schema/v2_1/Mentions.php new file mode 100644 index 0000000000..657e666e98 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Mentions.php @@ -0,0 +1,108 @@ +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', + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'time' => new Column( + name: 'time', + type: 'int', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + [ + 'name' => 'id_mentioned', + ], + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + ], + ), + 'mentionee' => new DbIndex( + name: 'mentionee', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/MessageIcons.php b/Sources/Db/Schema/v2_1/MessageIcons.php new file mode 100644 index 0000000000..55d7771d21 --- /dev/null +++ b/Sources/Db/Schema/v2_1/MessageIcons.php @@ -0,0 +1,174 @@ + '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, + not_null: 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: [ + [ + 'name' => 'id_icon', + ], + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + [ + 'name' => 'id_board', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Messages.php b/Sources/Db/Schema/v2_1/Messages.php new file mode 100644 index 0000000000..c39c61c2b0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Messages.php @@ -0,0 +1,312 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_id_board' => new DbIndex( + type: 'unique', + name: 'idx_id_board', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'id_msg', + ], + [ + 'name' => 'approved', + ], + ], + ), + 'idx_id_member' => new DbIndex( + type: 'unique', + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_ip_index' => new DbIndex( + name: 'idx_ip_index', + columns: [ + [ + 'name' => 'poster_ip', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_participation' => new DbIndex( + name: 'idx_participation', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_show_posts' => new DbIndex( + name: 'idx_show_posts', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_member_msg' => new DbIndex( + name: 'idx_id_member_msg', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'approved', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_current_topic' => new DbIndex( + name: 'idx_current_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_msg', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => 'approved', + ], + ], + ), + 'idx_related_ip' => new DbIndex( + name: 'idx_related_ip', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'poster_ip', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_likes' => new DbIndex( + name: 'idx_likes', + columns: [ + [ + 'name' => 'likes', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/ModeratorGroups.php b/Sources/Db/Schema/v2_1/ModeratorGroups.php new file mode 100644 index 0000000000..a0e637ab8a --- /dev/null +++ b/Sources/Db/Schema/v2_1/ModeratorGroups.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'id_group', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Moderators.php b/Sources/Db/Schema/v2_1/Moderators.php new file mode 100644 index 0000000000..dc155b7f2d --- /dev/null +++ b/Sources/Db/Schema/v2_1/Moderators.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PackageServers.php b/Sources/Db/Schema/v2_1/PackageServers.php new file mode 100644 index 0000000000..afeed58b16 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PackageServers.php @@ -0,0 +1,108 @@ + '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, + not_null: 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: [ + [ + 'name' => 'id_server', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PermissionProfiles.php b/Sources/Db/Schema/v2_1/PermissionProfiles.php new file mode 100644 index 0000000000..a75888ba1b --- /dev/null +++ b/Sources/Db/Schema/v2_1/PermissionProfiles.php @@ -0,0 +1,95 @@ + 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', + not_null: true, + 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: [ + [ + 'name' => 'id_profile', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Permissions.php b/Sources/Db/Schema/v2_1/Permissions.php new file mode 100644 index 0000000000..795e414ba3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Permissions.php @@ -0,0 +1,290 @@ + -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: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'permission', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PersonalMessages.php b/Sources/Db/Schema/v2_1/PersonalMessages.php new file mode 100644 index 0000000000..1efbe2dd84 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PersonalMessages.php @@ -0,0 +1,135 @@ +name = 'personal_messages'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_pm', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member_from', + ], + [ + 'name' => 'deleted_by_sender', + ], + ], + ), + 'idx_msgtime' => new DbIndex( + name: 'idx_msgtime', + columns: [ + [ + 'name' => 'msgtime', + ], + ], + ), + 'idx_id_pm_head' => new DbIndex( + name: 'idx_id_pm_head', + columns: [ + [ + 'name' => 'id_pm_head', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PmLabeledMessages.php b/Sources/Db/Schema/v2_1/PmLabeledMessages.php new file mode 100644 index 0000000000..e59b03aa76 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmLabeledMessages.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_label', + ], + [ + 'name' => 'id_pm', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PmLabels.php b/Sources/Db/Schema/v2_1/PmLabels.php new file mode 100644 index 0000000000..6757313bbe --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmLabels.php @@ -0,0 +1,75 @@ +name = 'pm_labels'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_label', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PmRecipients.php b/Sources/Db/Schema/v2_1/PmRecipients.php new file mode 100644 index 0000000000..2c1deefe0b --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmRecipients.php @@ -0,0 +1,118 @@ +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: [ + [ + 'name' => 'id_pm', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_member' => new DbIndex( + type: 'unique', + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'deleted', + ], + [ + 'name' => 'id_pm', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PmRules.php b/Sources/Db/Schema/v2_1/PmRules.php new file mode 100644 index 0000000000..d37489402a --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmRules.php @@ -0,0 +1,114 @@ +name = 'pm_rules'; + + $this->columns = [ + 'id_rule' => new Column( + name: 'id_rule', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_rule', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_delete_pm' => new DbIndex( + name: 'idx_delete_pm', + columns: [ + [ + 'name' => 'delete_pm', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/PollChoices.php b/Sources/Db/Schema/v2_1/PollChoices.php new file mode 100644 index 0000000000..3e81c67b27 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PollChoices.php @@ -0,0 +1,83 @@ +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: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => 'id_choice', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Polls.php b/Sources/Db/Schema/v2_1/Polls.php new file mode 100644 index 0000000000..16bd2b32e9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Polls.php @@ -0,0 +1,137 @@ +name = 'polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_poll', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Qanda.php b/Sources/Db/Schema/v2_1/Qanda.php new file mode 100644 index 0000000000..acdb052ff0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Qanda.php @@ -0,0 +1,89 @@ +name = 'qanda'; + + $this->columns = [ + 'id_question' => new Column( + name: 'id_question', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_question', + ], + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + [ + 'name' => 'lngfile', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/ScheduledTasks.php b/Sources/Db/Schema/v2_1/ScheduledTasks.php new file mode 100644 index 0000000000..a47d5fb4f0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/ScheduledTasks.php @@ -0,0 +1,253 @@ + 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', + not_null: true, + 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: [ + [ + 'name' => 'id_task', + ], + ], + ), + 'idx_next_time' => new DbIndex( + name: 'idx_next_time', + columns: [ + [ + 'name' => 'next_time', + ], + ], + ), + 'idx_disabled' => new DbIndex( + name: 'idx_disabled', + columns: [ + [ + 'name' => 'disabled', + ], + ], + ), + 'idx_task' => new DbIndex( + type: 'unique', + name: 'idx_task', + columns: [ + [ + 'name' => 'task', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Sessions.php b/Sources/Db/Schema/v2_1/Sessions.php new file mode 100644 index 0000000000..7223cb748b --- /dev/null +++ b/Sources/Db/Schema/v2_1/Sessions.php @@ -0,0 +1,73 @@ +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: [ + [ + 'name' => 'session_id', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Settings.php b/Sources/Db/Schema/v2_1/Settings.php new file mode 100644 index 0000000000..ae3472640d --- /dev/null +++ b/Sources/Db/Schema/v2_1/Settings.php @@ -0,0 +1,896 @@ + '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: [ + [ + 'name' => 'variable', + 'size' => 30, + ], + ], + ), + ]; + + parent::__construct(); + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/SmileyFiles.php b/Sources/Db/Schema/v2_1/SmileyFiles.php new file mode 100644 index 0000000000..2b5947fee3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/SmileyFiles.php @@ -0,0 +1,77 @@ +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: [ + [ + 'name' => 'id_smiley', + ], + [ + 'name' => 'smiley_set', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Smileys.php b/Sources/Db/Schema/v2_1/Smileys.php new file mode 100644 index 0000000000..f2d0dd3cb6 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Smileys.php @@ -0,0 +1,240 @@ + ':)', + 'description' => '{$default_smiley_smiley}', + 'smiley_order' => 0, + 'hidden' => 0, + ], + [ + 'code' => ';)', + 'description' => '{$default_wink_smiley}', + 'smiley_order' => 1, + 'hidden' => 0, + ], + [ + 'code' => ':D', + 'description' => '{$default_cheesy_smiley}', + 'smiley_order' => 2, + 'hidden' => 0, + ], + [ + 'code' => ';D', + 'description' => '{$default_grin_smiley}', + 'smiley_order' => 3, + 'hidden' => 0, + ], + [ + 'code' => '>:(', + 'description' => '{$default_angry_smiley}', + 'smiley_order' => 4, + 'hidden' => 0, + ], + [ + 'code' => ':(', + 'description' => '{$default_sad_smiley}', + 'smiley_order' => 5, + 'hidden' => 0, + ], + [ + 'code' => ':o', + 'description' => '{$default_shocked_smiley}', + 'smiley_order' => 6, + 'hidden' => 0, + ], + [ + 'code' => '8)', + 'description' => '{$default_cool_smiley}', + 'smiley_order' => 7, + 'hidden' => 0, + ], + [ + 'code' => '???', + 'description' => '{$default_huh_smiley}', + 'smiley_order' => 8, + 'hidden' => 0, + ], + [ + 'code' => '::)', + 'description' => '{$default_roll_eyes_smiley}', + 'smiley_order' => 9, + 'hidden' => 0, + ], + [ + 'code' => ':P', + 'description' => '{$default_tongue_smiley}', + 'smiley_order' => 10, + 'hidden' => 0, + ], + [ + 'code' => ':-[', + 'description' => '{$default_embarrassed_smiley}', + 'smiley_order' => 11, + 'hidden' => 0, + ], + [ + 'code' => ':-X', + 'description' => '{$default_lips_sealed_smiley}', + 'smiley_order' => 12, + 'hidden' => 0, + ], + [ + 'code' => ':-\\', + 'description' => '{$default_undecided_smiley}', + 'smiley_order' => 13, + 'hidden' => 0, + ], + [ + 'code' => ':-*', + 'description' => '{$default_kiss_smiley}', + 'smiley_order' => 14, + 'hidden' => 0, + ], + [ + 'code' => ':\'(', + 'description' => '{$default_cry_smiley}', + 'smiley_order' => 15, + 'hidden' => 0, + ], + [ + 'code' => '>:D', + 'description' => '{$default_evil_smiley}', + 'smiley_order' => 16, + 'hidden' => 1, + ], + [ + 'code' => '^-^', + 'description' => '{$default_azn_smiley}', + 'smiley_order' => 17, + 'hidden' => 1, + ], + [ + 'code' => 'O0', + 'description' => '{$default_afro_smiley}', + 'smiley_order' => 18, + 'hidden' => 1, + ], + [ + 'code' => ':))', + 'description' => '{$default_laugh_smiley}', + 'smiley_order' => 19, + 'hidden' => 1, + ], + [ + 'code' => 'C:-)', + 'description' => '{$default_police_smiley}', + 'smiley_order' => 20, + 'hidden' => 1, + ], + [ + 'code' => 'O:-)', + 'description' => '{$default_angel_smiley}', + 'smiley_order' => 21, + 'hidden' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'smileys'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_smiley', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Spiders.php b/Sources/Db/Schema/v2_1/Spiders.php new file mode 100644 index 0000000000..43d99a4369 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Spiders.php @@ -0,0 +1,204 @@ + '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, + not_null: 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: [ + [ + 'name' => 'id_spider', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Subscriptions.php b/Sources/Db/Schema/v2_1/Subscriptions.php new file mode 100644 index 0000000000..cc17d059ad --- /dev/null +++ b/Sources/Db/Schema/v2_1/Subscriptions.php @@ -0,0 +1,137 @@ +name = 'subscriptions'; + + $this->columns = [ + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_subscribe', + ], + ], + ), + 'idx_active' => new DbIndex( + name: 'idx_active', + columns: [ + [ + 'name' => 'active', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Themes.php b/Sources/Db/Schema/v2_1/Themes.php new file mode 100644 index 0000000000..5b6a018929 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Themes.php @@ -0,0 +1,176 @@ + 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', + ], + [ + 'id_member' => -1, + 'id_theme' => 1, + 'variable' => 'posts_apply_ignore_list', + 'value' => '1', + ], + [ + 'id_member' => -1, + 'id_theme' => 1, + 'variable' => 'drafts_show_saved_enabled', + 'value' => '1', + ], + [ + 'id_member' => -1, + 'id_theme' => 1, + 'variable' => 'return_to_post', + '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: [ + [ + 'name' => 'id_theme', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => 'variable', + 'size' => 30, + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/Topics.php b/Sources/Db/Schema/v2_1/Topics.php new file mode 100644 index 0000000000..144342315a --- /dev/null +++ b/Sources/Db/Schema/v2_1/Topics.php @@ -0,0 +1,275 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_last_message' => new DbIndex( + type: 'unique', + name: 'idx_last_message', + columns: [ + [ + 'name' => 'id_last_msg', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_first_message' => new DbIndex( + type: 'unique', + name: 'idx_first_message', + columns: [ + [ + 'name' => 'id_first_msg', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_poll' => new DbIndex( + type: 'unique', + name: 'idx_poll', + columns: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_is_sticky' => new DbIndex( + name: 'idx_is_sticky', + columns: [ + [ + 'name' => 'is_sticky', + ], + ], + ), + 'idx_approved' => new DbIndex( + name: 'idx_approved', + columns: [ + [ + 'name' => 'approved', + ], + ], + ), + 'idx_member_started' => new DbIndex( + name: 'idx_member_started', + columns: [ + [ + 'name' => 'id_member_started', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_last_message_sticky' => new DbIndex( + name: 'idx_last_message_sticky', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'is_sticky', + ], + [ + 'name' => 'id_last_msg', + ], + ], + ), + 'idx_board_news' => new DbIndex( + name: 'idx_board_news', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'id_first_msg', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/UserAlerts.php b/Sources/Db/Schema/v2_1/UserAlerts.php new file mode 100644 index 0000000000..9ea100cbee --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserAlerts.php @@ -0,0 +1,138 @@ +name = 'user_alerts'; + + $this->columns = [ + 'id_alert' => new Column( + name: 'id_alert', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_alert', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_alert_time' => new DbIndex( + name: 'idx_alert_time', + columns: [ + [ + 'name' => 'alert_time', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/UserAlertsPrefs.php b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php new file mode 100644 index 0000000000..6f3bc97c7d --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php @@ -0,0 +1,232 @@ + 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: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'alert_pref', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/UserDrafts.php b/Sources/Db/Schema/v2_1/UserDrafts.php new file mode 100644 index 0000000000..e543237a8d --- /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, + not_null: 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: [ + [ + 'name' => 'id_draft', + ], + ], + ), + 'idx_id_member' => new DbIndex( + type: 'unique', + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_draft', + ], + [ + 'name' => 'type', + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v2_1/UserLikes.php b/Sources/Db/Schema/v2_1/UserLikes.php new file mode 100644 index 0000000000..3bd61acdeb --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserLikes.php @@ -0,0 +1,104 @@ +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: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + ], + ), + 'liker' => new DbIndex( + name: 'liker', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + parent::__construct(); + } +} 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, + not_null: 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: [ + [ + 'name' => 'id_file', + ], + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + [ + 'name' => 'filename', + 'size' => 30, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + 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..20ccec36ab --- /dev/null +++ b/Sources/Db/Schema/v3_0/ApprovalQueue.php @@ -0,0 +1,63 @@ +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..7e3c8dbf8b --- /dev/null +++ b/Sources/Db/Schema/v3_0/Attachments.php @@ -0,0 +1,193 @@ +name = 'attachments'; + + $this->columns = [ + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_attach', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_attach', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_attachment_type' => new DbIndex( + name: 'idx_attachment_type', + columns: [ + [ + 'name' => 'attachment_type', + ], + ], + ), + 'idx_id_thumb' => new DbIndex( + name: 'idx_id_thumb', + columns: [ + [ + 'name' => '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..ea13bae765 --- /dev/null +++ b/Sources/Db/Schema/v3_0/BackgroundTasks.php @@ -0,0 +1,87 @@ +name = 'background_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => '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..02b9ffeddf --- /dev/null +++ b/Sources/Db/Schema/v3_0/BanGroups.php @@ -0,0 +1,120 @@ +name = 'ban_groups'; + + $this->columns = [ + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => '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..f6fbbfbbaf --- /dev/null +++ b/Sources/Db/Schema/v3_0/BanItems.php @@ -0,0 +1,125 @@ +name = 'ban_items'; + + $this->columns = [ + 'id_ban' => new Column( + name: 'id_ban', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_ban', + ], + ], + ), + 'idx_id_ban_group' => new DbIndex( + name: 'idx_id_ban_group', + columns: [ + [ + 'name' => 'id_ban_group', + ], + ], + ), + 'idx_id_ban_ip' => new DbIndex( + name: 'idx_id_ban_ip', + columns: [ + [ + 'name' => 'ip_low', + ], + [ + 'name' => '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..11800ec393 --- /dev/null +++ b/Sources/Db/Schema/v3_0/BoardPermissions.php @@ -0,0 +1,1638 @@ + -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', + not_null: true, + default: 0, + ), + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'id_profile', + ], + [ + 'name' => '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..7a0da0b066 --- /dev/null +++ b/Sources/Db/Schema/v3_0/BoardPermissionsView.php @@ -0,0 +1,104 @@ + -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: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => 'id_board', + ], + [ + 'name' => '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..3280bb242f --- /dev/null +++ b/Sources/Db/Schema/v3_0/Boards.php @@ -0,0 +1,250 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_categories' => new DbIndex( + name: 'idx_categories', + type: 'unique', + columns: [ + [ + 'name' => 'id_cat', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_parent' => new DbIndex( + name: 'idx_id_parent', + columns: [ + [ + 'name' => 'id_parent', + ], + ], + ), + 'idx_id_msg_updated' => new DbIndex( + name: 'idx_id_msg_updated', + columns: [ + [ + 'name' => 'id_msg_updated', + ], + ], + ), + 'idx_member_groups' => new DbIndex( + name: 'idx_member_groups', + columns: [ + [ + 'name' => 'member_groups', + 'size' => 48, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + 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..f45c4d3b91 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Calendar.php @@ -0,0 +1,598 @@ + '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, + not_null: 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: false, + default: null, + ), + '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: [ + [ + 'name' => 'id_event', + ], + ], + ), + 'idx_start_date' => new DbIndex( + name: 'idx_start_date', + columns: [ + [ + 'name' => 'start_date', + ], + ], + ), + 'idx_end_date' => new DbIndex( + name: 'idx_end_date', + columns: [ + [ + 'name' => 'end_date', + ], + ], + ), + 'idx_topic' => new DbIndex( + name: 'idx_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => '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..502d7111ef --- /dev/null +++ b/Sources/Db/Schema/v3_0/Categories.php @@ -0,0 +1,105 @@ + 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, + not_null: 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', + size: 1, + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => '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..d29015f567 --- /dev/null +++ b/Sources/Db/Schema/v3_0/CustomFields.php @@ -0,0 +1,284 @@ + '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', + not_null: true, + 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: [ + [ + 'name' => 'id_field', + ], + ], + ), + 'idx_col_name' => new DbIndex( + name: 'idx_col_name', + type: 'unique', + columns: [ + [ + 'name' => '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..5e65aa695f --- /dev/null +++ b/Sources/Db/Schema/v3_0/GroupModerators.php @@ -0,0 +1,71 @@ +name = 'group_moderators'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + 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, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => '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..8d73cb5b07 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogActions.php @@ -0,0 +1,171 @@ +name = 'log_actions'; + + $this->columns = [ + 'id_action' => new Column( + name: 'id_action', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_action', + ], + ], + ), + 'idx_id_log' => new DbIndex( + name: 'idx_id_log', + columns: [ + [ + 'name' => 'id_log', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_id_topic_id_log' => new DbIndex( + name: 'idx_id_topic_id_log', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => '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..7926b4395e --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogActivity.php @@ -0,0 +1,94 @@ +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: [ + [ + 'name' => '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..589117a578 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogBanned.php @@ -0,0 +1,95 @@ +name = 'log_banned'; + + $this->columns = [ + 'id_ban_log' => new Column( + name: 'id_ban_log', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_ban_log', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => '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..f8510d46fe --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogBoards.php @@ -0,0 +1,78 @@ +name = 'log_boards'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + 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_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => '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..4afe44731b --- /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, + not_null: 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: [ + [ + 'name' => 'id_comment', + ], + ], + ), + 'idx_id_recipient' => new DbIndex( + name: 'idx_id_recipient', + columns: [ + [ + 'name' => 'id_recipient', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_comment_type' => new DbIndex( + name: 'idx_comment_type', + columns: [ + [ + 'name' => 'comment_type', + 'size' => 8, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + 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..12d2d5e61f --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogDigest.php @@ -0,0 +1,77 @@ +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..f6ac878c9b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogErrors.php @@ -0,0 +1,149 @@ +name = 'log_errors'; + + $this->columns = [ + 'id_error' => new Column( + name: 'id_error', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_error', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_ip' => new DbIndex( + name: 'idx_ip', + columns: [ + [ + 'name' => '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..1e53459df6 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogFloodcontrol.php @@ -0,0 +1,77 @@ +name = 'log_floodcontrol'; + + $this->columns = [ + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + not_null: true, + ), + '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, + not_null: true, + default: 'post', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'ip', + ], + [ + 'name' => '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..68974deebc --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogGroupRequests.php @@ -0,0 +1,138 @@ +name = 'log_group_requests'; + + $this->columns = [ + 'id_request' => new Column( + name: 'id_request', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_request', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => '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..7a26c5c44b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogMarkRead.php @@ -0,0 +1,78 @@ +name = 'log_mark_read'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + 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_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => '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..d539137e34 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogMemberNotices.php @@ -0,0 +1,73 @@ +name = 'log_member_notices'; + + $this->columns = [ + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => '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..b2f7deed6b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogNotify.php @@ -0,0 +1,107 @@ +name = 'log_notify'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + 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, + ), + 'sent' => new Column( + name: 'sent', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'id_board' => new DbIndex( + name: 'id_board', + columns: [ + [ + 'name' => '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..c9d31cc5ff --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogOnline.php @@ -0,0 +1,109 @@ +name = 'log_online'; + + $this->columns = [ + 'session' => new Column( + name: 'session', + type: 'varchar', + size: 128, + not_null: true, + 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: [ + [ + 'name' => 'session', + ], + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + [ + 'name' => 'log_time', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => '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..a277040ebf --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogPackages.php @@ -0,0 +1,188 @@ +name = 'log_packages'; + + $this->columns = [ + 'id_install' => new Column( + name: 'id_install', + type: 'int', + unsigned: true, + not_null: 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: 'int', + 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: 'int', + not_null: true, + default: 0, + ), + 'install_state' => new Column( + name: 'install_state', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'failed_steps' => new Column( + name: 'failed_steps', + type: 'text', + not_null: true, + ), + '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, + ), + '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: [ + [ + 'name' => 'id_install', + ], + ], + ), + 'idx_filename' => new DbIndex( + name: 'filename', + columns: [ + [ + 'name' => 'filename', + 'size' => 15, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_hash' => new DbIndex( + name: 'idx_hash', + columns: [ + [ + 'name' => 'sha256_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..2a5075879e --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogPolls.php @@ -0,0 +1,81 @@ +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: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => '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..3b373856cc --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogReported.php @@ -0,0 +1,178 @@ +name = 'log_reported'; + + $this->columns = [ + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_report', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_closed' => new DbIndex( + name: 'idx_closed', + columns: [ + [ + 'name' => 'closed', + ], + ], + ), + 'idx_time_started' => new DbIndex( + name: 'idx_time_started', + columns: [ + [ + 'name' => 'time_started', + ], + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + [ + 'name' => '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..a80e168f93 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogReportedComments.php @@ -0,0 +1,120 @@ +name = 'log_reported_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_comment', + ], + ], + ), + 'idx_id_report' => new DbIndex( + name: 'idx_id_report', + columns: [ + [ + 'name' => 'id_report', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + [ + 'name' => '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..628877e4b8 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogScheduledTasks.php @@ -0,0 +1,78 @@ +name = 'log_scheduled_tasks'; + + $this->columns = [ + 'id_log' => new Column( + name: 'id_log', + type: 'mediumint', + not_null: true, + 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: [ + [ + 'name' => '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..5162e479f2 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchMessages.php @@ -0,0 +1,71 @@ +name = 'log_search_messages'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => '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..7b5ad43001 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchResults.php @@ -0,0 +1,95 @@ +name = 'log_search_results'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + 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, + ), + '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: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => 'id_topic', + ], + [ + 'name' => '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..d4a99c3a78 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchSubjects.php @@ -0,0 +1,79 @@ +name = 'log_search_subjects'; + + $this->columns = [ + 'word' => new Column( + name: 'word', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'word', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => '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..3d9249d539 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchTopics.php @@ -0,0 +1,71 @@ +name = 'log_search_topics'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_search', + ], + [ + 'name' => '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..754dfff1c8 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSpiderHits.php @@ -0,0 +1,96 @@ +name = 'log_spider_hits'; + + $this->columns = [ + 'id_hit' => new Column( + name: 'id_hit', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_hit', + ], + ], + ), + 'idx_processed' => new DbIndex( + name: 'idx_processed', + columns: [ + [ + 'name' => '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..bb579d3772 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSpiderStats.php @@ -0,0 +1,83 @@ +name = 'log_spider_stats'; + + $this->columns = [ + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + not_null: 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', + not_null: true, + default: '1004-01-01', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'stat_date', + ], + [ + 'name' => '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..303b6a9c9b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSubscribed.php @@ -0,0 +1,162 @@ +name = 'log_subscribed'; + + $this->columns = [ + 'id_sublog' => new Column( + name: 'id_sublog', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_sublog', + ], + ], + ), + 'idx_end_time' => new DbIndex( + name: 'idx_end_time', + columns: [ + [ + 'name' => 'end_time', + ], + ], + ), + 'idx_reminder_sent' => new DbIndex( + name: 'idx_reminder_sent', + columns: [ + [ + 'name' => 'reminder_sent', + ], + ], + ), + 'idx_payments_pending' => new DbIndex( + name: 'idx_payments_pending', + columns: [ + [ + 'name' => 'payments_pending', + ], + ], + ), + 'idx_status' => new DbIndex( + name: 'idx_status', + columns: [ + [ + 'name' => 'status', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => '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..97384d5098 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogTopics.php @@ -0,0 +1,92 @@ +name = 'log_topics'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + 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, + ), + 'unwatched' => new Column( + name: 'unwatched', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + [ + 'name' => '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..3e0c2ed0dc --- /dev/null +++ b/Sources/Db/Schema/v3_0/MailQueue.php @@ -0,0 +1,129 @@ +name = 'mail_queue'; + + $this->columns = [ + 'id_mail' => new Column( + name: 'id_mail', + type: 'int', + unsigned: true, + not_null: 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', + size: 1, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_mail', + ], + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + [ + 'name' => 'time_sent', + ], + ], + ), + 'idx_mail_priority' => new DbIndex( + name: 'idx_mail_priority', + columns: [ + [ + 'name' => 'priority', + ], + [ + 'name' => '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..0f0a98aca1 --- /dev/null +++ b/Sources/Db/Schema/v3_0/MemberLogins.php @@ -0,0 +1,98 @@ +name = 'member_logins'; + + $this->columns = [ + 'id_login' => new Column( + name: 'id_login', + type: 'int', + not_null: true, + 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: [ + [ + 'name' => 'id_login', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_time' => new DbIndex( + name: 'idx_time', + columns: [ + [ + 'name' => '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..39a18dff3d --- /dev/null +++ b/Sources/Db/Schema/v3_0/Membergroups.php @@ -0,0 +1,216 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_group', + ], + ], + ), + 'idx_min_posts' => new DbIndex( + name: 'idx_min_posts', + columns: [ + [ + 'name' => '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..cc62f47134 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Members.php @@ -0,0 +1,547 @@ +name = 'members'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_member_name' => new DbIndex( + name: 'idx_member_name', + columns: [ + [ + 'name' => 'member_name', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_real_name' => new DbIndex( + name: 'idx_real_name', + columns: [ + [ + 'name' => 'real_name', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_email_address' => new DbIndex( + name: 'idx_email_address', + columns: [ + [ + 'name' => 'email_address', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_date_registered' => new DbIndex( + name: 'idx_date_registered', + columns: [ + [ + 'name' => 'date_registered', + ], + ], + ), + 'idx_id_group' => new DbIndex( + name: 'idx_id_group', + columns: [ + [ + 'name' => 'id_group', + ], + ], + ), + 'idx_birthdate' => new DbIndex( + name: 'idx_birthdate', + columns: [ + [ + 'name' => 'birthdate', + ], + ], + ), + 'idx_posts' => new DbIndex( + name: 'idx_posts', + columns: [ + [ + 'name' => 'posts', + ], + ], + ), + 'idx_last_login' => new DbIndex( + name: 'idx_last_login', + columns: [ + [ + 'name' => 'last_login', + ], + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + [ + 'name' => 'lngfile', + 'size' => 30, + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_id_post_group' => new DbIndex( + name: 'idx_id_post_group', + columns: [ + [ + 'name' => 'id_post_group', + ], + ], + ), + 'idx_warning' => new DbIndex( + name: 'idx_warning', + columns: [ + [ + 'name' => 'warning', + ], + ], + ), + 'idx_total_time_logged_in' => new DbIndex( + name: 'idx_total_time_logged_in', + columns: [ + [ + 'name' => 'total_time_logged_in', + ], + ], + ), + 'idx_id_theme' => new DbIndex( + name: 'idx_id_theme', + columns: [ + [ + 'name' => 'id_theme', + ], + ], + ), + 'idx_active_real_name' => new DbIndex( + name: 'idx_active_real_name', + columns: [ + [ + 'name' => 'is_activated', + ], + [ + 'name' => 'real_name', + ], + ], + ), + 'idx_spoofdetector_name' => new DbIndex( + name: 'idx_spoofdetector_name', + columns: [ + [ + 'name' => 'spoofdetector_name', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + 'idx_spoofdetector_name_id' => new DbIndex( + name: 'idx_spoofdetector_name_id', + columns: [ + [ + 'name' => 'spoofdetector_name', + 'opclass' => 'varchar_pattern_ops', + ], + [ + 'name' => 'id_member', + ], + ], + ), + ]; + + if (Db::$db->title === POSTGRE_TITLE) { + $this->indexes['idx_birthdate2'] = new DbIndex( + name: 'idx_birthdate2', + columns: [ + [ + 'name' => 'indexable_month_day(birthdate)', + ], + ], + ); + $this->indexes['idx_member_name_low'] = new DbIndex( + name: 'idx_member_name_low', + columns: [ + [ + 'name' => 'LOWER(member_name)', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ); + $this->indexes['idx_real_name_low'] = new DbIndex( + name: 'idx_real_name_low', + columns: [ + [ + 'name' => 'LOWER(real_name)', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ); + } + + 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..0a683b7f0d --- /dev/null +++ b/Sources/Db/Schema/v3_0/Mentions.php @@ -0,0 +1,108 @@ +name = 'mentions'; + + $this->columns = [ + 'content_id' => new Column( + name: 'content_id', + type: 'int', + not_null: true, + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'varchar', + size: 10, + not_null: true, + default: '', + ), + 'id_mentioned' => new Column( + name: 'id_mentioned', + type: 'int', + not_null: true, + ), + '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: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + [ + 'name' => 'id_mentioned', + ], + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + ], + ), + 'mentionee' => new DbIndex( + name: 'mentionee', + columns: [ + [ + 'name' => '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..de8005825e --- /dev/null +++ b/Sources/Db/Schema/v3_0/MessageIcons.php @@ -0,0 +1,174 @@ + '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, + not_null: 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: [ + [ + 'name' => 'id_icon', + ], + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + [ + 'name' => '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..6fbb38f413 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Messages.php @@ -0,0 +1,319 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + type: 'unique', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'id_msg', + ], + [ + 'name' => 'approved', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_ip_index' => new DbIndex( + name: 'idx_ip_index', + columns: [ + [ + 'name' => 'poster_ip', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_participation' => new DbIndex( + name: 'idx_participation', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_show_posts' => new DbIndex( + name: 'idx_show_posts', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_id_member_msg' => new DbIndex( + name: 'idx_id_member_msg', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'approved', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_current_topic' => new DbIndex( + name: 'idx_current_topic', + columns: [ + [ + 'name' => 'id_topic', + ], + [ + 'name' => 'id_msg', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => 'approved', + ], + ], + ), + 'idx_related_ip' => new DbIndex( + name: 'idx_related_ip', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'poster_ip', + ], + [ + 'name' => 'id_msg', + ], + ], + ), + 'idx_likes' => new DbIndex( + name: 'idx_likes', + columns: [ + [ + 'name' => '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..ef9b49d0b7 --- /dev/null +++ b/Sources/Db/Schema/v3_0/ModeratorGroups.php @@ -0,0 +1,71 @@ +name = 'moderator_groups'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => '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..9d902a8ba3 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Moderators.php @@ -0,0 +1,69 @@ +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: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => '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..1a5c139d8a --- /dev/null +++ b/Sources/Db/Schema/v3_0/PackageServers.php @@ -0,0 +1,109 @@ + '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, + not_null: 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', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => '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..f81fd98128 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PermissionProfiles.php @@ -0,0 +1,95 @@ + 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', + not_null: true, + 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: [ + [ + 'name' => '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..520d006ce2 --- /dev/null +++ b/Sources/Db/Schema/v3_0/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' => 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' => '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', + not_null: true, + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_group', + ], + [ + 'name' => '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..b59b1b6df2 --- /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, + not_null: 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: [ + [ + 'name' => 'id_pm', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member_from', + ], + [ + 'name' => 'deleted_by_sender', + ], + ], + ), + 'idx_msgtime' => new DbIndex( + name: 'idx_msgtime', + columns: [ + [ + 'name' => 'msgtime', + ], + ], + ), + 'idx_id_pm_head' => new DbIndex( + name: 'idx_id_pm_head', + columns: [ + [ + 'name' => '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..e1ae742900 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmLabeledMessages.php @@ -0,0 +1,71 @@ +name = 'pm_labeled_messages'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_label', + ], + [ + 'name' => '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..92010d25fb --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmLabels.php @@ -0,0 +1,75 @@ +name = 'pm_labels'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => '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..ddad65e5d2 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmRecipients.php @@ -0,0 +1,121 @@ +name = 'pm_recipients'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + 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, + ), + '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', + unsigned: true, + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_pm', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'deleted', + ], + [ + 'name' => '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..7c916f36b7 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmRules.php @@ -0,0 +1,114 @@ +name = 'pm_rules'; + + $this->columns = [ + 'id_rule' => new Column( + name: 'id_rule', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_rule', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_delete_pm' => new DbIndex( + name: 'idx_delete_pm', + columns: [ + [ + 'name' => '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..f642303034 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PollChoices.php @@ -0,0 +1,85 @@ +name = 'poll_choices'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + 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, + ), + '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: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => '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..470bb213b5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Polls.php @@ -0,0 +1,138 @@ +name = 'polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: 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', + size: 1, + 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: [ + [ + 'name' => '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..93b734fd25 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Qanda.php @@ -0,0 +1,89 @@ +name = 'qanda'; + + $this->columns = [ + 'id_question' => new Column( + name: 'id_question', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_question', + ], + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + [ + 'name' => 'lngfile', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + 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..f60246cad3 --- /dev/null +++ b/Sources/Db/Schema/v3_0/ScheduledTasks.php @@ -0,0 +1,253 @@ + 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', + not_null: true, + 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: [ + [ + 'name' => 'id_task', + ], + ], + ), + 'idx_next_time' => new DbIndex( + name: 'idx_next_time', + columns: [ + [ + 'name' => 'next_time', + ], + ], + ), + 'idx_disabled' => new DbIndex( + name: 'idx_disabled', + columns: [ + [ + 'name' => 'disabled', + ], + ], + ), + 'idx_task' => new DbIndex( + name: 'idx_task', + type: 'unique', + columns: [ + [ + 'name' => 'task', + 'opclass' => 'varchar_pattern_ops', + ], + ], + ), + ]; + + 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..0db2f2c744 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Sessions.php @@ -0,0 +1,73 @@ +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: [ + [ + 'name' => '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..0754860589 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Settings.php @@ -0,0 +1,895 @@ + '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, + not_null: true, + default: '', + ), + 'value' => new Column( + name: 'value', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'variable', + 'size' => 30, + ], + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/SmileyFiles.php b/Sources/Db/Schema/v3_0/SmileyFiles.php new file mode 100644 index 0000000000..3495b216ab --- /dev/null +++ b/Sources/Db/Schema/v3_0/SmileyFiles.php @@ -0,0 +1,309 @@ + 1, + 'smiley_set' => 'fugue', + 'filename' => 'smiley.png', + ], + [ + 'id_smiley' => 1, + 'smiley_set' => 'alienine', + 'filename' => 'smiley.png', + ], + [ + 'id_smiley' => 2, + 'smiley_set' => 'fugue', + 'filename' => 'wink.png', + ], + [ + 'id_smiley' => 2, + 'smiley_set' => 'alienine', + 'filename' => 'wink.png', + ], + [ + 'id_smiley' => 3, + 'smiley_set' => 'fugue', + 'filename' => 'cheesy.png', + ], + [ + 'id_smiley' => 3, + 'smiley_set' => 'alienine', + 'filename' => 'cheesy.png', + ], + [ + 'id_smiley' => 4, + 'smiley_set' => 'fugue', + 'filename' => 'grin.png', + ], + [ + 'id_smiley' => 4, + 'smiley_set' => 'alienine', + 'filename' => 'grin.png', + ], + [ + 'id_smiley' => 5, + 'smiley_set' => 'fugue', + 'filename' => 'angry.png', + ], + [ + 'id_smiley' => 5, + 'smiley_set' => 'alienine', + 'filename' => 'angry.png', + ], + [ + 'id_smiley' => 6, + 'smiley_set' => 'fugue', + 'filename' => 'sad.png', + ], + [ + 'id_smiley' => 6, + 'smiley_set' => 'alienine', + 'filename' => 'sad.png', + ], + [ + 'id_smiley' => 7, + 'smiley_set' => 'fugue', + 'filename' => 'shocked.png', + ], + [ + 'id_smiley' => 7, + 'smiley_set' => 'alienine', + 'filename' => 'shocked.png', + ], + [ + 'id_smiley' => 8, + 'smiley_set' => 'fugue', + 'filename' => 'cool.png', + ], + [ + 'id_smiley' => 8, + 'smiley_set' => 'alienine', + 'filename' => 'cool.png', + ], + [ + 'id_smiley' => 9, + 'smiley_set' => 'fugue', + 'filename' => 'huh.png', + ], + [ + 'id_smiley' => 9, + 'smiley_set' => 'alienine', + 'filename' => 'huh.png', + ], + [ + 'id_smiley' => 10, + 'smiley_set' => 'fugue', + 'filename' => 'rolleyes.png', + ], + [ + 'id_smiley' => 10, + 'smiley_set' => 'alienine', + 'filename' => 'rolleyes.png', + ], + [ + 'id_smiley' => 11, + 'smiley_set' => 'fugue', + 'filename' => 'tongue.png', + ], + [ + 'id_smiley' => 11, + 'smiley_set' => 'alienine', + 'filename' => 'tongue.png', + ], + [ + 'id_smiley' => 12, + 'smiley_set' => 'fugue', + 'filename' => 'embarrassed.png', + ], + [ + 'id_smiley' => 12, + 'smiley_set' => 'alienine', + 'filename' => 'embarrassed.png', + ], + [ + 'id_smiley' => 13, + 'smiley_set' => 'fugue', + 'filename' => 'lipsrsealed.png', + ], + [ + 'id_smiley' => 13, + 'smiley_set' => 'alienine', + 'filename' => 'lipsrsealed.png', + ], + [ + 'id_smiley' => 14, + 'smiley_set' => 'fugue', + 'filename' => 'undecided.png', + ], + [ + 'id_smiley' => 14, + 'smiley_set' => 'alienine', + 'filename' => 'undecided.png', + ], + [ + 'id_smiley' => 15, + 'smiley_set' => 'fugue', + 'filename' => 'kiss.png', + ], + [ + 'id_smiley' => 15, + 'smiley_set' => 'alienine', + 'filename' => 'kiss.png', + ], + [ + 'id_smiley' => 16, + 'smiley_set' => 'fugue', + 'filename' => 'cry.png', + ], + [ + 'id_smiley' => 16, + 'smiley_set' => 'alienine', + 'filename' => 'cry.png', + ], + [ + 'id_smiley' => 17, + 'smiley_set' => 'fugue', + 'filename' => 'evil.png', + ], + [ + 'id_smiley' => 17, + 'smiley_set' => 'alienine', + 'filename' => 'evil.png', + ], + [ + 'id_smiley' => 18, + 'smiley_set' => 'fugue', + 'filename' => 'azn.png', + ], + [ + 'id_smiley' => 18, + 'smiley_set' => 'alienine', + 'filename' => 'azn.png', + ], + [ + 'id_smiley' => 19, + 'smiley_set' => 'fugue', + 'filename' => 'afro.png', + ], + [ + 'id_smiley' => 19, + 'smiley_set' => 'alienine', + 'filename' => 'afro.png', + ], + [ + 'id_smiley' => 20, + 'smiley_set' => 'fugue', + 'filename' => 'laugh.png', + ], + [ + 'id_smiley' => 20, + 'smiley_set' => 'alienine', + 'filename' => 'laugh.png', + ], + [ + 'id_smiley' => 21, + 'smiley_set' => 'fugue', + 'filename' => 'police.png', + ], + [ + 'id_smiley' => 21, + 'smiley_set' => 'alienine', + 'filename' => 'police.png', + ], + [ + 'id_smiley' => 22, + 'smiley_set' => 'fugue', + 'filename' => 'angel.png', + ], + [ + 'id_smiley' => 22, + 'smiley_set' => 'alienine', + 'filename' => 'angel.png', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->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: [ + [ + 'name' => 'id_smiley', + ], + [ + 'name' => '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..b2ac4261ef --- /dev/null +++ b/Sources/Db/Schema/v3_0/Smileys.php @@ -0,0 +1,284 @@ + 1, + 'code' => ':)', + 'description' => '{$default_smiley_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 0, + 'hidden' => 0, + ], + [ + 'id_smiley' => 2, + 'code' => ';)', + 'description' => '{$default_wink_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 1, + 'hidden' => 0, + ], + [ + 'id_smiley' => 3, + 'code' => ':D', + 'description' => '{$default_cheesy_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 2, + 'hidden' => 0, + ], + [ + 'id_smiley' => 4, + 'code' => ';D', + 'description' => '{$default_grin_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 3, + 'hidden' => 0, + ], + [ + 'id_smiley' => 5, + 'code' => '>:(', + 'description' => '{$default_angry_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 4, + 'hidden' => 0, + ], + [ + 'id_smiley' => 6, + 'code' => ':(', + 'description' => '{$default_sad_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 5, + 'hidden' => 0, + ], + [ + 'id_smiley' => 7, + 'code' => ':o', + 'description' => '{$default_shocked_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 6, + 'hidden' => 0, + ], + [ + 'id_smiley' => 8, + 'code' => '8)', + 'description' => '{$default_cool_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 7, + 'hidden' => 0, + ], + [ + 'id_smiley' => 9, + 'code' => '???', + 'description' => '{$default_huh_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 8, + 'hidden' => 0, + ], + [ + 'id_smiley' => 10, + 'code' => '::)', + 'description' => '{$default_roll_eyes_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 9, + 'hidden' => 0, + ], + [ + 'id_smiley' => 11, + 'code' => ':P', + 'description' => '{$default_tongue_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 10, + 'hidden' => 0, + ], + [ + 'id_smiley' => 12, + 'code' => ':-[', + 'description' => '{$default_embarrassed_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 11, + 'hidden' => 0, + ], + [ + 'id_smiley' => 13, + 'code' => ':-X', + 'description' => '{$default_lips_sealed_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 12, + 'hidden' => 0, + ], + [ + 'id_smiley' => 14, + 'code' => ':-\\', + 'description' => '{$default_undecided_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 13, + 'hidden' => 0, + ], + [ + 'id_smiley' => 15, + 'code' => ':-*', + 'description' => '{$default_kiss_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 14, + 'hidden' => 0, + ], + [ + 'id_smiley' => 16, + 'code' => ':\'(', + 'description' => '{$default_cry_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 15, + 'hidden' => 0, + ], + [ + 'id_smiley' => 17, + 'code' => '>:D', + 'description' => '{$default_evil_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 16, + 'hidden' => 1, + ], + [ + 'id_smiley' => 18, + 'code' => '^-^', + 'description' => '{$default_azn_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 17, + 'hidden' => 1, + ], + [ + 'id_smiley' => 19, + 'code' => 'O0', + 'description' => '{$default_afro_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 18, + 'hidden' => 1, + ], + [ + 'id_smiley' => 20, + 'code' => ':))', + 'description' => '{$default_laugh_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 19, + 'hidden' => 1, + ], + [ + 'id_smiley' => 21, + 'code' => 'C:-)', + 'description' => '{$default_police_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 20, + 'hidden' => 1, + ], + [ + 'id_smiley' => 22, + 'code' => 'O:-)', + 'description' => '{$default_angel_smiley}', + 'smiley_row' => 0, + 'smiley_order' => 21, + 'hidden' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'smileys'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + unsigned: true, + not_null: 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: [ + [ + 'name' => '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..6439dfaceb --- /dev/null +++ b/Sources/Db/Schema/v3_0/Spiders.php @@ -0,0 +1,204 @@ + '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, + not_null: 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: [ + [ + 'name' => '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..3025a13e4e --- /dev/null +++ b/Sources/Db/Schema/v3_0/Subscriptions.php @@ -0,0 +1,137 @@ +name = 'subscriptions'; + + $this->columns = [ + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_subscribe', + ], + ], + ), + 'idx_active' => new DbIndex( + name: 'idx_active', + columns: [ + [ + 'name' => '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..f8fee3e950 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Themes.php @@ -0,0 +1,161 @@ + 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', + not_null: true, + default: 0, + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'variable' => new Column( + name: 'variable', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'value' => new Column( + name: 'value', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_theme', + ], + [ + 'name' => 'id_member', + ], + [ + 'name' => 'variable', + 'size' => 30, + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => '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..3266be9f43 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Topics.php @@ -0,0 +1,275 @@ + 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, + not_null: 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: [ + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_last_message' => new DbIndex( + name: 'idx_last_message', + type: 'unique', + columns: [ + [ + 'name' => 'id_last_msg', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_first_message' => new DbIndex( + name: 'idx_first_message', + type: 'unique', + columns: [ + [ + 'name' => 'id_first_msg', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_poll' => new DbIndex( + name: 'idx_poll', + type: 'unique', + columns: [ + [ + 'name' => 'id_poll', + ], + [ + 'name' => 'id_topic', + ], + ], + ), + 'idx_is_sticky' => new DbIndex( + name: 'idx_is_sticky', + columns: [ + [ + 'name' => 'is_sticky', + ], + ], + ), + 'idx_approved' => new DbIndex( + name: 'idx_approved', + columns: [ + [ + 'name' => 'approved', + ], + ], + ), + 'idx_member_started' => new DbIndex( + name: 'idx_member_started', + columns: [ + [ + 'name' => 'id_member_started', + ], + [ + 'name' => 'id_board', + ], + ], + ), + 'idx_last_message_sticky' => new DbIndex( + name: 'idx_last_message_sticky', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => 'is_sticky', + ], + [ + 'name' => 'id_last_msg', + ], + ], + ), + 'idx_board_news' => new DbIndex( + name: 'idx_board_news', + columns: [ + [ + 'name' => 'id_board', + ], + [ + 'name' => '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..ce689af37c --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserAlerts.php @@ -0,0 +1,138 @@ +name = 'user_alerts'; + + $this->columns = [ + 'id_alert' => new Column( + name: 'id_alert', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_alert', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + [ + 'name' => 'id_member', + ], + ], + ), + 'idx_alert_time' => new DbIndex( + name: 'idx_alert_time', + columns: [ + [ + 'name' => '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..accdfb9784 --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php @@ -0,0 +1,234 @@ + 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, + not_null: true, + default: 0, + ), + 'alert_pref' => new Column( + name: 'alert_pref', + type: 'varchar', + size: 32, + not_null: true, + default: '', + ), + 'alert_value' => new Column( + name: 'alert_value', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => '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..b564891a7d --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserDrafts.php @@ -0,0 +1,161 @@ +name = 'user_drafts'; + + $this->columns = [ + 'id_draft' => new Column( + name: 'id_draft', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'id_draft', + ], + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + [ + 'name' => 'id_member', + ], + [ + 'name' => 'id_draft', + ], + [ + 'name' => '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..383b10c33f --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserLikes.php @@ -0,0 +1,107 @@ +name = 'user_likes'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'char', + size: 6, + not_null: true, + default: '', + ), + 'content_id' => new Column( + name: 'content_id', + type: 'int', + unsigned: true, + not_null: 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: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + [ + 'name' => 'id_member', + ], + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + [ + 'name' => 'content_id', + ], + [ + 'name' => 'content_type', + ], + ], + ), + 'liker' => new DbIndex( + name: 'liker', + columns: [ + [ + 'name' => '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 @@ +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. 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..cc9dd08570 --- /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..a90f131b01 --- /dev/null +++ b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php @@ -0,0 +1,160 @@ +getDirs(); + + return ( + isset($tasksdir) + && is_dir($tasksdir) + && basename($tasksdir) !== 'Tasks' + && Utils::makeWritable($tasksdir) + && Utils::makeWritable($sourcedir) + ); + } + + /** + * + */ + 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 ( + 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($sourcedir . DIRECTORY_SEPARATOR . 'Tasks' . DIRECTORY_SEPARATOR . '*') as $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'. + 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( + $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]; + } +} diff --git a/Sources/Maintenance/Cleanup/v3_0/index.php b/Sources/Maintenance/Cleanup/v3_0/index.php new file mode 100644 index 0000000000..cc9dd08570 --- /dev/null +++ b/Sources/Maintenance/Cleanup/v3_0/index.php @@ -0,0 +1,8 @@ +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/Maintenance.php b/Sources/Maintenance/Maintenance.php new file mode 100644 index 0000000000..255e4b8c4e --- /dev/null +++ b/Sources/Maintenance/Maintenance.php @@ -0,0 +1,917 @@ + '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; + } + + if ($num === 0) { + self::$tool->logProgress(date_create()->format('c'), reset: true); + } + + // 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(); + + // 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(); + + if ( + Sapi::isCLI() + && !empty(self::$tool->script_file) + && self::getCurrentStep() > count(self::$tool->getSteps()) + ) { + echo Lang::getTxt('cli_please_delete_file', ['file' => self::$tool->script_file], file: 'Maintenance') . PHP_EOL; + } + + 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('/^--([^=\s]+)(?:=(.*))?/', $_SERVER['argv'][$i], $match)) { + $_POST[$match[1]] = $match[2] ?? true; + } + } + } + } + + /** + * 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/Migration/MigrationBase.php b/Sources/Maintenance/Migration/MigrationBase.php new file mode 100644 index 0000000000..d8f47d3b17 --- /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 $db_string, array $db_values = [], ?object $connection = null, ?string $identifier = 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($db_string, $db_values, $connection, $identifier); + 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..cc9dd08570 --- /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..eb97967c0b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php @@ -0,0 +1,214 @@ + $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..438d71c0c1 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsObsolete.php @@ -0,0 +1,129 @@ +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..157ff76ca4 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsWatchedBoards.php @@ -0,0 +1,108 @@ +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..dcf56c6a4e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsWatchedTopics.php @@ -0,0 +1,113 @@ +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..77cca00277 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AttachmentSizes.php @@ -0,0 +1,85 @@ + 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..1b00fa0624 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AutoNotify.php @@ -0,0 +1,123 @@ +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..6508202b58 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/BoardDescriptions.php @@ -0,0 +1,87 @@ +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..ba8a17941e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php @@ -0,0 +1,254 @@ +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..20a7f13a75 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CalendarEvents.php @@ -0,0 +1,88 @@ +getCurrentStructure(); + + foreach ($this->newColumns as $column) { + if (!isset($existing_structure['columns'][$column])) { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..0879194033 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CalendarUpdates.php @@ -0,0 +1,55 @@ +query( + 'DELETE FROM {db_prefix}calendar_holidays + WHERE title in ({array_string:titles})', + [ + 'titles' => array_unique(array_column($table->initial_data, 'title')), + ], + ); + + $table->populate(); + + 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..16341be543 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CategoryDescrptions.php @@ -0,0 +1,55 @@ +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..c159763dbd --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CollapsedCategories.php @@ -0,0 +1,89 @@ +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..2e2b5320bb --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CreateAlerts.php @@ -0,0 +1,175 @@ +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. + $user_alert_prefs_table->populate(); + + $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..59f32abfbd --- /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..3b08a005a3 --- /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..5d164d0dad --- /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 Schema\v2_1\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..bdb3a55c1f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart1.php @@ -0,0 +1,87 @@ +normalize(); + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->populate(); + $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..a29411f4aa --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart2.php @@ -0,0 +1,138 @@ +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..117810e009 --- /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..aeddf2b06a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/FixDates.php @@ -0,0 +1,186 @@ +), which would be similar the more standard DATEFROMPARTS. + + // PostgreSQL does the query a bit different. + $is_pgsql = Db::$db->title === POSTGRE_TITLE; + + if (Maintenance::getCurrentStart() < 1 && $is_pgsql) { + $this->query( + 'UPDATE {db_prefix}calendar + SET start_date = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM start_date) < 1004 THEN 1004 END, EXTRACT(MONTH FROM start_date), EXTRACT(DAY FROM start_date))::date + WHERE EXTRACT(YEAR FROM start_date) < 1004', + [], + ); + } elseif (Maintenance::getCurrentStart() < 1) { + $this->query( + 'UPDATE {db_prefix}calendar + SET start_date = DATE(CONCAT(1004, {literal:-}, MONTH(start_date), {literal:-}, DAY(start_date))) + WHERE YEAR(start_date) < 1004', + [], + ); + } + 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..0d5c505fa2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxAdminInfo.php @@ -0,0 +1,62 @@ +dropIndex('filename'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_filename'); + + $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..841b6dc615 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxBoards.php @@ -0,0 +1,62 @@ +dropIndex('member_groups'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_member_groups'); + + $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..23296f408f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogActivity.php @@ -0,0 +1,62 @@ +dropIndex('mostOn'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('most_on'); + + $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..cbc090c53e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogComments.php @@ -0,0 +1,62 @@ +dropIndex('comment_type'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_comment_type'); + + $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..0f8b68f1c0 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogPackages.php @@ -0,0 +1,62 @@ +dropIndex('filename'); + + $this->handleTimeout(++$start); + } + + if ($start <= 0) { + $table->dropIndex('idx_filename'); + + $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..2267f7cc78 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxMembers.php @@ -0,0 +1,85 @@ +getCurrentStructure(); + + // Drop various indexes that we want to ditch or change. + // Some will be added again in a later step. + if ($start <= 0) { + $table->dropIndex('member_name_low'); + $table->dropIndex('idx_member_name_low'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('real_name_low'); + $table->dropIndex('idx_real_name_low'); + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + $table->dropIndex('active_real_name'); + $table->dropIndex('idx_active_real_name'); + + $this->handleTimeout(++$start); + } + + if ($start <= 3) { + $table->dropIndex('memberName'); + + $this->handleTimeout(++$start); + } + + if ($start <= 4) { + $table->dropIndex('birthdate2'); + $table->dropIndex('idx_birthdate2'); + + $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..fb508b73df --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxMessages.php @@ -0,0 +1,123 @@ +getCurrentStructure(); + + // Drop various indexes that we want to ditch or change. + // Some will be added again in a later step. + if ($start <= 0) { + $table->dropIndex('idx_id_topic'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_topic'); + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + $table->dropIndex('idx_likes'); + + $this->handleTimeout(++$start); + } + + if ($start <= 4) { + $table->dropIndex('ipIndex'); + + $this->handleTimeout(++$start); + } + + if ($start <= 5) { + $table->dropIndex('ip_index'); + + $this->handleTimeout(++$start); + } + + if ($start <= 6) { + $table->dropIndex('related_ip'); + + $this->handleTimeout(++$start); + } + + if ($start <= 7) { + $table->dropIndex('topic'); + + $this->handleTimeout(++$start); + } + + if ($start <= 8) { + $table->dropIndex('id_topic'); + + $this->handleTimeout(++$start); + } + + if ($start <= 9) { + $table->dropIndex('approved'); + + $this->handleTimeout(++$start); + } + + if ($start <= 10) { + $table->dropIndex('idx_approved'); + + $this->handleTimeout(++$start); + } + + if ($start <= 11) { + $table->dropIndex('id_board'); + + $this->handleTimeout(++$start); + } + + if ($start <= 12) { + $table->dropIndex('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..6eca737fe9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxScheduledTasks.php @@ -0,0 +1,62 @@ +dropIndex('task'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_task'); + + $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..58c487f8f0 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxTopics.php @@ -0,0 +1,62 @@ +dropIndex('id_board'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $table->dropIndex('idx_id_board'); + + $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..c3fcbc0531 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php @@ -0,0 +1,162 @@ +getCurrentStructure(); + + return !isset($existing_structure['columns']['ip_low']) || !isset($existing_structure['columns']['ip_high']); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new 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 (Db::$db->title == 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', + ); + + $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..00ebc29c9c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php @@ -0,0 +1,343 @@ +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 (Db::$db->title === 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 (Db::$db->title !== 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 (Db::$db->title !== 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'] !== (Db::$db->title === 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..928a2eec59 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogAction.php @@ -0,0 +1,88 @@ +title !== POSTGRE_TITLE) { + $this->name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogActions(); + $existing_structure = $table->getCurrentStructure(); + + if (Db::$db->title === 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 (Db::$db->title === 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..f1834694ff --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogBanned.php @@ -0,0 +1,88 @@ +title !== POSTGRE_TITLE) { + $this->name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogBanned(); + $existing_structure = $table->getCurrentStructure(); + + if (Db::$db->title === 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 (Db::$db->title === 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..0ba8bfe91f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogErrors.php @@ -0,0 +1,98 @@ +title !== POSTGRE_TITLE) { + $this->name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogErrors(); + $existing_structure = $table->getCurrentStructure(); + + if (Db::$db->title === 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 (Db::$db->title === 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..e83c5aa618 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogFloodControl.php @@ -0,0 +1,84 @@ +getCurrentStructure(); + + if (Db::$db->title === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..1f5a8903b1 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php @@ -0,0 +1,64 @@ +getCurrentStructure(); + + if (Db::$db->title === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..6fea6c6a81 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogReportedComments.php @@ -0,0 +1,70 @@ +title !== POSTGRE_TITLE) { + $this->name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new Schema\v2_1\LogFloodcontrol(); + $existing_structure = $table->getCurrentStructure(); + + if (Db::$db->title === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..d6e2761121 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MemberLogins.php @@ -0,0 +1,70 @@ +title !== POSTGRE_TITLE) { + $this->name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new Schema\v2_1\MemberLogins(); + $existing_structure = $table->getCurrentStructure(); + + if (Db::$db->title === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..fb15f9e6d1 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP.php @@ -0,0 +1,61 @@ +getCurrentStructure(); + + if (Db::$db->title === 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 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..9fcd56674b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP2.php @@ -0,0 +1,61 @@ +getCurrentStructure(); + + if (Db::$db->title === 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 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..2142e25f3d --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6Messages.php @@ -0,0 +1,79 @@ +getCurrentStructure(); + + if (Db::$db->title === 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 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..d159239af4 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LegacyAttachments.php @@ -0,0 +1,281 @@ +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..41744d9ae0 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LegacyData.php @@ -0,0 +1,204 @@ +title === MYSQL_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + // Updating board_permissions + if ($start <= 0) { + $this->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..8df65eb442 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogErrorsBacktrace.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'backtrace') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..026a027f4c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogOnlineURL.php @@ -0,0 +1,52 @@ +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..de53cb179c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogReportedCommentsEmail.php @@ -0,0 +1,78 @@ +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 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..7a33908b2d --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogSpiderHitsURL.php @@ -0,0 +1,53 @@ +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..a61a436ecb --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MailQueue.php @@ -0,0 +1,51 @@ +columns as $column) { + if ($column->name === 'body') { + $table->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..13521c3983 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MemberGroupsTfaRequired.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_required') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..0c4f8b44b5 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembergroupIcon.php @@ -0,0 +1,127 @@ +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..257b9a2cc2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersHideEmail.php @@ -0,0 +1,78 @@ +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 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..a9e79ad78e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersLangUTF8.php @@ -0,0 +1,51 @@ +query( + 'UPDATE {db_prefix}members + SET lngfile = REPLACE(lngfile, {string:utf8}, {string:empty})', + [ + 'utf8' => '-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..78a1d63225 --- /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 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..f4d27ac08b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTfaBackup.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_backup') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..c0868b2fee --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTfaSecret.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_secret') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..44e176d890 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTimezone.php @@ -0,0 +1,73 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'timezone') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new 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..b2c6a91a12 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Mentions.php @@ -0,0 +1,58 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'mentions', $tables); + } + + /** + * + */ + public function execute(): bool + { + $table = new Schema\v2_1\Mentions(); + $table->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..ec6fe8a5db --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MessagesModifiedReason.php @@ -0,0 +1,70 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'modified_reason') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new 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..bf6303b498 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php @@ -0,0 +1,58 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'moderator_groups', $tables); + } + + /** + * + */ + public function execute(): bool + { + $table = new Schema\v2_1\ModeratorGroups(); + $table->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..9f3080fe43 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MovedTopics.php @@ -0,0 +1,64 @@ +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..12a995ea12 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MysqlLegacyData.php @@ -0,0 +1,204 @@ +title === MYSQL_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + // Updating board_permissions + if ($start <= 0) { + $this->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..b9b7a2be9b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MysqlModFixes.php @@ -0,0 +1,149 @@ +title === MYSQL_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + // make members mod col nullable + if ($start <= 0) { + $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 . '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..d6c0a8ba60 --- /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..3c19059ac6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Permissions.php @@ -0,0 +1,323 @@ + '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..57c4aa35b6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php @@ -0,0 +1,343 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'message_labels') { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $pm_labels_table = new Schema\v2_1\PmLabels(); + $pm_labeled_messages_table = new Schema\v2_1\PmLabeledMessages(); + + $tables = Db::$db->list_tables(); + + if ($start <= 0) { + if (!in_array(Config::$db_prefix . 'pm_labels', $tables)) { + $pm_labels_table->create(); + $this->handleTimeout(0); + } + + if (!in_array(Config::$db_prefix . 'pm_labeled_messages', $tables)) { + $pm_labeled_messages_table->create(); + $this->handleTimeout(0); + } + + $pm_recipients_table = new Schema\v2_1\PmRecipients(); + $existing_structure = $pm_recipients_table->getCurrentStructure(); + + foreach ($pm_recipients_table->columns as $column) { + // Column exists, don't need to do this. + if (isset($existing_structure['columns'][$column->name])) { + continue; + } + + $pm_recipients_table->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; + } + } + } + + $pm_recipients_table = new Schema\v2_1\PmRecipients(); + $existing_structure = $pm_recipients_table->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'labels') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $pm_recipients_table->dropColumn($col); + } + } + + $members_table = new Schema\v2_1\Members(); + $existing_structure = $members_table->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'message_labels') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $members_table->dropColumn($col); + } + } + + 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..633ff40435 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageNotification.php @@ -0,0 +1,140 @@ +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 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..eba313f93b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlFindInSet.php @@ -0,0 +1,63 @@ +title === POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $this->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..7fa2f4f82c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlIPv6Helper.php @@ -0,0 +1,63 @@ +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/PostgreSqlSchemaDiff.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlSchemaDiff.php new file mode 100644 index 0000000000..94cf0a0417 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlSchemaDiff.php @@ -0,0 +1,149 @@ +title === POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + while (Maintenance::getCurrentStart() < count($this->schema_fixes)) { + $fix = $this->schemaFixes[Maintenance::getCurrentStart()]; + + $this->query( + 'ALTER TABLE {db_prefix}' . $fix[0] . ' + ' . $fix[1], + ); + + Maintenance::setCurrentStart(); + $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/PostgreSqlSequences.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php new file mode 100644 index 0000000000..c33ba78393 --- /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 Db::$db->title === 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/PostgreSqlTime.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php new file mode 100644 index 0000000000..0389f22d9d --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php @@ -0,0 +1,99 @@ +title === POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + // FROM_UNIXTIME fix + if ($start <= 0) { + // Drop the old int version + $this->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); + } + + // Indexable month and day. Used for birthdays. + if ($start <= 2) { + $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); + } + + 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..bf2b675e16 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlUnlogged.php @@ -0,0 +1,119 @@ +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/RemoveKarma.php b/Sources/Maintenance/Migration/v2_1/RemoveKarma.php new file mode 100644 index 0000000000..12f9f4a52e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/RemoveKarma.php @@ -0,0 +1,84 @@ +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..effd255dc2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ScheduledTasks.php @@ -0,0 +1,133 @@ +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); + } + + $inserts = []; + + foreach ($this->newTasks as $task) { + $request = $this->query( + 'SELECT id_task + FROM {db_prefix}scheduled_tasks + WHERE task = {string:task}', + [ + 'task' => $task[5], + ], + ); + + if (Db::$db->num_rows($request) === 0) { + $inserts[] = $task; + } + + Db::$db->free_result($request); + } + + if (!empty($inserts)) { + Db::$db->insert( + method: 'replace', + table: '{db_prefix}scheduled_tasks', + columns: [ + 'next_time' => 'int', + 'time_offset' => 'int', + 'time_regularity' => 'int', + 'time_unit' => 'string', + 'disabled' => 'int', + 'task' => 'string', + 'callable' => 'string', + ], + data: $inserts, + keys: [ + 'next_time', + 'time_offset', + 'time_regularity', + 'time_unit', + 'disabled', + 'task', + 'callable', + ], + ); + + } + + // 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..4e654a0d0e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/SessionIDs.php @@ -0,0 +1,71 @@ +columns as $column) { + if ($column->name !== 'session') { + continue; + } + + $log_online_table->alterColumn($column); + } + + foreach ($log_errors_table->columns as $column) { + if ($column->name !== 'session') { + continue; + } + + $log_errors_table->alterColumn($column); + } + + foreach ($sessions_table->columns as $column) { + if ($column->name !== 'session_id') { + continue; + } + + $sessions_table->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..c1ed74e1e9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/SettingsUpdate.php @@ -0,0 +1,255 @@ + 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..319ca31892 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Smileys.php @@ -0,0 +1,192 @@ +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::getTxt('default_fugue_smileyset_name', file: 'Maintenance'), + 'png', + ]; + + $combined['alienine'] = [ + Lang::getTxt('default_alienine_smileyset_name', file: 'Maintenance'), + 'png', + ]; + + // Add/fix our 2.0 sets (to correct past problems where these got corrupted) + $combined['default'] = [ + Lang::getTxt('default_legacy_smileyset_name', file: 'Maintenance'), + 'gif', + ]; + + $combined['aaron'] = [ + Lang::getTxt('default_aaron_smileyset_name', file: 'Maintenance'), + 'gif', + ]; + + $combined['akyhne'] = [ + Lang::getTxt('default_akyhne_smileyset_name', file: 'Maintenance'), + '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 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..c839eba0b2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ThemeSettings.php @@ -0,0 +1,185 @@ + '3000', + ]; + + protected array $removed_theme_settings = [ + '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->updated_theme_settings 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->removed_theme_settings, + ], + ); + + $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..9bab1f26f2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/TopicUnwatch.php @@ -0,0 +1,58 @@ +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 + $table->dropColumn('disregarded'); + } + + 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..c97eb91804 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/UserDrafts.php @@ -0,0 +1,179 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'user_drafts', $tables) || Maintenance::getCurrentStart() > 0; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $drafts_table = new Schema\v2_1\UserDrafts(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if ($start <= 0 && !in_array(Config::$db_prefix . 'user_drafts', $tables)) { + $drafts_table->create(); + + $this->handleTimeout(++$start); + } + + // Adding draft permissions. + if ( + $start <= 1 + && version_compare( + str_replace(' ', '.', strtolower(Config::$modSettings['smfVersion'] ?? '0.0.dev.0')), + '2.1.dev.0', + '<', + ) + ) { + // 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..d99a036d3d --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ValidationServers.php @@ -0,0 +1,125 @@ +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..133712341c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php @@ -0,0 +1,110 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'qanda', $tables); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new Schema\v2_1\Qanda(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if ($start <= 0 && !in_array(Config::$db_prefix . 'qanda', $tables)) { + $table->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..cc9dd08570 --- /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..6c6cbb1284 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/EventUids.php @@ -0,0 +1,69 @@ +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/other/upgrade_3-0_PostgreSQL.sql b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php similarity index 56% rename from other/upgrade_3-0_PostgreSQL.sql rename to Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php index 4a59c46a5f..666cbf2cef 100644 --- a/other/upgrade_3-0_PostgreSQL.sql +++ b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php @@ -1,223 +1,48 @@ -/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ - -/******************************************************************************/ ---- Language Upgrade... -/******************************************************************************/ - ----{ -$limit = 10000; -$statements = []; -$langs = []; -$args = ['defaultLang' => '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) +query( - 'SELECT id_member - FROM {db_prefix}members - WHERE lngfile IN ({array_string:possible_languages}) - ORDER BY id_member - LIMIT {int:limit}', - [ - 'limit' => $limit, - 'possible_languages' => $langs - ] - ); - if (Db::$db->num_rows($request) == 0) { - $is_done = true; - break; - } else { - 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; - } else { - $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 - ); -} - -// Rename the privacy policy records. -foreach (Config::$modSettings as $variable => $value) { - if (!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); - } -} ----} ----# - -/******************************************************************************/ ---- Updating log_errors table -/******************************************************************************/ - ----# Fixing the default for the sessions column ----{ -Db::$db->change_column('{db_prefix}log_errors', 'session', ['default' => '']); ----} ----# - -/******************************************************************************/ ---- Adding version information to posts, polls, and personal messages -/******************************************************************************/ - ----# Adding a new column "version" to messages table -ALTER TABLE {$db_prefix}messages -ADD COLUMN IF NOT EXISTS version VARCHAR(5) NOT NULL DEFAULT ''; ----# - ----# Adding a new column "version" to personal_messages table -ALTER TABLE {$db_prefix}personal_messages -ADD COLUMN IF NOT EXISTS version VARCHAR(5) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Adding support for recurring events... -/******************************************************************************/ - ----# Add duration, rrule, rdates, and exdates columns to calendar table -ALTER TABLE {$db_prefix}calendar -ADD COLUMN IF NOT EXISTS duration varchar(32) NOT NULL DEFAULT '', -ADD COLUMN IF NOT EXISTS rrule varchar(1024) NOT NULL DEFAULT 'FREQ=YEARLY;COUNT=1', -ADD COLUMN IF NOT EXISTS rdates text NOT NULL, -ADD COLUMN IF NOT EXISTS exdates text NOT NULL, -ADD COLUMN IF NOT EXISTS adjustments jsonb DEFAULT NULL, -ADD COLUMN IF NOT EXISTS sequence smallint NOT NULL DEFAULT '0', -ADD COLUMN IF NOT EXISTS uid varchar(255) NOT NULL DEFAULT '', -ADD COLUMN IF NOT EXISTS type smallint NOT NULL DEFAULT '0', -ADD COLUMN IF NOT EXISTS enabled smallint NOT NULL DEFAULT '1'; ----# - ----# Set duration and rrule values and change end_date ----{ -$cols = Db::$db->list_columns('{db_prefix}calendar'); - -if (in_array('end_time', $cols)) { - $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'); - - $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 - ); - } -} ----} ----# - ----# Drop end_time column from calendar table -ALTER TABLE {$db_prefix}calendar -DROP COLUMN end_time; ----# - ----# Migrate holidays to events ----{ -$request = Db::$db->query('SELECT 1 - FROM information_schema.tables - WHERE table_schema = {string:db_name} - AND table_name = {string:table_name}', - [ - 'db_name' => Config::$db_name, - 'table_name' => Config::$db_prefix . 'calendar_holidays', - ] -); -$exists = Db::$db->num_rows($request) > 0; -Db::$db->free_result($request); - -if ($exists) { - if (!isset(\SMF\User::$me)) { - \SMF\User::load(); - } - - if (empty(\SMF\User::$me->id) && !empty($upcontext['user']['id'])) { - \SMF\User::setMe($upcontext['user']['id']); - } - - $known_holidays = [ + /******************* + * Public properties + *******************/ + + /** + * + */ + public string $name = 'Migrating holidays to events'; + + /********************* + * Internal properties + *********************/ + + /** + * @var array + * + * Data for holidays that are included in SMF's default when installing. + */ + private array $known_holidays = [ 'April Fools' => [ 'title' => "April Fools' Day", 'start_date' => '2000-04-01', @@ -788,137 +613,70 @@ ], ]; - $request = Db::$db->query('SELECT title, GROUP_CONCAT(event_date) as rdates - FROM {db_prefix}calendar_holidays - GROUP BY title', - [] - ); + /**************** + * Public methods + ****************/ - while ($row = Db::$db->fetch_assoc($request)) { - if (isset($known_holidays[$row['title']])) { - $holiday = &$known_holidays[$row['title']]; + /** + * + */ + public function execute(): bool + { + $exists = count(Db::$db->list_tables(false, Config::$db_prefix . 'calendar_holidays')) > 0; - $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 \SMF\Time($holiday['start_date'] . (!$holiday['allday'] ? ' ' . $holiday['start_time'] . ' ' . $holiday['timezone'] : '')); - $holiday['duration'] = new \DateInterval($holiday['duration'] ?? 'P1D'); - $holiday['recurrence_end'] = new \SMF\Time($holiday['recurrence_end']); - unset($holiday['start_date'], $holiday['start_time'], $holiday['timezone']); - - $event = new \SMF\Calendar\Event(0, $known_holidays[$row['title']]); - } else { - $row['type'] = 1; - $row['allday'] = true; - $row['recurrence_end'] = new \SMF\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 \SMF\Time(preg_replace('/^100\d-/', '2000-', $row['start'])); - $row['rrule'] = 'FREQ=YEARLY'; - } else { - $row['start'] = new \SMF\Time($row['start']); - $row['rrule'] = 'FREQ=DAILY;COUNT=1'; + if ($exists) { + if (!isset(User::$me)) { + User::load(); } - $event = new \SMF\Calendar\Event(0, $row); - } + $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(); + } - $event->save(); - } + Db::$db->free_result($request); - Db::$db->free_result($request); -} ----} ----# - ----# Setting the UID column for calendar events. ----{ -$calendar_updates = []; -$request = Db::$db->query('SELECT id_event, uid - FROM {db_prefix}calendar', - [], -); + Db::$db->drop_table('{db_prefix}calendar_holidays'); + } -while ($row = Db::$db->fetch_assoc($request)) { - if ($row['uid'] === '') { - $calendar_updates[] = ['id_event' => $row['id_event'], 'uid' => (string) new Uuid()]; + return true; } } -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, - ); -} ----} ----# - ----# Dropping "calendar_holidays" -DROP TABLE IF EXISTS {$db_prefix}calendar_holidays; ----# - -/******************************************************************************/ ---- Adding SpoofDetector support -/******************************************************************************/ - ----# Adding a new column "spoofdetector_name" to members table -ALTER TABLE {$db_prefix}members -ADD COLUMN IF NOT EXISTS spoofdetector_name VARCHAR(255) NOT NULL DEFAULT ''; -CREATE INDEX {$db_prefix}idx_spoofdetector_name ON {$db_prefix}members (spoofdetector_name); -CREATE INDEX {$db_prefix}idx_spoofdetector_name_id ON {$db_prefix}members (spoofdetector_name, id_member); ----# - ----# Adding new "spoofdetector_censor" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('spoofdetector_censor', '1') ON CONFLICT DO NOTHING; ----# - -/******************************************************************************/ ---- Adding SMF version information to log_packages -/******************************************************************************/ - ----# Adding a new column "smf_version" to log_packages table -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN IF NOT EXISTS smf_version VARCHAR(5) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Improving search results storage -/******************************************************************************/ - ----# Improving search results storage -ALTER TABLE {$db_prefix}log_search_results DROP CONSTRAINT {$db_prefix}log_search_results_pkey; -ALTER TABLE {$db_prefix}log_search_results ADD PRIMARY KEY (id_search, id_topic, id_msg); ----# - -/******************************************************************************/ ---- Updating Settings -/******************************************************************************/ - ----# Update mail_type -UPDATE {$db_prefix}settings -SET value = - CASE - WHEN value = '0' - THEN 'SendMail' - WHEN value = '1' - THEN 'SMTP' - WHEN value = '2' - THEN 'SMTPTLS' - ELSE - value - END -WHERE variable = 'mail_type' - AND value IN ('0','1','2'); ----# - ----# Remove cookieTime setting -DELETE FROM {$db_prefix}settings -WHERE variable = 'cookieTime'; ----# diff --git a/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php new file mode 100644 index 0000000000..4a7e0d78b5 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php @@ -0,0 +1,146 @@ + '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..eae04ac8c9 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/MailType.php @@ -0,0 +1,67 @@ +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..201142ac52 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/MessageVersion.php @@ -0,0 +1,69 @@ +getCurrentStructure(); + + if (!isset($existing_structure['columns']['version'])) { + foreach ($table->columns as $column) { + if ($column->name === 'version') { + $table->addColumn($column); + break; + } + } + } + + $this->handleTimeout(); + + $table = new 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..62b94e51fb --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/PackageVersion.php @@ -0,0 +1,55 @@ +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/PermissionChanges.php b/Sources/Maintenance/Migration/v3_0/PermissionChanges.php new file mode 100644 index 0000000000..5ddaf21c34 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/PermissionChanges.php @@ -0,0 +1,53 @@ +query( + 'DELETE FROM {db_prefix}permissions + WHERE permission IN ({array_string:perms})', + [ + 'perms' => [ + 'send_email_to_members', + ], + ], + ); + + 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..4413d5e9f2 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php @@ -0,0 +1,135 @@ +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..1119e74339 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/SearchResultsPrimaryKey.php @@ -0,0 +1,53 @@ +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..356ff60ec3 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/SpoofDetector.php @@ -0,0 +1,68 @@ +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..cc9dd08570 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/index.php @@ -0,0 +1,8 @@ +id = $id; + $this->name = $name; + $this->title = $title ?? $name; + $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; + } + + /** + * 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/SubStepInterface.php b/Sources/Maintenance/SubStepInterface.php new file mode 100644 index 0000000000..4dea4f07f6 --- /dev/null +++ b/Sources/Maintenance/SubStepInterface.php @@ -0,0 +1,40 @@ +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->getProgress(); + + // Template needs to know about this. + Maintenance::$context['started'] = $this->time_started; + } + + /** + * + */ + public function getScriptName(): string + { + return Lang::getTxt('smf_installer', file: 'Maintenance'); + } + + /** + * 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. The value of $this->getScriptName(). + * + * @return string The title for the page. + */ + public function getPageTitle(): string + { + return $this->page_title ?? $this->getStep()->getTitle() ?? $this->getScriptName(); + } + + /** + * + */ + public function hasSteps(): bool + { + return true; + } + + /** + * + */ + public function getSteps(): array + { + return [ + 0 => new Step( + id: 1, + 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: 'Maintenance'), + function: 'checkFilesWritable', + template: 'checkFilesWritable', + progress: 10, + ), + 2 => new Step( + id: 3, + 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: '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: '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: '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: 'Maintenance'), + function: 'finalize', + template: 'finalize', + progress: 0, + ), + ]; + } + + /** + * + */ + public function getStepTitle(): string + { + return $this->getStep()->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; + } + + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + + if (Maintenance::isInstalled()) { + Maintenance::$context['warning'] = Lang::getTxt('error_already_installed', file: 'Maintenance'); + $this->logProgress(Maintenance::$context['warning']); + } + + 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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // Make sure we have a supported database + if (empty(Maintenance::$context['supported_databases'])) { + Maintenance::$fatal_error = Lang::getTxt('error_db_missing', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // How about session support? Some crazy sysadmin remove it? + if (!function_exists('session_start')) { + Maintenance::$errors[] = Lang::getTxt('error_session_missing', file: 'Maintenance'); + $this->logProgress(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: 'Maintenance'); + $this->logProgress(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: 'Maintenance'); + $this->logProgress(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: 'Maintenance') . '

' . Lang::getTxt('error_message_click', file: 'Maintenance') . ' ' . Lang::getTxt('error_message_bad_try_again', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('error_mod_security', file: 'Maintenance')); + } + + // 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. + $supported_streams = stream_get_wrappers(); + + if (!in_array('https', $supported_streams)) { + Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('install_no_https', file: 'Maintenance')); + } + + if (empty(Maintenance::$errors)) { + Maintenance::$context['continue'] = true; + } + + // Are we doing debug? + if (isset($_REQUEST['debug'])) { + $this->debug = true; + } + + return false; + } + + /** + * Check Files Writable action. + * + * @return bool True if we can continue, false otherwise. + */ + public function checkFilesWritable(): bool + { + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + $writable_files = [ + 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) { + $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') ? Config::$boarddir . '/.htaccess' : Config::$boarddir; + } + + return $this->makeFilesWritable($writable_files); + } + + /** + * 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; + } + + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + // 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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // Validate the prefix. + $db = Maintenance::$context['databases'][$db_type]; + + if (!$db->validatePrefix($db_prefix)) { + Maintenance::$fatal_error = Lang::getTxt('error_db_prefix_invalid', ['prefix' => $db_prefix], file: 'Maintenance'); + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => Maintenance::$fatal_error], file: 'Maintenance')); + + 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']; + } + + // Save the settings. + if (!$this->updateSettingsFile($vars)) { + 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']); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // We need to make some queries that would trigger 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: 'Maintenance') . '
' . $db_error . '
'; + $this->logProgress(Lang::getTxt('error_db_connect', file: 'Maintenance') . ': ' . $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]); + $this->logProgress(Maintenance::$fatal_error); + + 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; + $this->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]); + $this->logProgress(Maintenance::$fatal_error); + + 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']; + + if (!$this->updateSettingsFile(['db_type' => Config::$db_type])) { + 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; + + // Do we have a failure of database configuration? + try { + Db::$db->checkConfiguration(); + } catch (\Throwable $e) { + Maintenance::$fatal_error = $e->getMessage(); + $this->logProgress(Maintenance::$fatal_error); + + 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; + } + + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + // 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(), + ]; + + if (!$this->updateSettingsFile($vars)) { + 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; + } + + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + // Reload settings. + Config::load(); + Db::load(); + $newSettings = []; + + Config::$modSettings['disableQueryCheck'] = true; + + $replaces = [ + '{$db_prefix}' => Db::$db->prefix, + '{$attachdir}' => json_encode([1 => Db::$db->escape_string(Config::$boarddir . '/attachments')]), + '{$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(); + + $install_tables = Table::getAll($this->schema_version); + + // Before running any of the queries, let's make sure another version isn't already installed. + if (in_array(Config::$db_prefix . 'settings', $existing_tables)) { + $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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + } + } + + Maintenance::$context['sql_results'] = [ + 'tables' => 0, + 'inserts' => 0, + 'table_dups' => 0, + 'insert_dups' => 0, + ]; + + foreach ($install_tables as $table) { + $this->logProgress(Lang::getTxt('log_table_create', ['table' => Config::$db_prefix . $table->name], file: 'Maintenance'), true); + + // Create the table, unless it already exists. + if (!in_array(Config::$db_prefix . $table->name, $existing_tables)) { + try { + if (!$table->create()) { + throw new \Exception(Db::$db->error()); + } + + Maintenance::$context['sql_results']['tables']++; + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = trim($e->getMessage()); + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => trim($e->getMessage())], file: 'Maintenance')); + + continue; + } + } else { + Maintenance::$context['sql_results']['table_dups']++; + $this->logProgress(Lang::getTxt('log_skipped', file: 'Maintenance')); + } + + // If this table has some initial data to insert, do so. + if (!empty($table->initial_data)) { + $this->logProgress(Lang::getTxt('log_table_populate', ['table' => Config::$db_prefix . $table->name], file: 'Maintenance'), true); + + try { + $num_inserts = $table->populate(); + + Maintenance::$context['sql_results']['inserts'] += $num_inserts; + Maintenance::$context['sql_results']['insert_dups'] += (count($table->initial_data) - $num_inserts); + + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = $table->name . ':' . $e->getMessage(); + + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + } + } + + // 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], file: 'Maintenance'); + + $this->logProgress(Maintenance::$context['sql_results'][$key]); + } + } + + $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(); + + if (!empty($newSettings)) { + $this->updateModSettings($newSettings); + } + + // Let's optimize those new tables, but not on InnoDB, ok? (SMF will check this) + foreach ($install_tables as $table) { + try { + if (!(Db::$db->optimize_table(Config::$db_prefix . $table->name) > -1)) { + Maintenance::$context['failures'][] = Db::$db->error(); + $this->logProgress(Db::$db->error()); + } + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = $e->getMessage(); + $this->logProgress($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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + } + + // Was this a refresh? + if (count($existing_tables) > 0) { + $this->page_title = Lang::getTxt('user_refresh_install', file: 'Maintenance'); + 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; + } + + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + // 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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // Not matching passwords? + if ($_POST['password1'] != $_POST['password2']) { + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_again_match', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // No password? + if (strlen($_POST['password1']) < 4) { + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_no_password', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + if (!file_exists(Config::$sourcedir . '/Utils.php')) { + Maintenance::$fatal_error = Lang::getTxt('error_sourcefile_missing', ['file' => 'Utils.php']); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // Update the webmaster's email? + if (!empty($_POST['server_email']) && (empty(Config::$webmaster_email) || Config::$webmaster_email == $settingsDefs['webmaster_email']['default'])) { + $this->updateSettingsFile(['webmaster_email' => (string) $_POST['server_email']]); + } + + // Normalize Unicode characters. + $_POST['username'] = Utils::normalize($_POST['username']); + + // Replace any kind of space or illegal character with a normal space, and then trim. + $_POST['username'] = Utils::htmlTrim(Utils::normalizeSpaces(Utils::sanitizeChars($_POST['username'], 1, ' '), true, true, ['no_breaks' => true, 'replace_tabs' => true, 'collapse_hspace' => true])); + + $username_errors = User::validateUsername(0, $_POST['username'], true, false); + + if (!empty($username_errors)) { + foreach ($username_errors as $error) { + switch ($error[1]) { + case 'error_long_name': + Maintenance::$fatal_error = Lang::getTxt('error_username_too_long', file: 'Maintenance'); + break; + + case 'need_username': + Maintenance::$fatal_error = Lang::getTxt('error_username_left_empty', file: 'Maintenance'); + break; + + case 'username_reserved': + Maintenance::$fatal_error = Lang::getTxt('username_reserved', $error[3], file: 'Errors'); + break; + + default: + Maintenance::$fatal_error = Lang::getTxt('error_invalid_characters_username', file: 'Maintenance'); + break; + } + } + + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + $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) { + Maintenance::$context += Db::$db->fetch_row($result); + Db::$db->free_result($result); + + Maintenance::$context['account_existed'] = Lang::getTxt('error_user_settings_taken', file: 'Maintenance'); + + return false; + } + + if (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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + if (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: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + if ($_POST['username'] != '') { + Maintenance::$context['password_salt'] = bin2hex(random_bytes(16)); + + $ip = isset($_SERVER['REMOTE_ADDR']) ? substr($_SERVER['REMOTE_ADDR'], 0, 255) : ''; + + $_POST['password1'] = Security::hashPassword($_POST['password1']); + + try { + Maintenance::$context['id_member'] = 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['password_salt'], + '', + '', + '', + $ip, + $ip, + '', + '', + '', + '', + '', + '', + '', + '', + '', + ], + ], + ['id_member'], + 1, + ); + + if ((int) Maintenance::$context['id_member'] > 0) { + return true; + } + + Maintenance::$fatal_error = trim(Db::$db->error()); + $this->logProgress(Maintenance::$fatal_error); + + return false; + + } catch (\Throwable $e) { + Maintenance::$fatal_error = $e->getMessage(); + $this->logProgress(Maintenance::$fatal_error); + } + } + + return false; + } + + /** + * Delete Install action. + * + * @return bool True if we can continue, false otherwise. + */ + 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['continue'] = false; + + // Rebuild the settings file. + $this->updateSettingsFile(['maintenance_tool_progress' => ''], 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['id_member']) ? 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['id_member'], Maintenance::$context['password_salt'])) { + Cookie::setLoginCookie(3153600 * 60, Maintenance::$context['id_member'], Cookie::encrypt($_POST['password1'], Maintenance::$context['password_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: 'Maintenance'))); + } + 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['id_member'])) { + User::setMe((int) Maintenance::$context['id_member']); + } 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 + $this->updateModSettings([ + 'disabledBBC' => implode(',', Utils::$context['legacy_bbc']), + ]); + + // Some final context for the template. + Maintenance::$context['dir_still_writable'] = is_writable(Config::$boarddir); + Maintenance::$context['can_delete_script'] = $this->canDeleteTool(); + + // Update hash's cost to an appropriate setting + $this->updateModSettings([ + 'bcrypt_hash_cost' => Security::hashBenchmark(), + ]); + + $this->logProgress(Lang::getTxt('log_install_complete', file: 'Maintenance')); + + if (!Sapi::isCLI() && $this->isDebug()) { + Maintenance::$context['log_contents'] = file_get_contents($this->log_file); + } + + $this->finalizeLog(); + + return false; + } + + /** + * Write out our current information to our settings file to track the upgrade progress. + */ + public function preExit(): void + { + $this->saveProgress(); + } + + /****************** + * 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 getProgress(): void + { + $defined_vars = Config::getCurrentSettings(); + + $data = isset($defined_vars['maintenance_tool_progress']) ? Utils::jsonDecode($defined_vars['maintenance_tool_progress'], true) : []; + + $this->time_started = (int) ($data['started'] ?? time()); + $this->debug = !empty($data['debug']); + } + + /** + * Save our data. + * + * @return bool True if we could update our settings file, false otherwise. + */ + private function saveProgress(): bool + { + return $this->updateSettingsFile(['maintenance_tool_progress' => json_encode([ + 'started' => $this->time_started, + 'debug' => $this->debug, + ])]); + } + + /** + * 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. + * + * @return string A valid time zone identifier. + */ + private function determineTimezone(): string + { + if (isset(Config::$modSettings['default_timezone']) && in_array(Config::$modSettings['default_timezone'], timezone_identifiers_list(\DateTimeZone::ALL_WITH_BC))) { + return Config::$modSettings['default_timezone']; + } + + // 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(\DateTimeZone::ALL_WITH_BC))) { + $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; + } + + date_default_timezone_set('UTC'); + + return 'UTC'; + } +} diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php new file mode 100644 index 0000000000..2c77f5d2a3 --- /dev/null +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -0,0 +1,676 @@ +setStep() and retrieved by $this->getStep(). + */ + private ?Step $current_step; + + /** + * @var FtpConnection + * + * Object container for the FTP session. + */ + private FtpConnection $ftp; + + /**************** + * 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; + } + + /** + * Updates the tool's log with new info. + * + * If using the CLI interface, the message is also printed to STDOUT. + * + * @param mixed $message The message to append to the log. + * If not a string, will be converted into one using print_r(). + * @param bool $ongoing Whether this message indicates an incomplete action. + * Default: false. + * @param bool $reset If true, wipes out the old contents of the log file. + * Default: false. + */ + public function logProgress(mixed $message, bool $ongoing = false, bool $reset = false): void + { + if (!is_string($message)) { + $message = print_r($message, true); + } + + $message = preg_replace('/(]*>)+|\R/', PHP_EOL, $message); + + $message .= $ongoing ? '... ' : PHP_EOL; + + if (Sapi::isCLI()) { + echo $message; + } + + if (!isset($this->log_file)) { + $name = isset($this->script_file) ? pathinfo($this->script_file, PATHINFO_FILENAME) : substr($this::class, strrpos($this::class, '\\') + 1); + + foreach ( + [ + Config::$boarddir . DIRECTORY_SEPARATOR . 'logs', + Config::$boarddir, + Sapi::getTempDir(), + ] as $dir + ) { + if (!file_exists($dir)) { + Utils::makeWritable(dirname($dir)); + @mkdir($dir, 0750); + } + + if (is_dir($dir) && Utils::makeWritable($dir)) { + break; + } + } + + $this->log_file = $dir . DIRECTORY_SEPARATOR . $name . '.log'; + } + + file_put_contents($this->log_file, $message, $reset ? 0 : FILE_APPEND); + } + + /** + * Stores the log in a secure directory, or deletes it on failure. + * + * The saved log file is named after the tool plus a UTC timestamp, with a + * '.log' file extension. + * + * @return string Path to the saved log file, or null if log was deleted. + */ + public function finalizeLog(): ?string + { + if (!isset($this->log_file) || !file_exists($this->log_file)) { + return null; + } + + $dir = Config::$boarddir . DIRECTORY_SEPARATOR . 'logs'; + + $new_name = $dir . DIRECTORY_SEPARATOR . pathinfo($this->log_file, PATHINFO_FILENAME) . '_' . date_create('now UTC')->format('YmdHis') . '.log'; + + if (!file_exists($dir)) { + Utils::makeWritable(Config::$boarddir); + @mkdir($dir, 0750); + } + + if ( + !is_dir($dir) + || !Utils::makeWritable($dir) + || Security::secureDirectory($dir) !== true + || !@rename($this->log_file, $new_name) + ) { + @unlink($this->log_file); + + return null; + } + + return $new_name; + } + + /** + * Find all databases that are supported on this system. + * + * @return array An array of supported databases in the format of + * $db_key => (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; + } + + /** + * 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. + * + * 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 ($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']); + } + + if (isset($ftp)) { + $ftp->unlink($this->script_file); + } else { + @unlink(Config::$boarddir . '/' . $this->script_file); + } + + $this->deleteOldSchemaAndMaintenanceFiles($ftp ?? null); + + if (isset($ftp)) { + unset($_SESSION['ftp']); + $ftp->close(); + } + + // 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 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; + } + + foreach ($files as $k => $file) { + $this->logProgress(Lang::getTxt('log_ensuring_file_writable', ['file' => $file], file: 'Maintenance'), true); + + // Some files won't exist, try to address up front + if (!file_exists($file)) { + if (pathinfo($file, PATHINFO_EXTENSION) !== '') { + @touch($file); + } else { + mkdir($file, recursive: true); + } + } + + // 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'; + + if (!file_exists($file)) { + @touch($file); + } + } + + // NOW do the writable check... + if (Utils::makeWritable($file)) { + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + unset($files[$k]); + } else { + $this->logProgress(Lang::getTxt('log_failed', file: 'Maintenance')); + } + } + + 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 (!empty($files) && Sapi::isOS(Sapi::OS_WINDOWS)) { + Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Maintenance') . ' +
    +
  • ' . implode('
  • +
  • ', $files) . '
  • +
'; + + $this->logProgress(Lang::getTxt('error_windows_chmod', file: 'Maintenance') . "\n\t" . implode("\n\t", $files)); + + return false; + } + + // We're going to have to use... FTP! + 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']; + 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) { + $this->logProgress(Lang::getTxt('log_ensuring_file_writable_ftp', ['file' => $file], file: 'Maintenance'), true); + + 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]); + $this->logProgress(Lang::getTxt('done', file: 'Maintenance')); + } else { + $this->logProgress(Lang::getTxt('failed', file: 'Maintenance')); + } + } + + $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!'); + } + + /** + * 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. + */ + public function updateSettingsFile(array $config_vars, ?bool $keep_quotes = null, bool $rebuild = false): bool + { + if (array_keys($config_vars) !== ['maintenance_tool_progress']) { + $this->logProgress(Lang::getTxt('log_settings_file_save', ['setting_names' => Lang::sentenceList(array_keys($config_vars))], file: 'Maintenance'), true); + } + + if (!Config::updateSettingsFile($config_vars, $keep_quotes, $rebuild)) { + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => Lang::getTxt('settings_error', file: 'Maintenance')], file: 'Maintenance')); + + if (Sapi::isCLI()) { + die(); + } + + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); + + return false; + } + + if (array_keys($config_vars) !== ['maintenance_tool_progress']) { + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + } + + return true; + } + + /** + * Wrapper for Config::updateSettingsFile() with special error handling. + * + * @param array $change_array An array of info about what we're changing + * in 'setting' => 'value' format. + * @param bool $update Whether to use an UPDATE query instead of a REPLACE + * query. + * @return bool True on success, false on failure. + */ + public function updateModSettings(array $change_array, bool $update = false): bool + { + $this->logProgress(Lang::getTxt('log_modsettings_save', ['setting_names' => Lang::sentenceList(array_keys($change_array))], file: 'Maintenance'), true); + + try { + Config::updateModSettings($change_array, $update); + } catch (\Thowable $e) { + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + } + + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * Attempts to delete SMF\Maintenance\Migration, SMF\Maintenance\Cleanup, + * and SMF\Db\Schema files that will not be needed again. + * + * This should be done only when the install or upgrade process is complete. + */ + protected function deleteOldSchemaAndMaintenanceFiles(?FtpConnection $ftp): void + { + if (!isset(Config::$modSettings['smf_version'])) { + Config::reloadModSettings(); + } + + $this_ns = preg_replace('/^(\d+)\.(\d+).*/', 'v$1_$2', Config::$modSettings['smf_version'] ?? '0.0'); + + $base_dirs = [ + Config::$sourcedir . '/Maintenance/Migration', + Config::$sourcedir . '/Maintenance/Cleanup', + Config::$sourcedir . '/Db/Schema', + ]; + + foreach ($base_dirs as $base_dir) { + $dir_list = new \GlobIterator($base_dir . '/v*', \FilesystemIterator::NEW_CURRENT_AND_KEY); + + foreach ($dir_list as $dir) { + // Just in case... + if (!$dir->isDir()) { + continue; + } + + if ($dir->getBasename() < $this_ns) { + Utils::makeWritable($dir->getPathname()); + + $file_list = new \GlobIterator($dir->getPathname() . '/*', \FilesystemIterator::NEW_CURRENT_AND_KEY); + + foreach ($file_list as $file) { + if (isset($ftp)) { + $ftp->unlink(str_replace(Config::$boarddir . '/', '', $file->getPathname())); + } else { + Utils::makeWritable($file->getPathname()); + @unlink($file->getPathname()); + } + } + + if (isset($ftp)) { + $ftp->unlink(str_replace(Config::$boarddir . '/', '', $dir->getPathname())); + } else { + @rmdir($dir->getPathname()); + } + } + } + } + } +} diff --git a/Sources/Maintenance/Tools/ToolsInterface.php b/Sources/Maintenance/Tools/ToolsInterface.php new file mode 100644 index 0000000000..6588f1fa72 --- /dev/null +++ b/Sources/Maintenance/Tools/ToolsInterface.php @@ -0,0 +1,126 @@ +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; + + /** + * 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. + * + * Some tools may call this to save their progress, etc. + */ + public function preExit(): void; + + /** + * 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. + * + * No output is returned. Upon successful deletion, the browser is + * redirected to a blank file. + */ + public function deleteTool(): void; +} diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php new file mode 100644 index 0000000000..e7bedfa459 --- /dev/null +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -0,0 +1,1728 @@ + 'v3_0' means that if the current version of SMF + * is less than or equal to 3.0.99, run the v3_0 steps. + * + * This is a bit counter-intuitive, since one might think that the steps for + * upgrading to SMF 3.0 should only be run if the current version is less + * that 3.0. However, the upgrader also needs to work for upgrading between + * patch releases (e.g. 3.0.1 --> 3.0.5), so the boundary actually needs to + * be the highest version that the steps could apply to, not the lowest. + */ + public const VERSION_MAP = [ + '2.1.99' => 'v2_1', + '3.0.99' => 'v3_0', + ]; + + /** + * @var array + * + * Migration substeps to perform, listed in order. + * + * Note that additional substeps will be automatically appended to the list + * to ensure that all tables are structured correctly. + */ + 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\PostgreSqlTime::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\IdxLogPackages::class, + Migration\v2_1\IdxScheduledTasks::class, + Migration\v2_1\IdxAdminInfo::class, + Migration\v2_1\IdxBoards::class, + Migration\v2_1\IdxLogComments::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\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\PackageVersion::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, + Migration\v3_0\PermissionChanges::class, + ], + ]; + + /** + * @var array + * + * 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 + * + * Whether we can continue. + * + * When false the continue button is removed. + */ + public bool $continue = true; + + /** + * @var bool + * + * Whether we can skip the current step. + * + * If false, no skip option will be shown. + */ + 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_file = '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 $maintenance_tool_progress = []; + + /** + * @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; + + /** + * @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 + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + Maintenance::$languages = $this->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+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(QueryString::class)) { + 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); + } + } + + /** + * + */ + public function getScriptName(): string + { + return Lang::getTxt('smf_upgrade', file: 'Maintenance'); + } + + /** + * 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. The value of $this->getScriptName(). + * + * @return string The title for the page. + */ + public function getPageTitle(): string + { + 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 + { + return true; + } + + /** + * Upgrade Steps + * + * @return \SMF\Maintenance\Step[] + */ + public function getSteps(): array + { + return [ + new Step( + id: 1, + name: Lang::getTxt('upgrade_step_login', file: 'Maintenance'), + function: 'welcomeLogin', + template: 'welcomeLogin', + progress: 2, + ), + new Step( + id: 2, + name: Lang::getTxt('upgrade_step_options', file: 'Maintenance'), + function: 'upgradeOptions', + template: 'upgradeOptions', + progress: 3, + ), + new Step( + id: 3, + name: Lang::getTxt('upgrade_step_backup', file: 'Maintenance'), + function: 'backupDatabase', + template: 'backupDatabase', + progress: 10, + ), + new Step( + id: 4, + name: Lang::getTxt('upgrade_step_migration', file: 'Maintenance'), + function: 'migrations', + template: 'migrations', + progress: 45, + ), + new Utf8ConverterStep( + // Note: Utf8ConverterStep does not take a function argument. + id: 5, + name: Lang::getTxt('upgrade_step_convertutf8', file: 'Maintenance'), + template: 'convertUtf8', + progress: 30, + ), + new Step( + id: 6, + name: Lang::getTxt('upgrade_step_cleanup', file: 'Maintenance'), + function: 'cleanup', + template: 'cleanup', + progress: 10, + ), + new Step( + id: 7, + name: Lang::getTxt('upgrade_step_finalize', file: 'Maintenance'), + function: 'finalize', + template: 'finalize', + progress: 0, + ), + ]; + } + + /** + * Gets the title for the step we are performing + * + * @return string + */ + public function getStepTitle(): string + { + return $this->getStep()->getName(); + } + + /** + * Welcome action. + * + * @return bool True if we can continue, false otherwise. + */ + 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; + } + + // 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; + } + + // 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; + } + + // Check for some key files. + $check = ( + @file_exists(Maintenance::$theme_dir . '/index.template.php') + && @file_exists(Config::$sourcedir . '/Forum.php') + && @file_exists(Config::$sourcedir . '/QueryString.php') + && @file_exists(Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php') + ); + + // Need legacy scripts? + foreach (self::VERSION_MAP as $search => $ns) { + if (version_compare($this->start_smf_version, $search, '>')) { + continue; + } + + foreach (self::MIGRATIONS[$ns] as $class) { + $check &= class_exists($class); + } + + foreach (self::CLEANUPS[$ns] as $class) { + $check &= class_exists($class); + } + } + + 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; + } + + 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()]); + $this->logProgress(Maintenance::$fatal_error); + + 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]); + $this->logProgress(Maintenance::$fatal_error); + + 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::getTxt('error_upgrade_old_files', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + 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'; + + $writable_files = [$custom_av_dir]; + $this->makeFilesWritable($writable_files); + + // 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; + } + + if ($need_settings_update) { + $this->updateModSettings(['custom_avatar_dir' => $custom_av_dir]); + $this->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::getTxt('error_cache_not_found', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + $writable_files = [$cache_dir_temp . '/db_last_error.php']; + $this->makeFilesWritable($writable_files); + + 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; + } + + // 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) { + $this->updateSettingsFile(['language' => $new_locale]); + } + + if (empty(Config::$languagesdir)) { + $this->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::getTxt('error_agreement_not_writable', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); + + return false; + } + + // 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. + $supported_streams = stream_get_wrappers(); + + 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()) { + 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::getTxt('token_verify_fail', file: 'Maintenance'); + 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'] ?? '0.0.dev.0')), '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; + } + + 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'); + + $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::getTxt('mtitle', file: 'Maintenance'); + $file_settings['mmessage'] = Lang::getTxt('mmessage', file: 'Maintenance'); + } + } + + // 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. + $this->updateModSettings($db_settings); + + // Update Settings.php with the new settings, and rebuild if they selected that option. + $this->updateSettingsFile($file_settings, false, !empty($_POST['migrateSettings'])); + + // Empty our error log. + if (!empty($_POST['empty_error'])) { + Db::$db->query( + 'TRUNCATE {db_prefix}log_errors', + [], + identifier: 'truncate_table', + ); + } + + // 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['cur_table_name'] = $table_names[Maintenance::getCurrentSubStep()]; + Maintenance::$context['continue'] = true; + + // We are set up for backing up. + if (!Sapi::isCLI() && !Maintenance::isJson()) { + return false; + } + + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + + // Back up each table! + $substeps = []; + + 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], + ); + } + + $this->performSubsteps($substeps); + + // 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; + } + + foreach (self::MIGRATIONS[$ns] as $class) { + $substeps[] = new $class(); + } + + // Ensure the tables are structured correctly. + foreach (Table::getAll($ns) as $table) { + $substeps[] = new GenericSubStep( + name: Lang::getTxt('upgrade_normalizing_table', ['table' => Config::$db_prefix . $table->name], file: 'Maintenance'), + exec: [$table, 'normalize'], + ); + } + } + + $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; + } + + foreach (self::CLEANUPS[$ns] as $class) { + $substeps[] = new $class(); + } + } + + $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 + { + 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. + $this->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); + + // 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! + $this->logProgress(Lang::getTxt('log_upgrade_complete', file: 'Maintenance')); + Maintenance::$overall_percent = 100; + Maintenance::setCurrentSubStep(0); + + // Wipe this out... + $this->user = []; + + 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->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', + ); + + Maintenance::$context['log_contents'] = file_get_contents($this->log_file); + } + } + + $this->finalizeLog(); + + return Sapi::isCLI(); + } + + /** + * Write out our current information to our settings file to track the + * upgrade progress. + */ + public function preExit(): void + { + $this->saveProgress(); + } + + /** + * 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 successful, false otherwise. + */ + public function doBackupTable($table): bool + { + return Db::$db->backup_table($table, 'backup_' . $table); + } + + /****************** + * 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. + $this->updateSettingsFile(['db_type' => 'mysql']); + } + + try { + Maintenance::loadDatabase(); + Maintenance::loadModSettings(); + Maintenance::setThemeData(); + } catch (\Throwable $e) { + die($e->getMessage()); + } + + + $this->getProgress(); + + // 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 getProgress(): void + { + try { + $data = isset(Config::$custom['maintenance_tool_progress']) ? Utils::jsonDecode(base64_decode(Config::$custom['maintenance_tool_progress']), 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 saveProgress(): 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 = ''; + } + + return $this->updateSettingsFile(['maintenance_tool_progress' => $data]); + } + + /** + * 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; + } + } + + /** + * Performs a series of substeps. + * + * @param array $substeps All substep objects 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'] = $substeps[Maintenance::getCurrentSubStep()]->name ?? ''; + + 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; + } + + 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 = $substeps[Maintenance::getCurrentSubStep()]; + + $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' => $substeps[Maintenance::getCurrentSubStep()]->name ?? '', + 'skipped' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + ], + ]); + + continue; + } + } catch (\Throwable $e) { + $this->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; + } + + try { + 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, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + 'msg' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ]); + + return; + } + + $this->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([ + '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/Sources/Maintenance/Tools/index.php b/Sources/Maintenance/Tools/index.php new file mode 100644 index 0000000000..cc9dd08570 --- /dev/null +++ b/Sources/Maintenance/Tools/index.php @@ -0,0 +1,8 @@ + '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('log_table_convertutf8', ['table' => $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::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([ + 'name' => '', + 'skipped' => true, + 'substep' => 0, + 'start' => 0, + 'total' => 0, + 'debug' => [ + 'call' => '', + ], + ]); + } + + return true; + } + + while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { + $substep = $substeps[Maintenance::getCurrentSubStep()]; + + Maintenance::$tool->logProgress(' +++ ' . $substep->name, true); + + try { + if (!$substep->isCandidate()) { + Maintenance::setCurrentSubStep(); + + Maintenance::$tool->logProgress(Lang::getTxt('log_skipped', file: 'Maintenance')); + + 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, + ], + ]); + + continue; + } + } 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([ + 'name' => $substep->name, + 'next' => $substeps[Maintenance::getCurrentSubStep()]->name, + 'completed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + ], + ]); + } + } + + 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/Maintenance/index.php b/Sources/Maintenance/index.php new file mode 100644 index 0000000000..cc9dd08570 --- /dev/null +++ b/Sources/Maintenance/index.php @@ -0,0 +1,8 @@ + true]); + Utils::$context['install_finished'] = false; // @todo Make a log of any errors that occurred and output them? @@ -1416,6 +1419,9 @@ public function install(): void // Does Config::$backward_compatibility need to be updated? $this->updateBackwardCompatibility(); + + // Resume background tasks. + Config::updateSettingsFile(['package_installing' => null]); } /** 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; } } diff --git a/Sources/Security.php b/Sources/Security.php index e585e41ed2..b27cb311d1 100644 --- a/Sources/Security.php +++ b/Sources/Security.php @@ -494,12 +494,9 @@ public static function secureDirectory(string|array $paths, bool $attachments = foreach ($paths as $path) { if (!is_writable($path)) { $errors[] = 'path_not_writable'; - continue; } - $directory_name = basename($path); - // First, create the .htaccess file. $contents = << @@ -519,28 +516,16 @@ public static function secureDirectory(string|array $paths, bool $attachments = END; } - if (file_exists($path . '/.htaccess')) { + if (!file_exists($path . '/.htaccess')) { + if (@file_put_contents($path . '/.htaccess', $contents) !== strlen($contents)) { + $errors[] = 'htaccess_cannot_create_file'; + } + } elseif (file_get_contents($path . '/.htaccess') !== $contents) { $errors[] = 'htaccess_exists'; - continue; } - $fh = @fopen($path . '/.htaccess', 'w'); - - if ($fh) { - fwrite($fh, $contents); - fclose($fh); - } else { - $errors[] = 'htaccess_cannot_create_file'; - } - // Next, the index.php file - if (file_exists($path . '/index.php')) { - $errors[] = 'index-php_exists'; - - continue; - } - $contents = <<obExit(); + } + Security::frameOptionsHeader(); // Before we go any further, if this is not a CLI request, we need to do some checking. diff --git a/Sources/Tasks/ExportProfileData.php b/Sources/Tasks/ExportProfileData.php index 9549d6a4da..4a0e2452fd 100644 --- a/Sources/Tasks/ExportProfileData.php +++ b/Sources/Tasks/ExportProfileData.php @@ -921,7 +921,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 new file mode 100644 index 0000000000..873c222edc --- /dev/null +++ b/Sources/Tasks/Utf8EntityDecode.php @@ -0,0 +1,389 @@ +_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, $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 $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 $where, array $string_columns): void + { + $params = [ + 'table' => $this->_details['table'], + ]; + + $set = []; + + foreach ($row as $col => $value) { + if (isset($where[$col])) { + $params[$col] = $value; + } + + if (in_array($col, $string_columns) && is_string($value)) { + $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( + 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: [], + ); + } +} diff --git a/Themes/default/InstallTemplate.php b/Themes/default/InstallTemplate.php new file mode 100644 index 0000000000..7bad5c7408 --- /dev/null +++ b/Themes/default/InstallTemplate.php @@ -0,0 +1,508 @@ +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['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['chmod_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 + { + MaintenanceTemplate::warningsAndErrors(); + + echo ' +

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

'; + + MaintenanceTemplate::showLog(); + + // 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['can_delete_script']) { + 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..f8288f3bfd --- /dev/null +++ b/Themes/default/MaintenanceTemplate.php @@ -0,0 +1,514 @@ + + + + + + ', 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; + } + + /** + * 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; + + 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'), '

+ +
'; + + echo ' +

', + 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 ' + '; + + // 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..69f780b62a --- /dev/null +++ b/Themes/default/UpgradeTemplate.php @@ -0,0 +1,669 @@ +'; + } + + /** + * 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; + + self::showStepWithSubSteps('backup', 'backup_done'); + } + + /** + * 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]), '

'; + + // 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'] . '

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

+ +

+ '; + } + + echo ' +

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

'; + } + + /************************* + * Internal static methods + *************************/ + + /** + * 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('error_files_not_writable', 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 new file mode 100644 index 0000000000..21dacdf6e1 --- /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.adminlogin dt, dl.settings.adminlogin 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; + } +} 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 * */ 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": { diff --git a/other/Settings.php b/other/Settings.php index 398c6b4e0e..a99eaf35dd 100644 --- a/other/Settings.php +++ b/other/Settings.php @@ -227,17 +227,7 @@ */ $backward_compatibility = 0; -if (file_exists(__DIR__ . '/install.php')) -{ - $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; - - if (basename($_SERVER['PHP_SELF']) != 'install.php') - { - header('location: http' . ($secure ? 's' : '') . '://' . (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']) . (strtr(dirname($_SERVER['PHP_SELF']), '\\', '/') == '/' ? '' : strtr(dirname($_SERVER['PHP_SELF']), '\\', '/')) . '/install.php'); - exit; - } +if (file_exists(__DIR__ . '/install.php') && basename($_SERVER['PHP_SELF']) != 'install.php') { + header('location: install.php'); + exit; } diff --git a/other/Settings_bak.php b/other/Settings_bak.php index 18199ace13..9e19b5aae5 100644 --- a/other/Settings_bak.php +++ b/other/Settings_bak.php @@ -227,17 +227,7 @@ */ $backward_compatibility = 0; -if (file_exists(__DIR__ . '/install.php')) -{ - $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; - - if (basename($_SERVER['PHP_SELF']) != 'install.php') - { - header('location: http' . ($secure ? 's' : '') . '://' . (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']) . (strtr(dirname($_SERVER['PHP_SELF']), '\\', '/') == '/' ? '' : strtr(dirname($_SERVER['PHP_SELF']), '\\', '/')) . '/install.php'); - exit; - } +if (file_exists(__DIR__ . '/install.php') && basename($_SERVER['PHP_SELF']) != 'install.php') { + header('location: install.php'); + exit; } diff --git a/other/install.php b/other/install.php index 2228ac8713..d57de9537b 100644 --- a/other/install.php +++ b/other/install.php @@ -11,2540 +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 "Install" 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')) { - 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'] . '/Install.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+Install'); -} - -// 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); diff --git a/other/install_3-0_MySQL.sql b/other/install_3-0_MySQL.sql deleted file mode 100644 index 2871acce00..0000000000 --- a/other/install_3-0_MySQL.sql +++ /dev/null @@ -1,2117 +0,0 @@ -#### ATTENTION: You do not need to run or use this file! The install.php script does everything for you! - -# -# Table structure for table `admin_info_files` -# - -CREATE TABLE {$db_prefix}admin_info_files ( - id_file TINYINT UNSIGNED AUTO_INCREMENT, - filename VARCHAR(255) NOT NULL DEFAULT '', - path VARCHAR(255) NOT NULL DEFAULT '', - parameters VARCHAR(255) NOT NULL DEFAULT '', - data TEXT NOT NULL, - filetype VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_file), - INDEX idx_filename (filename(30)) -) ENGINE={$engine}; - -# -# Table structure for table `approval_queue` -# - -CREATE TABLE {$db_prefix}approval_queue ( - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_attach INT UNSIGNED NOT NULL DEFAULT '0', - id_event SMALLINT UNSIGNED NOT NULL DEFAULT '0' -) ENGINE={$engine}; - -# -# Table structure for table `attachments` -# - -CREATE TABLE {$db_prefix}attachments ( - id_attach INT UNSIGNED AUTO_INCREMENT, - id_thumb INT UNSIGNED NOT NULL DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_folder TINYINT NOT NULL DEFAULT '1', - attachment_type TINYINT UNSIGNED NOT NULL DEFAULT '0', - filename VARCHAR(255) NOT NULL DEFAULT '', - file_hash VARCHAR(40) NOT NULL DEFAULT '', - fileext VARCHAR(8) NOT NULL DEFAULT '', - size INT UNSIGNED NOT NULL DEFAULT '0', - downloads MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - width MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - height MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - mime_type VARCHAR(128) NOT NULL DEFAULT '', - approved TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_attach), - UNIQUE idx_id_member (id_member, id_attach), - INDEX idx_id_msg (id_msg), - INDEX idx_attachment_type (attachment_type), - INDEX idx_id_thumb (id_thumb) -) ENGINE={$engine}; - -# -# Table structure for table `background_tasks` -# - -CREATE TABLE {$db_prefix}background_tasks ( - id_task INT UNSIGNED AUTO_INCREMENT, - task_file VARCHAR(255) NOT NULL DEFAULT '', - task_class VARCHAR(255) NOT NULL DEFAULT '', - task_data MEDIUMTEXT NOT NULL, - claimed_time INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_task) -) ENGINE={$engine}; - -# -# Table structure for table `ban_groups` -# - -CREATE TABLE {$db_prefix}ban_groups ( - id_ban_group MEDIUMINT UNSIGNED AUTO_INCREMENT, - name VARCHAR(20) NOT NULL DEFAULT '', - ban_time INT UNSIGNED NOT NULL DEFAULT '0', - expire_time INT UNSIGNED, - cannot_access TINYINT UNSIGNED NOT NULL DEFAULT '0', - cannot_register TINYINT UNSIGNED NOT NULL DEFAULT '0', - cannot_post TINYINT UNSIGNED NOT NULL DEFAULT '0', - cannot_login TINYINT UNSIGNED NOT NULL DEFAULT '0', - reason VARCHAR(255) NOT NULL DEFAULT '', - notes TEXT NOT NULL, - PRIMARY KEY (id_ban_group) -) ENGINE={$engine}; - -# -# Table structure for table `ban_items` -# - -CREATE TABLE {$db_prefix}ban_items ( - id_ban MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_ban_group SMALLINT UNSIGNED NOT NULL DEFAULT '0', - ip_low VARBINARY(16), - ip_high VARBINARY(16), - hostname VARCHAR(255) NOT NULL DEFAULT '', - email_address VARCHAR(255) NOT NULL DEFAULT '', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - hits MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_ban), - INDEX idx_id_ban_group (id_ban_group), - INDEX idx_id_ban_ip (ip_low,ip_high) -) ENGINE={$engine}; - -# -# Table structure for table `board_permissions` -# - -CREATE TABLE {$db_prefix}board_permissions ( - id_group SMALLINT DEFAULT '0', - id_profile SMALLINT UNSIGNED DEFAULT '0', - permission VARCHAR(30) DEFAULT '', - add_deny TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_group, id_profile, permission) -) ENGINE={$engine}; - -# -# Table structure for table `boards` -# - -CREATE TABLE {$db_prefix}boards ( - id_board SMALLINT UNSIGNED AUTO_INCREMENT, - id_cat TINYINT UNSIGNED NOT NULL DEFAULT '0', - child_level TINYINT UNSIGNED NOT NULL DEFAULT '0', - id_parent SMALLINT UNSIGNED NOT NULL DEFAULT '0', - board_order SMALLINT NOT NULL DEFAULT '0', - id_last_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_msg_updated INT UNSIGNED NOT NULL DEFAULT '0', - member_groups VARCHAR(255) NOT NULL DEFAULT '-1,0', - id_profile SMALLINT UNSIGNED NOT NULL DEFAULT '1', - name VARCHAR(255) NOT NULL DEFAULT '', - description TEXT NOT NULL, - num_topics MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - num_posts MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - count_posts TINYINT NOT NULL DEFAULT '0', - id_theme TINYINT UNSIGNED NOT NULL DEFAULT '0', - override_theme TINYINT UNSIGNED NOT NULL DEFAULT '0', - unapproved_posts SMALLINT NOT NULL DEFAULT '0', - unapproved_topics SMALLINT NOT NULL DEFAULT '0', - redirect VARCHAR(255) NOT NULL DEFAULT '', - deny_member_groups VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_board), - UNIQUE idx_categories (id_cat, id_board), - INDEX idx_id_parent (id_parent), - INDEX idx_id_msg_updated (id_msg_updated), - INDEX idx_member_groups (member_groups(48)) -) ENGINE={$engine}; - -# -# Table structure for table `board_permissions_view` -# - -CREATE TABLE {$db_prefix}board_permissions_view -( - id_group SMALLINT NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL, - deny smallint NOT NULL, - PRIMARY KEY (id_group, id_board, deny) -) ENGINE={$engine}; - -# -# Table structure for table `calendar` -# - -CREATE TABLE {$db_prefix}calendar ( - id_event SMALLINT UNSIGNED AUTO_INCREMENT, - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - title VARCHAR(255) NOT NULL DEFAULT '', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - start_date DATE NOT NULL DEFAULT '1004-01-01', - end_date DATE NOT NULL DEFAULT '1004-01-01', - start_time TIME, - end_time TIME, - timezone VARCHAR(80), - location VARCHAR(255) NOT NULL DEFAULT '', - duration VARCHAR(32) NOT NULL DEFAULT '', - rrule VARCHAR(1024) NOT NULL DEFAULT 'FREQ=YEARLY;COUNT=1', - rdates TEXT NOT NULL, - exdates TEXT NOT NULL, - adjustments JSON DEFAULT NULL, - sequence SMALLINT UNSIGNED NOT NULL DEFAULT '0', - uid VARCHAR(255) NOT NULL DEFAULT '', - type TINYINT UNSIGNED NOT NULL DEFAULT '0', - enabled TINYINT UNSIGNED NOT NULL DEFAULT '1', - PRIMARY KEY (id_event), - INDEX idx_start_date (start_date), - INDEX idx_end_date (end_date), - INDEX idx_topic (id_topic, id_member) -) ENGINE={$engine}; - -# -# Table structure for table `categories` -# - -CREATE TABLE {$db_prefix}categories ( - id_cat TINYINT UNSIGNED AUTO_INCREMENT, - cat_order TINYINT NOT NULL DEFAULT '0', - name VARCHAR(255) NOT NULL DEFAULT '', - description TEXT NOT NULL, - can_collapse TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_cat) -) ENGINE={$engine}; - -# -# Table structure for table `custom_fields` -# - -CREATE TABLE {$db_prefix}custom_fields ( - id_field SMALLINT AUTO_INCREMENT, - col_name VARCHAR(12) NOT NULL DEFAULT '', - field_name VARCHAR(40) NOT NULL DEFAULT '', - field_desc VARCHAR(255) NOT NULL DEFAULT '', - field_type VARCHAR(8) NOT NULL DEFAULT 'text', - field_length SMALLINT NOT NULL DEFAULT '255', - field_options TEXT NOT NULL, - field_order SMALLINT NOT NULL DEFAULT '0', - mask VARCHAR(255) NOT NULL DEFAULT '', - show_reg TINYINT NOT NULL DEFAULT '0', - show_display TINYINT NOT NULL DEFAULT '0', - show_mlist SMALLINT NOT NULL DEFAULT '0', - show_profile VARCHAR(20) NOT NULL DEFAULT 'forumprofile', - private TINYINT NOT NULL DEFAULT '0', - active TINYINT NOT NULL DEFAULT '1', - bbc TINYINT NOT NULL DEFAULT '0', - can_search TINYINT NOT NULL DEFAULT '0', - default_value VARCHAR(255) NOT NULL DEFAULT '', - enclose TEXT NOT NULL, - placement TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_field), - UNIQUE idx_col_name (col_name) -) ENGINE={$engine}; - -# -# Table structure for table `group_moderators` -# - -CREATE TABLE {$db_prefix}group_moderators ( - id_group SMALLINT UNSIGNED DEFAULT '0', - id_member MEDIUMINT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_group, id_member) -) ENGINE={$engine}; - -# -# Table structure for table `log_actions` -# - -CREATE TABLE {$db_prefix}log_actions ( - id_action INT UNSIGNED AUTO_INCREMENT, - id_log TINYINT UNSIGNED NOT NULL DEFAULT '1', - log_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - ip VARBINARY(16), - action VARCHAR(30) NOT NULL DEFAULT '', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - extra TEXT NOT NULL, - PRIMARY KEY (id_action), - INDEX idx_id_log (id_log), - INDEX idx_log_time (log_time), - INDEX idx_id_member (id_member), - INDEX idx_id_board (id_board), - INDEX idx_id_msg (id_msg), - INDEX idx_id_topic_id_log (id_topic, id_log) -) ENGINE={$engine}; - -# -# Table structure for table `log_activity` -# - -CREATE TABLE {$db_prefix}log_activity ( - date DATE NOT NULL, - hits MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - topics SMALLINT UNSIGNED NOT NULL DEFAULT '0', - posts SMALLINT UNSIGNED NOT NULL DEFAULT '0', - registers SMALLINT UNSIGNED NOT NULL DEFAULT '0', - most_on SMALLINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (date) -) ENGINE={$engine}; - -# -# Table structure for table `log_banned` -# - -CREATE TABLE {$db_prefix}log_banned ( - id_ban_log MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - ip VARBINARY(16), - email VARCHAR(255) NOT NULL DEFAULT '', - log_time INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_ban_log), - INDEX idx_log_time (log_time) -) ENGINE={$engine}; - -# -# Table structure for table `log_boards` -# - -CREATE TABLE {$db_prefix}log_boards ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - id_board SMALLINT UNSIGNED DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_board) -) ENGINE={$engine}; - -# -# Table structure for table `log_comments` -# - -CREATE TABLE {$db_prefix}log_comments ( - id_comment MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - member_name VARCHAR(80) NOT NULL DEFAULT '', - comment_type VARCHAR(8) NOT NULL DEFAULT 'warning', - id_recipient MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - recipient_name VARCHAR(255) NOT NULL DEFAULT '', - log_time INT NOT NULL DEFAULT '0', - id_notice MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - counter TINYINT NOT NULL DEFAULT '0', - body TEXT NOT NULL, - PRIMARY KEY (id_comment), - INDEX idx_id_recipient (id_recipient), - INDEX idx_log_time (log_time), - INDEX idx_comment_type (comment_type(8)) -) ENGINE={$engine}; - -# -# Table structure for table `log_digest` -# - -CREATE TABLE {$db_prefix}log_digest ( - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - note_type VARCHAR(10) NOT NULL DEFAULT 'post', - daily TINYINT UNSIGNED NOT NULL DEFAULT '0', - exclude MEDIUMINT UNSIGNED NOT NULL DEFAULT '0' -) ENGINE={$engine}; - -# -# Table structure for table `log_errors` -# - -CREATE TABLE {$db_prefix}log_errors ( - id_error MEDIUMINT UNSIGNED AUTO_INCREMENT, - log_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - ip VARBINARY(16), - url TEXT NOT NULL, - message TEXT NOT NULL, - session VARCHAR(128) NOT NULL DEFAULT '', - error_type CHAR(15) NOT NULL DEFAULT 'general', - file VARCHAR(255) NOT NULL DEFAULT '', - line MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - backtrace VARCHAR(10000) NOT NULL DEFAULT '', - PRIMARY KEY (id_error), - INDEX idx_log_time (log_time), - INDEX idx_id_member (id_member), - INDEX idx_ip (ip) -) ENGINE={$engine}; - -# -# Table structure for table `log_floodcontrol` -# - -CREATE TABLE {$db_prefix}log_floodcontrol ( - ip VARBINARY(16), - log_time INT UNSIGNED NOT NULL DEFAULT '0', - log_type VARCHAR(30) DEFAULT 'post', - PRIMARY KEY (ip, log_type) -) ENGINE={$memory}; - -# -# Table structure for table `log_group_requests` -# - -CREATE TABLE {$db_prefix}log_group_requests ( - id_request MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_group SMALLINT UNSIGNED NOT NULL DEFAULT '0', - time_applied INT UNSIGNED NOT NULL DEFAULT '0', - reason TEXT NOT NULL, - status TINYINT UNSIGNED NOT NULL DEFAULT '0', - id_member_acted MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - member_name_acted VARCHAR(255) NOT NULL DEFAULT '', - time_acted INT UNSIGNED NOT NULL DEFAULT '0', - act_reason TEXT NOT NULL, - PRIMARY KEY (id_request), - INDEX idx_id_member (id_member, id_group) -) ENGINE={$engine}; - -# -# Table structure for table `log_mark_read` -# - -CREATE TABLE {$db_prefix}log_mark_read ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - id_board SMALLINT UNSIGNED DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_board) -) ENGINE={$engine}; - -# -# Table structure for table `log_member_notices` -# - -CREATE TABLE {$db_prefix}log_member_notices ( - id_notice MEDIUMINT UNSIGNED AUTO_INCREMENT, - subject VARCHAR(255) NOT NULL DEFAULT '', - body TEXT NOT NULL, - PRIMARY KEY (id_notice) -) ENGINE={$engine}; - -# -# Table structure for table `log_notify` -# - -CREATE TABLE {$db_prefix}log_notify ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - id_topic MEDIUMINT UNSIGNED DEFAULT '0', - id_board SMALLINT UNSIGNED DEFAULT '0', - sent TINYINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_topic, id_board), - INDEX idx_id_topic (id_topic, id_member), - INDEX id_board (id_board) -) ENGINE={$engine}; - -# -# Table structure for table `log_online` -# - -CREATE TABLE {$db_prefix}log_online ( - session VARCHAR(128) DEFAULT '', - log_time INT NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_spider SMALLINT UNSIGNED NOT NULL DEFAULT '0', - ip VARBINARY(16), - url VARCHAR(2048) NOT NULL DEFAULT '', - PRIMARY KEY (session), - INDEX idx_log_time (log_time), - INDEX idx_id_member (id_member) -) ENGINE={$memory}; - -# -# Table structure for table `log_packages` -# - -CREATE TABLE {$db_prefix}log_packages ( - id_install INT AUTO_INCREMENT, - filename VARCHAR(255) NOT NULL DEFAULT '', - package_id VARCHAR(255) NOT NULL DEFAULT '', - name VARCHAR(255) NOT NULL DEFAULT '', - version VARCHAR(255) NOT NULL DEFAULT '', - id_member_installed MEDIUMINT NOT NULL DEFAULT '0', - member_installed VARCHAR(255) NOT NULL DEFAULT '', - time_installed INT NOT NULL DEFAULT '0', - id_member_removed MEDIUMINT NOT NULL DEFAULT '0', - member_removed VARCHAR(255) NOT NULL DEFAULT '', - time_removed INT NOT NULL DEFAULT '0', - install_state TINYINT NOT NULL DEFAULT '1', - failed_steps TEXT NOT NULL, - themes_installed VARCHAR(255) NOT NULL DEFAULT '', - db_changes TEXT NOT NULL, - credits TEXT NOT NULL, - sha256_hash TEXT, - smf_version VARCHAR(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_install), - INDEX idx_filename (filename(15)) -) ENGINE={$engine}; - -# -# Table structure for table `log_polls` -# - -CREATE TABLE {$db_prefix}log_polls ( - id_poll MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_choice TINYINT UNSIGNED NOT NULL DEFAULT '0', - INDEX idx_id_poll (id_poll, id_member, id_choice) -) ENGINE={$engine}; - -# -# Table structure for table `log_reported` -# - -CREATE TABLE {$db_prefix}log_reported ( - id_report MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - membername VARCHAR(255) NOT NULL DEFAULT '', - subject VARCHAR(255) NOT NULL DEFAULT '', - body MEDIUMTEXT NOT NULL, - time_started INT NOT NULL DEFAULT '0', - time_updated INT NOT NULL DEFAULT '0', - num_reports MEDIUMINT NOT NULL DEFAULT '0', - closed TINYINT NOT NULL DEFAULT '0', - ignore_all TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_report), - INDEX idx_id_member (id_member), - INDEX idx_id_topic (id_topic), - INDEX idx_closed (closed), - INDEX idx_time_started (time_started), - INDEX idx_id_msg (id_msg) -) ENGINE={$engine}; - -# -# Table structure for table `log_reported_comments` -# - -CREATE TABLE {$db_prefix}log_reported_comments ( - id_comment MEDIUMINT UNSIGNED AUTO_INCREMENT, - id_report MEDIUMINT NOT NULL DEFAULT '0', - id_member MEDIUMINT NOT NULL, - membername VARCHAR(255) NOT NULL DEFAULT '', - member_ip VARBINARY(16), - comment VARCHAR(255) NOT NULL DEFAULT '', - time_sent INT NOT NULL, - PRIMARY KEY (id_comment), - INDEX idx_id_report (id_report), - INDEX idx_id_member (id_member), - INDEX idx_time_sent (time_sent) -) ENGINE={$engine}; - -# -# Table structure for table `log_scheduled_tasks` -# - -CREATE TABLE {$db_prefix}log_scheduled_tasks ( - id_log MEDIUMINT AUTO_INCREMENT, - id_task SMALLINT NOT NULL DEFAULT '0', - time_run INT NOT NULL DEFAULT '0', - time_taken float NOT NULL DEFAULT '0', - PRIMARY KEY (id_log) -) ENGINE={$engine}; - -# -# Table structure for table `log_search_messages` -# - -CREATE TABLE {$db_prefix}log_search_messages ( - id_search TINYINT UNSIGNED DEFAULT '0', - id_msg INT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_search, id_msg) -) ENGINE={$engine}; - -# -# Table structure for table `log_search_results` -# - -CREATE TABLE {$db_prefix}log_search_results ( - id_search TINYINT UNSIGNED DEFAULT '0', - id_topic MEDIUMINT UNSIGNED DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - relevance SMALLINT UNSIGNED NOT NULL DEFAULT '0', - num_matches SMALLINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_search, id_topic, id_msg) -) ENGINE={$engine}; - -# -# Table structure for table `log_search_subjects` -# - -CREATE TABLE {$db_prefix}log_search_subjects ( - word VARCHAR(20) DEFAULT '', - id_topic MEDIUMINT UNSIGNED DEFAULT '0', - PRIMARY KEY (word, id_topic), - INDEX idx_id_topic (id_topic) -) ENGINE={$engine}; - -# -# Table structure for table `log_search_topics` -# - -CREATE TABLE {$db_prefix}log_search_topics ( - id_search TINYINT UNSIGNED DEFAULT '0', - id_topic MEDIUMINT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_search, id_topic) -) ENGINE={$engine}; - -# -# Table structure for table `log_spider_hits` -# - -CREATE TABLE {$db_prefix}log_spider_hits ( - id_hit INT UNSIGNED AUTO_INCREMENT, - id_spider SMALLINT UNSIGNED NOT NULL DEFAULT '0', - log_time INT UNSIGNED NOT NULL DEFAULT '0', - url VARCHAR(1024) NOT NULL DEFAULT '', - processed TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_hit), - INDEX idx_id_spider(id_spider), - INDEX idx_log_time(log_time), - INDEX idx_processed (processed) -) ENGINE={$engine}; - -# -# Table structure for table `log_spider_stats` -# - -CREATE TABLE {$db_prefix}log_spider_stats ( - id_spider SMALLINT UNSIGNED DEFAULT '0', - page_hits INT NOT NULL DEFAULT '0', - last_seen INT UNSIGNED NOT NULL DEFAULT '0', - stat_date DATE DEFAULT '1004-01-01', - PRIMARY KEY (stat_date, id_spider) -) ENGINE={$engine}; - -# -# Table structure for table `log_subscribed` -# - -CREATE TABLE {$db_prefix}log_subscribed ( - id_sublog INT UNSIGNED AUTO_INCREMENT, - id_subscribe MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_member INT NOT NULL DEFAULT '0', - old_id_group SMALLINT NOT NULL DEFAULT '0', - start_time INT NOT NULL DEFAULT '0', - end_time INT NOT NULL DEFAULT '0', - status TINYINT NOT NULL DEFAULT '0', - payments_pending TINYINT NOT NULL DEFAULT '0', - pending_details TEXT NOT NULL, - reminder_sent TINYINT NOT NULL DEFAULT '0', - vendor_ref VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_sublog), - UNIQUE KEY id_subscribe (id_subscribe, id_member), - INDEX idx_end_time (end_time), - INDEX idx_reminder_sent (reminder_sent), - INDEX idx_payments_pending (payments_pending), - INDEX idx_status (status), - INDEX idx_id_member (id_member) -) ENGINE={$engine}; - -# -# Table structure for table `log_topics` -# - -CREATE TABLE {$db_prefix}log_topics ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - id_topic MEDIUMINT UNSIGNED DEFAULT '0', - id_msg INT UNSIGNED NOT NULL DEFAULT '0', - unwatched TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_topic), - INDEX idx_id_topic (id_topic) -) ENGINE={$engine}; - -# -# Table structure for table `mail_queue` -# - -CREATE TABLE {$db_prefix}mail_queue ( - id_mail INT UNSIGNED AUTO_INCREMENT, - time_sent INT NOT NULL DEFAULT '0', - recipient VARCHAR(255) NOT NULL DEFAULT '', - body MEDIUMTEXT NOT NULL, - subject VARCHAR(255) NOT NULL DEFAULT '', - headers TEXT NOT NULL, - send_html TINYINT NOT NULL DEFAULT '0', - priority TINYINT NOT NULL DEFAULT '1', - private TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_mail), - INDEX idx_time_sent (time_sent), - INDEX idx_mail_priority (priority, id_mail) -) ENGINE={$engine}; - -# -# Table structure for table `membergroups` -# - -CREATE TABLE {$db_prefix}membergroups ( - id_group SMALLINT UNSIGNED AUTO_INCREMENT, - group_name VARCHAR(80) NOT NULL DEFAULT '', - description TEXT NOT NULL, - online_color VARCHAR(20) NOT NULL DEFAULT '', - min_posts MEDIUMINT NOT NULL DEFAULT '-1', - max_messages SMALLINT UNSIGNED NOT NULL DEFAULT '0', - icons VARCHAR(255) NOT NULL DEFAULT '', - group_type TINYINT NOT NULL DEFAULT '0', - hidden TINYINT NOT NULL DEFAULT '0', - id_parent SMALLINT NOT NULL DEFAULT '-2', - tfa_required TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_group), - INDEX idx_min_posts (min_posts) -) ENGINE={$engine}; - -# -# Table structure for table `members` -# - -CREATE TABLE {$db_prefix}members ( - id_member MEDIUMINT UNSIGNED AUTO_INCREMENT, - member_name VARCHAR(80) NOT NULL DEFAULT '', - date_registered INT UNSIGNED NOT NULL DEFAULT '0', - posts MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_group SMALLINT UNSIGNED NOT NULL DEFAULT '0', - lngfile VARCHAR(255) NOT NULL DEFAULT '', - last_login INT UNSIGNED NOT NULL DEFAULT '0', - real_name VARCHAR(255) NOT NULL DEFAULT '', - instant_messages SMALLINT NOT NULL DEFAULT 0, - unread_messages SMALLINT NOT NULL DEFAULT 0, - new_pm TINYINT UNSIGNED NOT NULL DEFAULT '0', - alerts INT UNSIGNED NOT NULL DEFAULT '0', - buddy_list TEXT NOT NULL, - pm_ignore_list TEXT NULL, - pm_prefs MEDIUMINT NOT NULL DEFAULT '0', - mod_prefs VARCHAR(20) NOT NULL DEFAULT '', - passwd VARCHAR(64) NOT NULL DEFAULT '', - email_address VARCHAR(255) NOT NULL DEFAULT '', - personal_text VARCHAR(255) NOT NULL DEFAULT '', - birthdate date NOT NULL DEFAULT '1004-01-01', - website_title VARCHAR(255) NOT NULL DEFAULT '', - website_url VARCHAR(255) NOT NULL DEFAULT '', - show_online TINYINT NOT NULL DEFAULT '1', - time_format VARCHAR(80) NOT NULL DEFAULT '', - signature TEXT NOT NULL, - time_offset float NOT NULL DEFAULT '0', - avatar VARCHAR(255) NOT NULL DEFAULT '', - usertitle VARCHAR(255) NOT NULL DEFAULT '', - member_ip VARBINARY(16), - member_ip2 VARBINARY(16), - secret_question VARCHAR(255) NOT NULL DEFAULT '', - secret_answer VARCHAR(64) NOT NULL DEFAULT '', - id_theme TINYINT UNSIGNED NOT NULL DEFAULT '0', - is_activated TINYINT UNSIGNED NOT NULL DEFAULT '1', - validation_code VARCHAR(10) NOT NULL DEFAULT '', - id_msg_last_visit INT UNSIGNED NOT NULL DEFAULT '0', - additional_groups VARCHAR(255) NOT NULL DEFAULT '', - smiley_set VARCHAR(48) NOT NULL DEFAULT '', - id_post_group SMALLINT UNSIGNED NOT NULL DEFAULT '0', - total_time_logged_in INT UNSIGNED NOT NULL DEFAULT '0', - password_salt VARCHAR(255) NOT NULL DEFAULT '', - ignore_boards TEXT NOT NULL, - warning TINYINT NOT NULL DEFAULT '0', - passwd_flood VARCHAR(12) NOT NULL DEFAULT '', - pm_receive_from TINYINT UNSIGNED NOT NULL DEFAULT '1', - timezone VARCHAR(80) NOT NULL DEFAULT '', - tfa_secret VARCHAR(24) NOT NULL DEFAULT '', - tfa_backup VARCHAR(64) NOT NULL DEFAULT '', - spoofdetector_name VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_member), - INDEX idx_member_name (member_name), - INDEX idx_real_name (real_name), - INDEX idx_email_address (email_address), - INDEX idx_date_registered (date_registered), - INDEX idx_id_group (id_group), - INDEX idx_birthdate (birthdate), - INDEX idx_posts (posts), - INDEX idx_last_login (last_login), - INDEX idx_lngfile (lngfile(30)), - INDEX idx_id_post_group (id_post_group), - INDEX idx_warning (warning), - INDEX idx_total_time_logged_in (total_time_logged_in), - INDEX idx_id_theme (id_theme), - INDEX idx_active_real_name (is_activated, real_name), - INDEX idx_spoofdetector_name (spoofdetector_name), - INDEX idx_spoofdetector_name_id (spoofdetector_name, id_member) -) ENGINE={$engine}; - -# -# Table structure for table `member_logins` -# - -CREATE TABLE {$db_prefix}member_logins ( - id_login INT AUTO_INCREMENT, - id_member MEDIUMINT NOT NULL DEFAULT '0', - time INT NOT NULL DEFAULT '0', - ip VARBINARY(16), - ip2 VARBINARY(16), - PRIMARY KEY (id_login), - INDEX idx_id_member (id_member), - INDEX idx_time (time) -) ENGINE={$engine}; - -# -# Table structure for table `message_icons` -# - -CREATE TABLE {$db_prefix}message_icons ( - id_icon SMALLINT UNSIGNED AUTO_INCREMENT, - title VARCHAR(80) NOT NULL DEFAULT '', - filename VARCHAR(80) NOT NULL DEFAULT '', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - icon_order SMALLINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_icon), - INDEX idx_id_board (id_board) -) ENGINE={$engine}; - -# -# Table structure for table `messages` -# - -CREATE TABLE {$db_prefix}messages ( - id_msg INT UNSIGNED AUTO_INCREMENT, - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - poster_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_msg_modified INT UNSIGNED NOT NULL DEFAULT '0', - subject VARCHAR(255) NOT NULL DEFAULT '', - poster_name VARCHAR(255) NOT NULL DEFAULT '', - poster_email VARCHAR(255) NOT NULL DEFAULT '', - poster_ip VARBINARY(16), - smileys_enabled TINYINT NOT NULL DEFAULT '1', - modified_time INT UNSIGNED NOT NULL DEFAULT '0', - modified_name VARCHAR(255) NOT NULL DEFAULT '', - modified_reason VARCHAR(255) NOT NULL DEFAULT '', - body TEXT NOT NULL, - icon VARCHAR(16) NOT NULL DEFAULT 'xx', - approved TINYINT NOT NULL DEFAULT '1', - likes SMALLINT UNSIGNED NOT NULL DEFAULT '0', - version VARCHAR(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_msg), - UNIQUE idx_id_board (id_board, id_msg, approved), - UNIQUE idx_id_member (id_member, id_msg), - INDEX idx_ip_index (poster_ip, id_topic), - INDEX idx_participation (id_member, id_topic), - INDEX idx_show_posts (id_member, id_board), - INDEX idx_id_member_msg (id_member, approved, id_msg), - INDEX idx_current_topic (id_topic, id_msg, id_member, approved), - INDEX idx_related_ip (id_member, poster_ip, id_msg), - INDEX idx_likes (likes) -) ENGINE={$engine}; - -# -# Table structure for table `moderators` -# - -CREATE TABLE {$db_prefix}moderators ( - id_board SMALLINT UNSIGNED DEFAULT '0', - id_member MEDIUMINT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_board, id_member) -) ENGINE={$engine}; - -# -# Table structure for table `moderator_groups` -# - -CREATE TABLE {$db_prefix}moderator_groups ( - id_board SMALLINT UNSIGNED DEFAULT '0', - id_group SMALLINT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_board, id_group) -) ENGINE={$engine}; - -# -# Table structure for table `package_servers` -# - -CREATE TABLE {$db_prefix}package_servers ( - id_server SMALLINT UNSIGNED AUTO_INCREMENT, - name VARCHAR(255) NOT NULL DEFAULT '', - url VARCHAR(255) NOT NULL DEFAULT '', - validation_url VARCHAR(255) NOT NULL DEFAULT '', - extra TEXT, - PRIMARY KEY (id_server) -) ENGINE={$engine}; - -# -# Table structure for table `permission_profiles` -# - -CREATE TABLE {$db_prefix}permission_profiles ( - id_profile SMALLINT AUTO_INCREMENT, - profile_name VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_profile) -) ENGINE={$engine}; - -# -# Table structure for table `permissions` -# - -CREATE TABLE {$db_prefix}permissions ( - id_group SMALLINT DEFAULT '0', - permission VARCHAR(30) DEFAULT '', - add_deny TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_group, permission) -) ENGINE={$engine}; - -# -# Table structure for table `personal_messages` -# - -CREATE TABLE {$db_prefix}personal_messages ( - id_pm INT UNSIGNED AUTO_INCREMENT, - id_pm_head INT UNSIGNED NOT NULL DEFAULT '0', - id_member_from MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - deleted_by_sender TINYINT UNSIGNED NOT NULL DEFAULT '0', - from_name VARCHAR(255) NOT NULL DEFAULT '', - msgtime INT UNSIGNED NOT NULL DEFAULT '0', - subject VARCHAR(255) NOT NULL DEFAULT '', - body TEXT NOT NULL, - version VARCHAR(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_pm), - INDEX idx_id_member (id_member_from, deleted_by_sender), - INDEX idx_msgtime (msgtime), - INDEX idx_id_pm_head (id_pm_head) -) ENGINE={$engine}; - -# -# Table structure for table `pm_labels` -# -CREATE TABLE {$db_prefix}pm_labels ( - id_label INT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - name VARCHAR(30) NOT NULL DEFAULT '', - PRIMARY KEY (id_label) -) ENGINE={$engine}; - -# -# Table structure for table `pm_labeled_messages` -# -CREATE TABLE {$db_prefix}pm_labeled_messages ( - id_label INT UNSIGNED DEFAULT '0', - id_pm INT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_label, id_pm) -) ENGINE={$engine}; - -# -# Table structure for table `pm_recipients` -# - -CREATE TABLE {$db_prefix}pm_recipients ( - id_pm INT UNSIGNED DEFAULT '0', - id_member MEDIUMINT UNSIGNED DEFAULT '0', - bcc TINYINT UNSIGNED NOT NULL DEFAULT '0', - is_read TINYINT UNSIGNED NOT NULL DEFAULT '0', - is_new TINYINT UNSIGNED NOT NULL DEFAULT '0', - deleted TINYINT UNSIGNED NOT NULL DEFAULT '0', - in_inbox TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_pm, id_member), - UNIQUE idx_id_member (id_member, deleted, id_pm) -) ENGINE={$engine}; - -# -# Table structure for table `pm_rules` -# - -CREATE TABLE {$db_prefix}pm_rules ( - id_rule INT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - rule_name VARCHAR(60) NOT NULL, - criteria TEXT NOT NULL, - actions TEXT NOT NULL, - delete_pm TINYINT UNSIGNED NOT NULL DEFAULT '0', - is_or TINYINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_rule), - INDEX idx_id_member (id_member), - INDEX idx_delete_pm (delete_pm) -) ENGINE={$engine}; - -# -# Table structure for table `polls` -# - -CREATE TABLE {$db_prefix}polls ( - id_poll MEDIUMINT UNSIGNED AUTO_INCREMENT, - question VARCHAR(255) NOT NULL DEFAULT '', - voting_locked TINYINT NOT NULL DEFAULT '0', - max_votes TINYINT UNSIGNED NOT NULL DEFAULT '1', - expire_time INT UNSIGNED NOT NULL DEFAULT '0', - hide_results TINYINT UNSIGNED NOT NULL DEFAULT '0', - change_vote TINYINT UNSIGNED NOT NULL DEFAULT '0', - guest_vote TINYINT UNSIGNED NOT NULL DEFAULT '0', - num_guest_voters INT UNSIGNED NOT NULL DEFAULT '0', - reset_poll INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - poster_name VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_poll) -) ENGINE={$engine}; - -# -# Table structure for table `poll_choices` -# - -CREATE TABLE {$db_prefix}poll_choices ( - id_poll MEDIUMINT UNSIGNED DEFAULT '0', - id_choice TINYINT UNSIGNED DEFAULT '0', - label VARCHAR(255) NOT NULL DEFAULT '', - votes SMALLINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_poll, id_choice) -) ENGINE={$engine}; - -# -# Table structure for table `qanda` -# - -CREATE TABLE {$db_prefix}qanda ( - id_question SMALLINT UNSIGNED AUTO_INCREMENT, - lngfile VARCHAR(255) NOT NULL DEFAULT '', - question VARCHAR(255) NOT NULL DEFAULT '', - answers TEXT NOT NULL, - PRIMARY KEY (id_question), - INDEX idx_lngfile (lngfile) -) ENGINE={$engine}; - -# -# Table structure for table `scheduled_tasks` -# - -CREATE TABLE {$db_prefix}scheduled_tasks ( - id_task SMALLINT AUTO_INCREMENT, - next_time INT NOT NULL DEFAULT '0', - time_offset INT NOT NULL DEFAULT '0', - time_regularity SMALLINT NOT NULL DEFAULT '0', - time_unit VARCHAR(1) NOT NULL DEFAULT 'h', - disabled TINYINT NOT NULL DEFAULT '0', - task VARCHAR(24) NOT NULL DEFAULT '', - callable VARCHAR(60) NOT NULL DEFAULT '', - PRIMARY KEY (id_task), - INDEX idx_next_time (next_time), - INDEX idx_disabled (disabled), - UNIQUE idx_task (task) -) ENGINE={$engine}; - -# -# Table structure for table `settings` -# - -CREATE TABLE {$db_prefix}settings ( - variable VARCHAR(255) DEFAULT '', - value TEXT NOT NULL, - PRIMARY KEY (variable(30)) -) ENGINE={$engine}; - -# -# Table structure for table `sessions` -# - -CREATE TABLE {$db_prefix}sessions ( - session_id VARCHAR(128) NOT NULL DEFAULT '', - last_update INT UNSIGNED NOT NULL DEFAULT '0', - data TEXT NOT NULL, - PRIMARY KEY (session_id) -) ENGINE={$engine}; - -# -# Table structure for table `smileys` -# - -CREATE TABLE {$db_prefix}smileys ( - id_smiley SMALLINT UNSIGNED AUTO_INCREMENT, - code VARCHAR(30) NOT NULL DEFAULT '', - description VARCHAR(80) NOT NULL DEFAULT '', - smiley_row TINYINT UNSIGNED NOT NULL DEFAULT '0', - smiley_order SMALLINT UNSIGNED NOT NULL DEFAULT '0', - hidden TINYINT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_smiley) -) ENGINE={$engine}; - -# -# Table structure for table `smiley_files` -# - -CREATE TABLE {$db_prefix}smiley_files -( - id_smiley SMALLINT NOT NULL DEFAULT '0', - smiley_set VARCHAR(48) NOT NULL DEFAULT '', - filename VARCHAR(48) NOT NULL DEFAULT '', - PRIMARY KEY (id_smiley, smiley_set) -) ENGINE={$engine}; - -# -# Table structure for table `spiders` -# - -CREATE TABLE {$db_prefix}spiders ( - id_spider SMALLINT UNSIGNED AUTO_INCREMENT, - spider_name VARCHAR(255) NOT NULL DEFAULT '', - user_agent VARCHAR(255) NOT NULL DEFAULT '', - ip_info VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY id_spider(id_spider) -) ENGINE={$engine}; - -# -# Table structure for table `subscriptions` -# - -CREATE TABLE {$db_prefix}subscriptions( - id_subscribe MEDIUMINT UNSIGNED AUTO_INCREMENT, - name VARCHAR(60) NOT NULL DEFAULT '', - description VARCHAR(255) NOT NULL DEFAULT '', - cost TEXT NOT NULL, - length VARCHAR(6) NOT NULL DEFAULT '', - id_group SMALLINT NOT NULL DEFAULT '0', - add_groups VARCHAR(40) NOT NULL DEFAULT '', - active TINYINT NOT NULL DEFAULT '1', - repeatable TINYINT NOT NULL DEFAULT '0', - allow_partial TINYINT NOT NULL DEFAULT '0', - reminder TINYINT NOT NULL DEFAULT '0', - email_complete TEXT NOT NULL, - PRIMARY KEY (id_subscribe), - INDEX idx_active (active) -) ENGINE={$engine}; - -# -# Table structure for table `themes` -# - -CREATE TABLE {$db_prefix}themes ( - id_member MEDIUMINT DEFAULT '0', - id_theme TINYINT UNSIGNED DEFAULT '1', - variable VARCHAR(255) DEFAULT '', - value TEXT NOT NULL, - PRIMARY KEY (id_theme, id_member, variable(30)), - INDEX idx_id_member (id_member) -) ENGINE={$engine}; - -# -# Table structure for table `topics` -# - -CREATE TABLE {$db_prefix}topics ( - id_topic MEDIUMINT UNSIGNED AUTO_INCREMENT, - is_sticky TINYINT NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_first_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_last_msg INT UNSIGNED NOT NULL DEFAULT '0', - id_member_started MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_member_updated MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_poll MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_previous_board SMALLINT NOT NULL DEFAULT '0', - id_previous_topic MEDIUMINT NOT NULL DEFAULT '0', - num_replies INT UNSIGNED NOT NULL DEFAULT '0', - num_views INT UNSIGNED NOT NULL DEFAULT '0', - locked TINYINT NOT NULL DEFAULT '0', - redirect_expires INT UNSIGNED NOT NULL DEFAULT '0', - id_redirect_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - unapproved_posts SMALLINT NOT NULL DEFAULT '0', - approved TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY (id_topic), - UNIQUE idx_last_message (id_last_msg, id_board), - UNIQUE idx_first_message (id_first_msg, id_board), - UNIQUE idx_poll (id_poll, id_topic), - INDEX idx_is_sticky (is_sticky), - INDEX idx_approved (approved), - INDEX idx_member_started (id_member_started, id_board), - INDEX idx_last_message_sticky (id_board, is_sticky, id_last_msg), - INDEX idx_board_news (id_board, id_first_msg) -) ENGINE={$engine}; - -# -# Table structure for table `user_alerts` -# - -CREATE TABLE {$db_prefix}user_alerts ( - id_alert INT UNSIGNED AUTO_INCREMENT, - alert_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_member_started MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - member_name VARCHAR(255) NOT NULL DEFAULT '', - content_type VARCHAR(255) NOT NULL DEFAULT '', - content_id INT UNSIGNED NOT NULL DEFAULT '0', - content_action VARCHAR(255) NOT NULL DEFAULT '', - is_read INT UNSIGNED NOT NULL DEFAULT '0', - extra TEXT NOT NULL, - PRIMARY KEY (id_alert), - INDEX idx_id_member (id_member), - INDEX idx_alert_time (alert_time) -) ENGINE={$engine}; - -# -# Table structure for table `user_alerts_prefs` -# - -CREATE TABLE {$db_prefix}user_alerts_prefs ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - alert_pref VARCHAR(32) DEFAULT '', - alert_value TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, alert_pref) -) ENGINE={$engine}; - -# -# Table structure for table `user_drafts` -# - -CREATE TABLE {$db_prefix}user_drafts ( - id_draft INT UNSIGNED AUTO_INCREMENT, - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_reply INT UNSIGNED NOT NULL DEFAULT '0', - type TINYINT NOT NULL DEFAULT '0', - poster_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - subject VARCHAR(255) NOT NULL DEFAULT '', - smileys_enabled TINYINT NOT NULL DEFAULT '1', - body MEDIUMTEXT NOT NULL, - icon VARCHAR(16) NOT NULL DEFAULT 'xx', - locked TINYINT NOT NULL DEFAULT '0', - is_sticky TINYINT NOT NULL DEFAULT '0', - to_list VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_draft), - UNIQUE idx_id_member (id_member, id_draft, type) -) ENGINE={$engine}; - -# -# Table structure for table `user_likes` -# - -CREATE TABLE {$db_prefix}user_likes ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - content_type CHAR(6) DEFAULT '', - content_id INT UNSIGNED DEFAULT '0', - like_time INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (content_id, content_type, id_member), - INDEX content (content_id, content_type), - INDEX liker (id_member) -) ENGINE={$engine}; - -# -# Table structure for table `mentions` -# -CREATE TABLE {$db_prefix}mentions ( - content_id INT DEFAULT '0', - content_type VARCHAR(10) DEFAULT '', - id_mentioned INT DEFAULT 0, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, - `time` INT NOT NULL DEFAULT 0, - PRIMARY KEY (content_id, content_type, id_mentioned), - INDEX content (content_id, content_type), - INDEX mentionee (id_member) -) ENGINE={$engine}; - -# Transactions for the win - only used if we have InnoDB available... -START TRANSACTION; - -# -# Dumping data for table `admin_info_files` -# - -INSERT INTO {$db_prefix}admin_info_files - (id_file, filename, path, parameters, data, filetype) -VALUES - (1, 'current-version.js', '/smf/', 'version=%3$s', '', 'text/javascript'), - (2, 'detailed-version.js', '/smf/', 'language=%1$s&version=%3$s', '', 'text/javascript'), - (3, 'latest-news.js', '/smf/', 'language=%1$s&format=%2$s', '', 'text/javascript'), - (4, 'latest-versions.txt', '/smf/', 'version=%3$s', '', 'text/plain'); -# -------------------------------------------------------- - -# -# Dumping data for table `board_permissions` -# - -INSERT INTO {$db_prefix}board_permissions - (id_group, id_profile, permission) -VALUES (-1, 1, 'poll_view'), - (0, 1, 'remove_own'), - (0, 1, 'lock_own'), - (0, 1, 'modify_own'), - (0, 1, 'poll_add_own'), - (0, 1, 'poll_edit_own'), - (0, 1, 'poll_lock_own'), - (0, 1, 'poll_post'), - (0, 1, 'poll_view'), - (0, 1, 'poll_vote'), - (0, 1, 'post_attachment'), - (0, 1, 'post_new'), - (0, 1, 'post_draft'), - (0, 1, 'post_reply_any'), - (0, 1, 'post_reply_own'), - (0, 1, 'post_unapproved_topics'), - (0, 1, 'post_unapproved_replies_any'), - (0, 1, 'post_unapproved_replies_own'), - (0, 1, 'post_unapproved_attachments'), - (0, 1, 'delete_own'), - (0, 1, 'report_any'), - (0, 1, 'view_attachments'), - (2, 1, 'moderate_board'), - (2, 1, 'post_new'), - (2, 1, 'post_draft'), - (2, 1, 'post_reply_own'), - (2, 1, 'post_reply_any'), - (2, 1, 'post_unapproved_topics'), - (2, 1, 'post_unapproved_replies_any'), - (2, 1, 'post_unapproved_replies_own'), - (2, 1, 'post_unapproved_attachments'), - (2, 1, 'poll_post'), - (2, 1, 'poll_add_any'), - (2, 1, 'poll_remove_any'), - (2, 1, 'poll_view'), - (2, 1, 'poll_vote'), - (2, 1, 'poll_lock_any'), - (2, 1, 'poll_edit_any'), - (2, 1, 'report_any'), - (2, 1, 'lock_own'), - (2, 1, 'delete_own'), - (2, 1, 'modify_own'), - (2, 1, 'make_sticky'), - (2, 1, 'lock_any'), - (2, 1, 'remove_any'), - (2, 1, 'move_any'), - (2, 1, 'merge_any'), - (2, 1, 'split_any'), - (2, 1, 'delete_any'), - (2, 1, 'modify_any'), - (2, 1, 'approve_posts'), - (2, 1, 'post_attachment'), - (2, 1, 'view_attachments'), - (3, 1, 'moderate_board'), - (3, 1, 'post_new'), - (3, 1, 'post_draft'), - (3, 1, 'post_reply_own'), - (3, 1, 'post_reply_any'), - (3, 1, 'post_unapproved_topics'), - (3, 1, 'post_unapproved_replies_any'), - (3, 1, 'post_unapproved_replies_own'), - (3, 1, 'post_unapproved_attachments'), - (3, 1, 'poll_post'), - (3, 1, 'poll_add_any'), - (3, 1, 'poll_remove_any'), - (3, 1, 'poll_view'), - (3, 1, 'poll_vote'), - (3, 1, 'poll_lock_any'), - (3, 1, 'poll_edit_any'), - (3, 1, 'report_any'), - (3, 1, 'lock_own'), - (3, 1, 'delete_own'), - (3, 1, 'modify_own'), - (3, 1, 'make_sticky'), - (3, 1, 'lock_any'), - (3, 1, 'remove_any'), - (3, 1, 'move_any'), - (3, 1, 'merge_any'), - (3, 1, 'split_any'), - (3, 1, 'delete_any'), - (3, 1, 'modify_any'), - (3, 1, 'approve_posts'), - (3, 1, 'post_attachment'), - (3, 1, 'view_attachments'), - (-1, 2, 'poll_view'), - (0, 2, 'remove_own'), - (0, 2, 'lock_own'), - (0, 2, 'modify_own'), - (0, 2, 'poll_view'), - (0, 2, 'poll_vote'), - (0, 2, 'post_attachment'), - (0, 2, 'post_new'), - (0, 2, 'post_draft'), - (0, 2, 'post_reply_any'), - (0, 2, 'post_reply_own'), - (0, 2, 'post_unapproved_topics'), - (0, 2, 'post_unapproved_replies_any'), - (0, 2, 'post_unapproved_replies_own'), - (0, 2, 'post_unapproved_attachments'), - (0, 2, 'delete_own'), - (0, 2, 'report_any'), - (0, 2, 'view_attachments'), - (2, 2, 'moderate_board'), - (2, 2, 'post_new'), - (2, 2, 'post_draft'), - (2, 2, 'post_reply_own'), - (2, 2, 'post_reply_any'), - (2, 2, 'post_unapproved_topics'), - (2, 2, 'post_unapproved_replies_any'), - (2, 2, 'post_unapproved_replies_own'), - (2, 2, 'post_unapproved_attachments'), - (2, 2, 'poll_post'), - (2, 2, 'poll_add_any'), - (2, 2, 'poll_remove_any'), - (2, 2, 'poll_view'), - (2, 2, 'poll_vote'), - (2, 2, 'poll_lock_any'), - (2, 2, 'poll_edit_any'), - (2, 2, 'report_any'), - (2, 2, 'lock_own'), - (2, 2, 'delete_own'), - (2, 2, 'modify_own'), - (2, 2, 'make_sticky'), - (2, 2, 'lock_any'), - (2, 2, 'remove_any'), - (2, 2, 'move_any'), - (2, 2, 'merge_any'), - (2, 2, 'split_any'), - (2, 2, 'delete_any'), - (2, 2, 'modify_any'), - (2, 2, 'approve_posts'), - (2, 2, 'post_attachment'), - (2, 2, 'view_attachments'), - (3, 2, 'moderate_board'), - (3, 2, 'post_new'), - (3, 2, 'post_draft'), - (3, 2, 'post_reply_own'), - (3, 2, 'post_reply_any'), - (3, 2, 'post_unapproved_topics'), - (3, 2, 'post_unapproved_replies_any'), - (3, 2, 'post_unapproved_replies_own'), - (3, 2, 'post_unapproved_attachments'), - (3, 2, 'poll_post'), - (3, 2, 'poll_add_any'), - (3, 2, 'poll_remove_any'), - (3, 2, 'poll_view'), - (3, 2, 'poll_vote'), - (3, 2, 'poll_lock_any'), - (3, 2, 'poll_edit_any'), - (3, 2, 'report_any'), - (3, 2, 'lock_own'), - (3, 2, 'delete_own'), - (3, 2, 'modify_own'), - (3, 2, 'make_sticky'), - (3, 2, 'lock_any'), - (3, 2, 'remove_any'), - (3, 2, 'move_any'), - (3, 2, 'merge_any'), - (3, 2, 'split_any'), - (3, 2, 'delete_any'), - (3, 2, 'modify_any'), - (3, 2, 'approve_posts'), - (3, 2, 'post_attachment'), - (3, 2, 'view_attachments'), - (-1, 3, 'poll_view'), - (0, 3, 'remove_own'), - (0, 3, 'lock_own'), - (0, 3, 'modify_own'), - (0, 3, 'poll_view'), - (0, 3, 'poll_vote'), - (0, 3, 'post_attachment'), - (0, 3, 'post_reply_any'), - (0, 3, 'post_reply_own'), - (0, 3, 'post_unapproved_replies_any'), - (0, 3, 'post_unapproved_replies_own'), - (0, 3, 'post_unapproved_attachments'), - (0, 3, 'delete_own'), - (0, 3, 'report_any'), - (0, 3, 'view_attachments'), - (2, 3, 'moderate_board'), - (2, 3, 'post_new'), - (2, 3, 'post_draft'), - (2, 3, 'post_reply_own'), - (2, 3, 'post_reply_any'), - (2, 3, 'post_unapproved_topics'), - (2, 3, 'post_unapproved_replies_any'), - (2, 3, 'post_unapproved_replies_own'), - (2, 3, 'post_unapproved_attachments'), - (2, 3, 'poll_post'), - (2, 3, 'poll_add_any'), - (2, 3, 'poll_remove_any'), - (2, 3, 'poll_view'), - (2, 3, 'poll_vote'), - (2, 3, 'poll_lock_any'), - (2, 3, 'poll_edit_any'), - (2, 3, 'report_any'), - (2, 3, 'lock_own'), - (2, 3, 'delete_own'), - (2, 3, 'modify_own'), - (2, 3, 'make_sticky'), - (2, 3, 'lock_any'), - (2, 3, 'remove_any'), - (2, 3, 'move_any'), - (2, 3, 'merge_any'), - (2, 3, 'split_any'), - (2, 3, 'delete_any'), - (2, 3, 'modify_any'), - (2, 3, 'approve_posts'), - (2, 3, 'post_attachment'), - (2, 3, 'view_attachments'), - (3, 3, 'moderate_board'), - (3, 3, 'post_new'), - (3, 3, 'post_draft'), - (3, 3, 'post_reply_own'), - (3, 3, 'post_reply_any'), - (3, 3, 'post_unapproved_topics'), - (3, 3, 'post_unapproved_replies_any'), - (3, 3, 'post_unapproved_replies_own'), - (3, 3, 'post_unapproved_attachments'), - (3, 3, 'poll_post'), - (3, 3, 'poll_add_any'), - (3, 3, 'poll_remove_any'), - (3, 3, 'poll_view'), - (3, 3, 'poll_vote'), - (3, 3, 'poll_lock_any'), - (3, 3, 'poll_edit_any'), - (3, 3, 'report_any'), - (3, 3, 'lock_own'), - (3, 3, 'delete_own'), - (3, 3, 'modify_own'), - (3, 3, 'make_sticky'), - (3, 3, 'lock_any'), - (3, 3, 'remove_any'), - (3, 3, 'move_any'), - (3, 3, 'merge_any'), - (3, 3, 'split_any'), - (3, 3, 'delete_any'), - (3, 3, 'modify_any'), - (3, 3, 'approve_posts'), - (3, 3, 'post_attachment'), - (3, 3, 'view_attachments'), - (-1, 4, 'poll_view'), - (0, 4, 'poll_view'), - (0, 4, 'poll_vote'), - (0, 4, 'report_any'), - (0, 4, 'view_attachments'), - (2, 4, 'moderate_board'), - (2, 4, 'post_new'), - (2, 4, 'post_draft'), - (2, 4, 'post_reply_own'), - (2, 4, 'post_reply_any'), - (2, 4, 'post_unapproved_topics'), - (2, 4, 'post_unapproved_replies_any'), - (2, 4, 'post_unapproved_replies_own'), - (2, 4, 'post_unapproved_attachments'), - (2, 4, 'poll_post'), - (2, 4, 'poll_add_any'), - (2, 4, 'poll_remove_any'), - (2, 4, 'poll_view'), - (2, 4, 'poll_vote'), - (2, 4, 'poll_lock_any'), - (2, 4, 'poll_edit_any'), - (2, 4, 'report_any'), - (2, 4, 'lock_own'), - (2, 4, 'delete_own'), - (2, 4, 'modify_own'), - (2, 4, 'make_sticky'), - (2, 4, 'lock_any'), - (2, 4, 'remove_any'), - (2, 4, 'move_any'), - (2, 4, 'merge_any'), - (2, 4, 'split_any'), - (2, 4, 'delete_any'), - (2, 4, 'modify_any'), - (2, 4, 'approve_posts'), - (2, 4, 'post_attachment'), - (2, 4, 'view_attachments'), - (3, 4, 'moderate_board'), - (3, 4, 'post_new'), - (3, 4, 'post_draft'), - (3, 4, 'post_reply_own'), - (3, 4, 'post_reply_any'), - (3, 4, 'post_unapproved_topics'), - (3, 4, 'post_unapproved_replies_any'), - (3, 4, 'post_unapproved_replies_own'), - (3, 4, 'post_unapproved_attachments'), - (3, 4, 'poll_post'), - (3, 4, 'poll_add_any'), - (3, 4, 'poll_remove_any'), - (3, 4, 'poll_view'), - (3, 4, 'poll_vote'), - (3, 4, 'poll_lock_any'), - (3, 4, 'poll_edit_any'), - (3, 4, 'report_any'), - (3, 4, 'lock_own'), - (3, 4, 'delete_own'), - (3, 4, 'modify_own'), - (3, 4, 'make_sticky'), - (3, 4, 'lock_any'), - (3, 4, 'remove_any'), - (3, 4, 'move_any'), - (3, 4, 'merge_any'), - (3, 4, 'split_any'), - (3, 4, 'delete_any'), - (3, 4, 'modify_any'), - (3, 4, 'approve_posts'), - (3, 4, 'post_attachment'), - (3, 4, 'view_attachments'); -# -------------------------------------------------------- - -# -# Dumping data for table `boards` -# - -INSERT INTO {$db_prefix}boards - (id_board, id_cat, board_order, id_last_msg, id_msg_updated, name, description, num_topics, num_posts, member_groups) -VALUES (1, 1, 1, 1, 1, '{$default_board_name}', '{$default_board_description}', 1, 1, '-1,0,2'); -# -------------------------------------------------------- - - -# -# Dumping data for table `board_permissions_view` -# - -INSERT INTO {$db_prefix}board_permissions_view - (id_group, id_board, deny) -VALUES (-1,1,0), (0,1,0), (2,1,0); -# -------------------------------------------------------- - -# -# Dumping data for table `calendar` -# - -INSERT INTO {$db_prefix}calendar - (title, start_date, end_date, start_time, timezone, location, duration, rrule, rdates, exdates, type, enabled) -VALUES - ('April Fools\' Day', '2000-04-01', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Christmas', '2000-12-25', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Cinco de Mayo', '2000-05-05', '9999-12-31', NULL, NULL, 'Mexico, USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('D-Day', '2000-06-06', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Easter', '2000-04-23', '9999-12-31', NULL, NULL, '', 'P1D', 'EASTER_W', '', '', 1, 1), - ('Earth Day', '2000-04-22', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Father\'s Day', '2000-06-19', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY;BYMONTH=6;BYDAY=3SU', '', '', 1, 1), - ('Flag Day', '2000-06-14', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Good Friday', '2000-04-21', '9999-12-31', NULL, NULL, '', 'P1D', 'EASTER_W-P2D', '', '', 1, 1), - ('Groundhog Day', '2000-02-02', '9999-12-31', NULL, NULL, 'Canada, USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Halloween', '2000-10-31', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Independence Day', '2000-07-04', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Labor Day', '2000-09-03', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', '', '', 1, 1), - ('Labour Day', '2000-09-03', '9999-12-31', NULL, NULL, 'Canada', 'P1D', 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', '', '', 1, 1), - ('Memorial Day', '2000-05-31', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=5;BYDAY=-1MO', '', '', 1, 1), - ('Mother\'s Day', '2000-05-08', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY;BYMONTH=5;BYDAY=2SU', '', '', 1, 1), - ('New Year\'s Day', '2000-01-01', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Remembrance Day', '2000-11-11', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('St. Patrick\'s Day', '2000-03-17', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Thanksgiving', '2000-11-26', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=11;BYDAY=4TH', '', '', 1, 1), - ('United Nations Day', '2000-10-24', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Valentine\'s Day', '2000-02-14', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Veterans Day', '2000-11-11', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Vernal Equinox', '2000-03-20', '9999-12-31', '07:30:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Summer Solstice', '2000-06-21', '9999-12-31', '01:44:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Autumnal Equinox', '2000-09-22', '9999-12-31', '17:16:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Winter Solstice', '2000-12-21', '9999-12-31', '13:27:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1); -# -------------------------------------------------------- - -# -# Dumping data for table `categories` -# - -INSERT INTO {$db_prefix}categories -VALUES (1, 0, '{$default_category_name}', '', 1); -# -------------------------------------------------------- - -# -# Dumping data for table `custom_fields` -# - -INSERT INTO {$db_prefix}custom_fields - (`col_name`, `field_name`, `field_desc`, `field_type`, `field_length`, `field_options`, `field_order`, `mask`, `show_reg`, `show_display`, `show_mlist`, `show_profile`, `private`, `active`, `bbc`, `can_search`, `default_value`, `enclose`, `placement`) -VALUES ('cust_skype', '{skype}', '{skype_desc}', 'text', 32, '', 2, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '{INPUT} ', 1), - ('cust_loca', '{location}', '{location_desc}', 'text', 50, '', 4, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '', 0), - ('cust_gender', '{gender}', '{gender_desc}', 'radio', 255, '{gender_0},{gender_1},{gender_2}', 5, 'nohtml', 1, 1, 0, 'forumprofile', 0, 1, 0, 0, '{gender_0}', '', 1); - -# -------------------------------------------------------- - -# -# Dumping data for table `membergroups` -# - -INSERT INTO {$db_prefix}membergroups - (id_group, group_name, description, online_color, min_posts, icons, group_type) -VALUES (1, '{$default_administrator_group}', '', '#FF0000', -1, '5#iconadmin.png', 1), - (2, '{$default_global_moderator_group}', '', '#0000FF', -1, '5#icongmod.png', 0), - (3, '{$default_moderator_group}', '', '', -1, '5#iconmod.png', 0), - (4, '{$default_newbie_group}', '', '', 0, '1#icon.png', 0), - (5, '{$default_junior_group}', '', '', 50, '2#icon.png', 0), - (6, '{$default_full_group}', '', '', 100, '3#icon.png', 0), - (7, '{$default_senior_group}', '', '', 250, '4#icon.png', 0), - (8, '{$default_hero_group}', '', '', 500, '5#icon.png', 0); -# -------------------------------------------------------- - -# -# Dumping data for table `message_icons` -# - -# // @todo i18n -INSERT INTO {$db_prefix}message_icons - (filename, title, icon_order) -VALUES ('xx', 'Standard', '0'), - ('thumbup', 'Thumb Up', '1'), - ('thumbdown', 'Thumb Down', '2'), - ('exclamation', 'Exclamation point', '3'), - ('question', 'Question mark', '4'), - ('lamp', 'Lamp', '5'), - ('smiley', 'Smiley', '6'), - ('angry', 'Angry', '7'), - ('cheesy', 'Cheesy', '8'), - ('grin', 'Grin', '9'), - ('sad', 'Sad', '10'), - ('wink', 'Wink', '11'), - ('poll', 'Poll', '12'); -# -------------------------------------------------------- - -# -# Dumping data for table `messages` -# - -INSERT INTO {$db_prefix}messages - (id_msg, id_msg_modified, id_topic, id_board, poster_time, subject, poster_name, poster_email, modified_name, body, icon) -VALUES (1, 1, 1, 1, UNIX_TIMESTAMP(), '{$default_topic_subject}', 'Simple Machines', 'info@simplemachines.org', '', '{$default_topic_message}', 'xx'); -# -------------------------------------------------------- - -# -# Dumping data for table `package_servers` -# - -INSERT INTO {$db_prefix}package_servers - (name, url, validation_url) -VALUES ('Simple Machines Third-party Mod Site', 'https://custom.simplemachines.org/packages/mods', 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}'), - ('Simple Machines Downloads 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}'); -# -------------------------------------------------------- - -# -# Dumping data for table `permission_profiles` -# - -INSERT INTO {$db_prefix}permission_profiles - (id_profile, profile_name) -VALUES (1, 'default'), (2, 'no_polls'), (3, 'reply_only'), (4, 'read_only'); -# -------------------------------------------------------- - -# -# Dumping data for table `permissions` -# - -INSERT INTO {$db_prefix}permissions - (id_group, permission) -VALUES (-1, 'search_posts'), - (-1, 'calendar_view'), - (-1, 'view_stats'), - (0, 'view_mlist'), - (0, 'search_posts'), - (0, 'profile_view'), - (0, 'pm_read'), - (0, 'pm_send'), - (0, 'pm_draft'), - (0, 'calendar_view'), - (0, 'view_stats'), - (0, 'who_view'), - (0, 'profile_identity_own'), - (0, 'profile_password_own'), - (0, 'profile_blurb_own'), - (0, 'profile_displayed_name_own'), - (0, 'profile_signature_own'), - (0, 'profile_website_own'), - (0, 'profile_forum_own'), - (0, 'profile_extra_own'), - (0, 'profile_remove_own'), - (0, 'profile_server_avatar'), - (0, 'profile_upload_avatar'), - (0, 'profile_remote_avatar'), - (2, 'view_mlist'), - (2, 'search_posts'), - (2, 'profile_view'), - (2, 'pm_read'), - (2, 'pm_send'), - (2, 'pm_draft'), - (2, 'calendar_view'), - (2, 'view_stats'), - (2, 'who_view'), - (2, 'profile_identity_own'), - (2, 'profile_password_own'), - (2, 'profile_blurb_own'), - (2, 'profile_displayed_name_own'), - (2, 'profile_signature_own'), - (2, 'profile_website_own'), - (2, 'profile_forum_own'), - (2, 'profile_extra_own'), - (2, 'profile_remove_own'), - (2, 'profile_server_avatar'), - (2, 'profile_upload_avatar'), - (2, 'profile_remote_avatar'), - (2, 'profile_title_own'), - (2, 'calendar_post'), - (2, 'calendar_edit_any'), - (2, 'access_mod_center'); -# -------------------------------------------------------- - -# -# Dumping data for table `scheduled_tasks` -# - -INSERT INTO {$db_prefix}scheduled_tasks - (id_task, next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (3, 0, 60, 1, 'd', 0, 'daily_maintenance', ''), - (5, 0, 0, 1, 'd', 0, 'daily_digest', ''), - (6, 0, 0, 1, 'w', 0, 'weekly_digest', ''), - (7, 0, {$sched_task_offset}, 1, 'd', 0, 'fetchSMfiles', ''), - (8, 0, 0, 1, 'd', 1, 'birthdayemails', ''), - (9, 0, 0, 1, 'w', 0, 'weekly_maintenance', ''), - (10, 0, 120, 1, 'd', 1, 'paid_subscriptions', ''), - (11, 0, 120, 1, 'd', 0, 'remove_temp_attachments', ''), - (12, 0, 180, 1, 'd', 0, 'remove_topic_redirect', ''), - (13, 0, 240, 1, 'd', 0, 'remove_old_drafts', ''), - (14, 0, 0, 1, 'w', 1, 'prune_log_topics', ''); - -# -------------------------------------------------------- - -# -# Dumping data for table `settings` -# - -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES ('smfVersion', '{$smf_version}'), - ('news', '{$default_news}'), - ('compactTopicPagesContiguous', '5'), - ('compactTopicPagesEnable', '1'), - ('todayMod', '1'), - ('enablePreviousNext', '1'), - ('pollMode', '1'), - ('enableCompressedOutput', '{$enableCompressedOutput}'), - ('attachmentSizeLimit', '128'), - ('attachmentPostLimit', '192'), - ('attachmentNumPerPostLimit', '4'), - ('attachmentDirSizeLimit', '10240'), - ('attachmentDirFileLimit', '1000'), - ('attachmentUploadDir', '{$attachdir}'), - ('attachmentExtensions', 'doc,gif,jpg,mpg,pdf,png,txt,zip'), - ('attachmentCheckExtensions', '0'), - ('attachmentShowImages', '1'), - ('attachmentEnable', '1'), - ('attachmentThumbnails', '1'), - ('attachmentThumbWidth', '150'), - ('attachmentThumbHeight', '150'), - ('use_subdirectories_for_attachments', '1'), - ('currentAttachmentUploadDir', 1), - ('censorIgnoreCase', '1'), - ('spoofdetector_censor', '1'), - ('mostOnline', '1'), - ('mostOnlineToday', '1'), - ('mostDate', UNIX_TIMESTAMP()), - ('trackStats', '1'), - ('userLanguage', '1'), - ('titlesEnable', '1'), - ('topicSummaryPosts', '15'), - ('enableErrorLogging', '1'), - ('max_image_width', '0'), - ('max_image_height', '0'), - ('onlineEnable', '0'), - ('boardindex_max_depth', '5'), - ('cal_enabled', '0'), - ('cal_showInTopic', '1'), - ('cal_maxyear', '2040'), - ('cal_minyear', '2018'), - ('cal_daysaslink', '0'), - ('cal_defaultboard', ''), - ('cal_showholidays', '1'), - ('cal_showbdays', '1'), - ('cal_showevents', '1'), - ('cal_maxspan', '0'), - ('cal_disable_prev_next', '0'), - ('cal_display_type', '0'), - ('cal_week_links', '2'), - ('cal_prev_next_links', '1'), - ('cal_short_days', '0'), - ('cal_short_months', '0'), - ('smtp_host', ''), - ('smtp_port', '25'), - ('smtp_username', ''), - ('smtp_password', ''), - ('mail_type', '0'), - ('timeLoadPageEnable', '0'), - ('totalMembers', '0'), - ('totalTopics', '1'), - ('totalMessages', '1'), - ('censor_vulgar', ''), - ('censor_proper', ''), - ('enablePostHTML', '0'), - ('theme_allow', '1'), - ('theme_default', '1'), - ('theme_guests', '1'), - ('xmlnews_enable', '1'), - ('xmlnews_maxlen', '255'), - ('registration_method', '{$registration_method}'), - ('send_validation_onChange', '0'), - ('send_welcomeEmail', '1'), - ('allow_editDisplayName', '1'), - ('allow_hideOnline', '1'), - ('spamWaitTime', '5'), - ('pm_spam_settings', '10,5,20'), - ('reserveWord', '0'), - ('reserveCase', '1'), - ('reserveUser', '1'), - ('reserveName', '1'), - ('reserveNames', '{$default_reserved_names}'), - ('autoLinkUrls', '1'), - ('banLastUpdated', '0'), - ('smileys_dir', '{$boarddir}/Smileys'), - ('smileys_url', '{$boardurl}/Smileys'), - ('custom_avatar_dir', '{$boarddir}/custom_avatar'), - ('custom_avatar_url', '{$boardurl}/custom_avatar'), - ('avatar_directory', '{$boarddir}/avatars'), - ('avatar_url', '{$boardurl}/avatars'), - ('avatar_max_height_external', '65'), - ('avatar_max_width_external', '65'), - ('avatar_action_too_large', 'option_css_resize'), - ('avatar_max_height_upload', '65'), - ('avatar_max_width_upload', '65'), - ('avatar_resize_upload', '1'), - ('avatar_download_png', '1'), - ('failed_login_threshold', '3'), - ('oldTopicDays', '120'), - ('edit_wait_time', '90'), - ('edit_disable_time', '0'), - ('autoFixDatabase', '1'), - ('allow_guestAccess', '1'), - ('time_format', '{$default_time_format}'), - ('number_format', '1234.00'), - ('enableBBC', '1'), - ('max_messageLength', '20000'), - ('signature_settings', '1,300,0,0,0,0,0,0:'), - ('defaultMaxMessages', '15'), - ('defaultMaxTopics', '20'), - ('defaultMaxMembers', '30'), - ('enableParticipation', '1'), - ('recycle_enable', '0'), - ('recycle_board', '0'), - ('maxMsgID', '1'), - ('enableAllMessages', '0'), - ('knownThemes', '1'), - ('enableThemes', '1'), - ('who_enabled', '1'), - ('lastActive', '15'), - ('smiley_sets_known', 'fugue,alienine'), - ('smiley_sets_names', '{$default_fugue_smileyset_name}\n{$default_alienine_smileyset_name}'), - ('smiley_sets_default', 'fugue'), - ('cal_days_for_index', '7'), - ('requireAgreement', '1'), - ('requirePolicyAgreement', '0'), - ('unapprovedMembers', '0'), - ('default_personal_text', ''), - ('package_make_backups', '1'), - ('databaseSession_enable', '{$databaseSession_enable}'), - ('databaseSession_loose', '1'), - ('databaseSession_lifetime', '2880'), - ('search_cache_size', '50'), - ('search_results_per_page', '30'), - ('search_weight_frequency', '30'), - ('search_weight_age', '25'), - ('search_weight_length', '20'), - ('search_weight_subject', '15'), - ('search_weight_first_message', '10'), - ('search_max_results', '1200'), - ('search_floodcontrol_time', '5'), - ('permission_enable_deny', '0'), - ('permission_enable_postgroups', '0'), - ('mail_next_send', '0'), - ('mail_recent', '0000000000|0'), - ('settings_updated', '0'), - ('next_task_time', '1'), - ('warning_settings', '1,20,0'), - ('warning_watch', '10'), - ('warning_moderate', '35'), - ('warning_mute', '60'), - ('last_mod_report_action', '0'), - ('pruningOptions', '30,180,180,180,30,0'), - ('mark_read_beyond', '90'), - ('mark_read_delete_beyond', '365'), - ('mark_read_max_users', '500'), - ('modlog_enabled', '1'), - ('adminlog_enabled', '1'), - ('reg_verification', '1'), - ('visual_verification_type', '3'), - ('enable_buddylist', '1'), - ('birthday_email', 'happy_birthday'), - ('dont_repeat_theme_core', '1'), - ('dont_repeat_smileys_20', '1'), - ('dont_repeat_buddylists', '1'), - ('attachment_image_reencode', '1'), - ('attachment_image_paranoid', '0'), - ('attachment_thumb_png', '1'), - ('avatar_reencode', '1'), - ('avatar_paranoid', '0'), - ('drafts_post_enabled', '1'), - ('drafts_pm_enabled', '1'), - ('drafts_autosave_enabled', '1'), - ('drafts_show_saved_enabled', '1'), - ('drafts_keep_days', '7'), - ('topic_move_any', '0'), - ('mail_limit', '5'), - ('mail_quantity', '5'), - ('additional_options_collapsable', '1'), - ('show_modify', '1'), - ('show_user_images', '1'), - ('show_blurb', '1'), - ('show_profile_buttons', '1'), - ('enable_ajax_alerts', '1'), - ('alerts_auto_purge', '30'), - ('gravatarEnabled', '1'), - ('gravatarOverride', '0'), - ('gravatarAllowExtraEmail', '1'), - ('gravatarMaxRating', 'PG'), - ('defaultMaxListItems', '15'), - ('loginHistoryDays', '30'), - ('httponlyCookies', '1'), - ('samesiteCookies', 'lax'), - ('tfa_mode', '1'), - ('export_dir', '{$boarddir}/exports'), - ('export_expiry', '7'), - ('export_min_diskspace_pct', '5'), - ('export_rate', '250'), - ('allow_expire_redirect', '1'), - ('json_done', '1'), - ('attachments_21_done', '1'), - ('displayFields', '[{"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"]}]'), - ('minimize_files', '1'), - ('securityDisable_moderate', '1'); - -# -------------------------------------------------------- - -# -# Dumping data for table `smileys` -# - -INSERT INTO {$db_prefix}smileys - (code, description, smiley_order, hidden) -VALUES (':)', '{$default_smiley_smiley}', 0, 0), - (';)', '{$default_wink_smiley}', 1, 0), - (':D', '{$default_cheesy_smiley}', 2, 0), - (';D', '{$default_grin_smiley}', 3, 0), - ('>:(', '{$default_angry_smiley}', 4, 0), - (':(', '{$default_sad_smiley}', 5, 0), - (':o', '{$default_shocked_smiley}', 6, 0), - ('8)', '{$default_cool_smiley}', 7, 0), - ('???', '{$default_huh_smiley}', 8, 0), - ('::)', '{$default_roll_eyes_smiley}', 9, 0), - (':P', '{$default_tongue_smiley}', 10, 0), - (':-[', '{$default_embarrassed_smiley}', 11, 0), - (':-X', '{$default_lips_sealed_smiley}', 12, 0), - (':-\\', '{$default_undecided_smiley}', 13, 0), - (':-*', '{$default_kiss_smiley}', 14, 0), - (':''(', '{$default_cry_smiley}', 15, 0), - ('>:D', '{$default_evil_smiley}', 16, 1), - ('^-^', '{$default_azn_smiley}', 17, 1), - ('O0', '{$default_afro_smiley}', 18, 1), - (':))', '{$default_laugh_smiley}', 19, 1), - ('C:-)', '{$default_police_smiley}', 20, 1), - ('O:-)', '{$default_angel_smiley}', 21, 1); -# -------------------------------------------------------- - -# -# Dumping data for table `spiders` -# - -INSERT INTO {$db_prefix}spiders - (spider_name, user_agent, ip_info) -VALUES ('Google', 'googlebot', ''), - ('Yahoo!', 'slurp', ''), - ('Bing', 'bingbot', ''), - ('Google (Mobile)', 'Googlebot-Mobile', ''), - ('Google (Image)', 'Googlebot-Image', ''), - ('Google (AdSense)', 'Mediapartners-Google', ''), - ('Google (Adwords)', 'AdsBot-Google', ''), - ('Yahoo! (Mobile)', 'YahooSeeker/M1A1-R2D2', ''), - ('Yahoo! (Image)', 'Yahoo-MMCrawler', ''), - ('Bing (Preview)', 'BingPreview', ''), - ('Bing (Ads)', 'adidxbot', ''), - ('Bing (MSNBot)', 'msnbot', ''), - ('Bing (Media)', 'msnbot-media', ''), - ('Cuil', 'twiceler', ''), - ('Ask', 'Teoma', ''), - ('Baidu', 'Baiduspider', ''), - ('Gigablast', 'Gigabot', ''), - ('InternetArchive', 'ia_archiver-web.archive.org', ''), - ('Alexa', 'ia_archiver', ''), - ('Omgili', 'omgilibot', ''), - ('EntireWeb', 'Speedy Spider', ''), - ('Yandex', 'yandex', ''); -#--------------------------------------------------------- - -# -# Dumping data for table `themes` -# - -INSERT INTO {$db_prefix}themes - (id_theme, variable, value) -VALUES (1, 'name', '{$default_theme_name}'), - (1, 'theme_url', '{$boardurl}/Themes/default'), - (1, 'images_url', '{$boardurl}/Themes/default/images'), - (1, 'theme_dir', '{$boarddir}/Themes/default'), - (1, 'show_latest_member', '1'), - (1, 'show_newsfader', '0'), - (1, 'number_recent_posts', '0'), - (1, 'show_stats_index', '1'), - (1, 'newsfader_time', '3000'), - (1, 'use_image_buttons', '1'), - (1, 'enable_news', '1'); - -INSERT INTO {$db_prefix}themes - (id_member, id_theme, variable, value) -VALUES (-1, 1, 'posts_apply_ignore_list', '1'), - (-1, 1, 'drafts_show_saved_enabled', '1'), - (-1, 1, 'return_to_post', '1'); -# -------------------------------------------------------- - -# -# Dumping data for table `topics` -# - -INSERT INTO {$db_prefix}topics - (id_topic, id_board, id_first_msg, id_last_msg, id_member_started, id_member_updated) -VALUES (1, 1, 1, 1, 0, 0); -# -------------------------------------------------------- - -# -# Dumping data for table `user_alerts_prefs` -# - -INSERT INTO {$db_prefix}user_alerts_prefs - (id_member, alert_pref, alert_value) -VALUES (0, 'alert_timeout', 10), - (0, 'announcements', 0), - (0, 'birthday', 2), - (0, 'board_notify', 1), - (0, 'buddy_request', 1), - (0, 'groupr_approved', 3), - (0, 'groupr_rejected', 3), - (0, 'member_group_request', 1), - (0, 'member_register', 1), - (0, 'member_report', 3), - (0, 'member_report_reply', 3), - (0, 'msg_auto_notify', 0), - (0, 'msg_like', 1), - (0, 'msg_mention', 1), - (0, 'msg_notify_pref', 1), - (0, 'msg_notify_type', 1), - (0, 'msg_quote', 1), - (0, 'msg_receive_body', 0), - (0, 'msg_report', 1), - (0, 'msg_report_reply', 1), - (0, 'pm_new', 1), - (0, 'pm_notify', 1), - (0, 'pm_reply', 1), - (0, 'request_group', 1), - (0, 'topic_notify', 1), - (0, 'unapproved_attachment', 1), - (0, 'unapproved_reply', 3), - (0, 'unapproved_post', 1), - (0, 'warn_any', 1); -# -------------------------------------------------------- - -COMMIT; diff --git a/other/install_3-0_PostgreSQL.sql b/other/install_3-0_PostgreSQL.sql deleted file mode 100644 index 300dc4a25e..0000000000 --- a/other/install_3-0_PostgreSQL.sql +++ /dev/null @@ -1,2664 +0,0 @@ -#### ATTENTION: You do not need to run or use this file! The install.php script does everything for you! -#### Install script for PostgreSQL 8.0.1 - -# -# Create PostgreSQL functions. -# Some taken from http://www.xach.com/aolserver/mysql-functions.sql and http://pgfoundry.org/projects/mysqlcompat/. -# IP Regex in inet_aton from https://www.mkyong.com/database/regular-expression-in-postgresql/. - -CREATE OR REPLACE FUNCTION FROM_UNIXTIME(bigint) RETURNS timestamp AS - 'SELECT timestamp ''epoch'' + $1 * interval ''1 second'' AS result' -LANGUAGE 'sql'; - -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'; - -CREATE OR REPLACE FUNCTION FIND_IN_SET(needle integer, 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] = CAST($1 AS text) - UNION ALL - SELECT 0 - LIMIT 1' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION FIND_IN_SET(needle smallint, 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] = CAST($1 AS text) - UNION ALL - SELECT 0 - LIMIT 1' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION add_num_text (text, integer) RETURNS text AS - 'SELECT CAST ((CAST($1 AS integer) + $2) AS text) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION YEAR (timestamp) RETURNS integer AS - 'SELECT CAST (EXTRACT(YEAR FROM $1) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION MONTH (timestamp) RETURNS integer AS - 'SELECT CAST (EXTRACT(MONTH FROM $1) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION MONTH (bigint) RETURNS integer AS - 'SELECT CAST (EXTRACT(MONTH FROM TO_TIMESTAMP($1)) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION day(date) RETURNS integer AS - 'SELECT EXTRACT(DAY FROM DATE($1))::integer AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION DAYOFMONTH (timestamp) RETURNS integer AS - 'SELECT CAST (EXTRACT(DAY FROM $1) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION DAYOFMONTH (bigint) RETURNS integer AS - 'SELECT CAST (EXTRACT(DAY FROM TO_TIMESTAMP($1)) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION HOUR (timestamp) RETURNS integer AS - 'SELECT CAST (EXTRACT(HOUR FROM $1) AS integer) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION DATE_FORMAT (timestamp, text) RETURNS text AS ' - SELECT - REPLACE( - REPLACE($2, ''%m'', to_char($1, ''MM'')), - ''%d'', to_char($1, ''DD'')) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION TO_DAYS (timestamp) RETURNS integer AS - 'SELECT DATE_PART(''DAY'', $1 - ''0001-01-01bc'')::integer AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION INSTR (text, text) RETURNS integer AS - 'SELECT POSITION($2 in $1) AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION bool_not_eq_int (boolean, integer) RETURNS boolean AS - 'SELECT CAST($1 AS integer) != $2 AS result' -LANGUAGE 'sql'; - -CREATE OR REPLACE FUNCTION indexable_month_day(date) RETURNS TEXT as ' - SELECT to_char($1, ''MM-DD'');' -LANGUAGE 'sql' IMMUTABLE STRICT; - -# -# Create PostgreSQL operators. -# - -CREATE OPERATOR + (PROCEDURE = add_num_text, LEFTARG = text, RIGHTARG = integer); -CREATE OPERATOR != (PROCEDURE = bool_not_eq_int, LEFTARG = boolean, RIGHTARG = integer); - -# -# Sequence for table `admin_info_files` -# - -CREATE SEQUENCE {$db_prefix}admin_info_files_seq START WITH 8; - -# -# Table structure for table `admin_info_files` -# - -CREATE TABLE {$db_prefix}admin_info_files ( - id_file smallint DEFAULT nextval('{$db_prefix}admin_info_files_seq'), - filename varchar(255) NOT NULL DEFAULT '', - path varchar(255) NOT NULL DEFAULT '', - parameters varchar(255) NOT NULL DEFAULT '', - data text NOT NULL, - filetype varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_file) -); - -# -# Indexes for table `admin_info_files` -# - -CREATE INDEX {$db_prefix}admin_info_files_filename ON {$db_prefix}admin_info_files (filename varchar_pattern_ops); - -# -# Table structure for table `approval_queue` -# - -CREATE TABLE {$db_prefix}approval_queue ( - id_msg bigint NOT NULL DEFAULT '0', - id_attach bigint NOT NULL DEFAULT '0', - id_event smallint NOT NULL DEFAULT '0' -); - -# -# Sequence for table `attachments` -# - -CREATE SEQUENCE {$db_prefix}attachments_seq; - -# -# Table structure for table `attachments` -# - -CREATE TABLE {$db_prefix}attachments ( - id_attach bigint DEFAULT nextval('{$db_prefix}attachments_seq'), - id_thumb bigint NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_folder smallint NOT NULL DEFAULT '1', - attachment_type smallint NOT NULL DEFAULT '0', - filename varchar(255) NOT NULL DEFAULT '', - file_hash varchar(40) NOT NULL DEFAULT '', - fileext varchar(8) NOT NULL DEFAULT '', - size int NOT NULL DEFAULT '0', - downloads int NOT NULL DEFAULT '0', - width int NOT NULL DEFAULT '0', - height int NOT NULL DEFAULT '0', - mime_type varchar(128) NOT NULL DEFAULT '', - approved smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_attach) -); - -# -# Indexes for table `attachments` -# - -CREATE UNIQUE INDEX {$db_prefix}attachments_id_member ON {$db_prefix}attachments (id_member, id_attach); -CREATE INDEX {$db_prefix}attachments_id_msg ON {$db_prefix}attachments (id_msg); -CREATE INDEX {$db_prefix}attachments_attachment_type ON {$db_prefix}attachments (attachment_type); -CREATE INDEX {$db_prefix}attachments_id_thumb ON {$db_prefix}attachments (id_thumb); - -# -# Sequence for table `background_tasks` -# - -CREATE SEQUENCE {$db_prefix}background_tasks_seq; - -# -# Table structure for table `background_tasks` -# - -CREATE TABLE {$db_prefix}background_tasks ( - id_task bigint DEFAULT nextval('{$db_prefix}background_tasks_seq'), - task_file varchar(255) NOT NULL DEFAULT '', - task_class varchar(255) NOT NULL DEFAULT '', - task_data text NOT NULL, - claimed_time int NOT NULL DEFAULT '0', - PRIMARY KEY (id_task) -); - -# -# Sequence for table `ban_groups` -# - -CREATE SEQUENCE {$db_prefix}ban_groups_seq; - -# -# Table structure for table `ban_groups` -# - -CREATE TABLE {$db_prefix}ban_groups ( - id_ban_group int DEFAULT nextval('{$db_prefix}ban_groups_seq'), - name varchar(20) NOT NULL DEFAULT '', - ban_time bigint NOT NULL DEFAULT '0', - expire_time bigint, - cannot_access smallint NOT NULL DEFAULT '0', - cannot_register smallint NOT NULL DEFAULT '0', - cannot_post smallint NOT NULL DEFAULT '0', - cannot_login smallint NOT NULL DEFAULT '0', - reason varchar(255) NOT NULL, - notes text NOT NULL, - PRIMARY KEY (id_ban_group) -); - -# -# Sequence for table `ban_items` -# - -CREATE SEQUENCE {$db_prefix}ban_items_seq; - -# -# Table structure for table `ban_items` -# - -CREATE TABLE {$db_prefix}ban_items ( - id_ban int DEFAULT nextval('{$db_prefix}ban_items_seq'), - id_ban_group smallint NOT NULL DEFAULT '0', - ip_low inet, - ip_high inet, - hostname varchar(255) NOT NULL DEFAULT '', - email_address varchar(255) NOT NULL DEFAULT '', - id_member int NOT NULL DEFAULT '0', - hits bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_ban) -); - -# -# Indexes for table `ban_items` -# - -CREATE INDEX {$db_prefix}ban_items_id_ban_group ON {$db_prefix}ban_items (id_ban_group); -CREATE INDEX {$db_prefix}ban_items_id_ban_ip ON {$db_prefix}ban_items (ip_low,ip_high); - -# -# Table structure for table `board_permissions` -# - -CREATE TABLE {$db_prefix}board_permissions ( - id_group smallint NOT NULL DEFAULT '0', - id_profile smallint NOT NULL DEFAULT '0', - permission varchar(30) NOT NULL DEFAULT '', - add_deny smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_group, id_profile, permission) -); - -# -# Sequence for table `boards` -# - -CREATE SEQUENCE {$db_prefix}boards_seq START WITH 2; - -# -# Table structure for table `boards` -# - -CREATE TABLE {$db_prefix}boards ( - id_board smallint DEFAULT nextval('{$db_prefix}boards_seq'), - id_cat smallint NOT NULL DEFAULT '0', - child_level smallint NOT NULL DEFAULT '0', - id_parent smallint NOT NULL DEFAULT '0', - board_order smallint NOT NULL DEFAULT '0', - id_last_msg bigint NOT NULL DEFAULT '0', - id_msg_updated bigint NOT NULL DEFAULT '0', - member_groups varchar(255) NOT NULL DEFAULT '-1,0', - id_profile smallint NOT NULL DEFAULT '1', - name varchar(255) NOT NULL DEFAULT '', - description text NOT NULL, - num_topics int NOT NULL DEFAULT '0', - num_posts int NOT NULL DEFAULT '0', - count_posts smallint NOT NULL DEFAULT '0', - id_theme smallint NOT NULL DEFAULT '0', - override_theme smallint NOT NULL DEFAULT '0', - unapproved_posts smallint NOT NULL DEFAULT '0', - unapproved_topics smallint NOT NULL DEFAULT '0', - redirect varchar(255) NOT NULL DEFAULT '', - deny_member_groups varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_board) -); - -# -# Indexes for table `ban_items` -# - -CREATE UNIQUE INDEX {$db_prefix}boards_categories ON {$db_prefix}boards (id_cat, id_board); -CREATE INDEX {$db_prefix}boards_id_parent ON {$db_prefix}boards (id_parent); -CREATE INDEX {$db_prefix}boards_id_msg_updated ON {$db_prefix}boards (id_msg_updated); -CREATE INDEX {$db_prefix}boards_member_groups ON {$db_prefix}boards (member_groups varchar_pattern_ops); - -# -# Table structure for table `board_permissions_view` -# - -CREATE TABLE {$db_prefix}board_permissions_view -( - id_group smallint NOT NULL DEFAULT '0', - id_board smallint NOT NULL, - deny smallint NOT NULL, - PRIMARY KEY (id_group, id_board, deny) -); - -# -# Sequence for table `calendar` -# - -CREATE SEQUENCE {$db_prefix}calendar_seq; - -# -# Table structure for table `calendar` -# - -CREATE TABLE {$db_prefix}calendar ( - id_event smallint DEFAULT nextval('{$db_prefix}calendar_seq'), - id_board smallint NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - title varchar(255) NOT NULL DEFAULT '', - id_member int NOT NULL DEFAULT '0', - start_date date NOT NULL DEFAULT '1004-01-01', - end_date date NOT NULL DEFAULT '1004-01-01', - start_time time, - end_time time, - timezone varchar(80), - location varchar(255) NOT NULL DEFAULT '', - duration varchar(32) NOT NULL DEFAULT '', - rrule varchar(1024) NOT NULL DEFAULT 'FREQ=YEARLY;COUNT=1', - rdates text NOT NULL, - exdates text NOT NULL, - adjustments jsonb DEFAULT NULL, - sequence smallint NOT NULL DEFAULT '0', - uid VARCHAR(255) NOT NULL DEFAULT '', - type smallint NOT NULL DEFAULT '0', - enabled smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_event) -); - -# -# Indexes for table `calendar` -# - -CREATE INDEX {$db_prefix}calendar_start_date ON {$db_prefix}calendar (start_date); -CREATE INDEX {$db_prefix}calendar_end_date ON {$db_prefix}calendar (end_date); -CREATE INDEX {$db_prefix}calendar_topic ON {$db_prefix}calendar (id_topic, id_member); - -# -# Sequence for table `categories` -# - -CREATE SEQUENCE {$db_prefix}categories_seq START WITH 2; - -# -# Table structure for table `categories` -# - -CREATE TABLE {$db_prefix}categories ( - id_cat smallint DEFAULT nextval('{$db_prefix}categories_seq'), - cat_order smallint NOT NULL DEFAULT '0', - name varchar(255) NOT NULL DEFAULT '', - description text NOT NULL, - can_collapse smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_cat) -); - -# -# Sequence for table `custom_fields` -# - -CREATE SEQUENCE {$db_prefix}custom_fields_seq; - -# -# Table structure for table `custom_fields` -# - -CREATE TABLE {$db_prefix}custom_fields ( - id_field smallint DEFAULT nextval('{$db_prefix}custom_fields_seq'), - col_name varchar(12) NOT NULL DEFAULT '', - field_name varchar(40) NOT NULL DEFAULT '', - field_desc varchar(255) NOT NULL DEFAULT '', - field_type varchar(8) NOT NULL DEFAULT 'text', - field_length smallint NOT NULL DEFAULT '255', - field_options text NOT NULL, - field_order smallint NOT NULL DEFAULT '0', - mask varchar(255) NOT NULL DEFAULT '', - show_reg smallint NOT NULL DEFAULT '0', - show_display smallint NOT NULL DEFAULT '0', - show_mlist smallint NOT NULL DEFAULT '0', - show_profile varchar(20) NOT NULL DEFAULT 'forumprofile', - private smallint NOT NULL DEFAULT '0', - active smallint NOT NULL DEFAULT '1', - bbc smallint NOT NULL DEFAULT '0', - can_search smallint NOT NULL DEFAULT '0', - default_value varchar(255) NOT NULL DEFAULT '', - enclose text NOT NULL, - placement smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_field) -); - -# -# Indexes for table `custom_fields` -# - -CREATE UNIQUE INDEX {$db_prefix}custom_fields_col_name ON {$db_prefix}custom_fields (col_name); - -# -# Table structure for table `group_moderators` -# - -CREATE TABLE {$db_prefix}group_moderators ( - id_group smallint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - PRIMARY KEY (id_group, id_member) -); - -# -# Sequence for table `log_actions` -# - -CREATE SEQUENCE {$db_prefix}log_actions_seq; - -# -# Table structure for table `log_actions` -# - -CREATE TABLE {$db_prefix}log_actions ( - id_action bigint DEFAULT nextval('{$db_prefix}log_actions_seq'), - id_log smallint NOT NULL DEFAULT '1', - log_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - ip inet, - action varchar(30) NOT NULL DEFAULT '', - id_board smallint NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - extra text NOT NULL, - PRIMARY KEY (id_action) -); - -# -# Indexes for table `log_actions` -# - -CREATE INDEX {$db_prefix}log_actions_log_time ON {$db_prefix}log_actions (log_time); -CREATE INDEX {$db_prefix}log_actions_id_member ON {$db_prefix}log_actions (id_member); -CREATE INDEX {$db_prefix}log_actions_id_board ON {$db_prefix}log_actions (id_board); -CREATE INDEX {$db_prefix}log_actions_id_msg ON {$db_prefix}log_actions (id_msg); -CREATE INDEX {$db_prefix}log_actions_id_log ON {$db_prefix}log_actions (id_log); -CREATE INDEX {$db_prefix}log_actions_id_topic_id_log ON {$db_prefix}log_actions (id_topic, id_log); - -# -# Table structure for table `log_activity` -# - -CREATE TABLE {$db_prefix}log_activity ( - date date NOT NULL, - hits int NOT NULL DEFAULT '0', - topics smallint NOT NULL DEFAULT '0', - posts smallint NOT NULL DEFAULT '0', - registers smallint NOT NULL DEFAULT '0', - most_on smallint NOT NULL DEFAULT '0', - PRIMARY KEY (date) -); - -# -# Sequence for table `log_banned` -# - -CREATE SEQUENCE {$db_prefix}log_banned_seq; - -# -# Table structure for table `log_banned` -# - -CREATE TABLE {$db_prefix}log_banned ( - id_ban_log int DEFAULT nextval('{$db_prefix}log_banned_seq'), - id_member int NOT NULL DEFAULT '0', - ip inet, - email varchar(255) NOT NULL DEFAULT '', - log_time bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_ban_log) -); - -# -# Indexes for table `log_banned` -# - -CREATE INDEX {$db_prefix}log_banned_log_time ON {$db_prefix}log_banned (log_time); - -# -# Table structure for table `log_boards` -# - -CREATE TABLE {$db_prefix}log_boards ( - id_member int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_board) -); - -# -# Sequence for table `log_comments` -# - -CREATE SEQUENCE {$db_prefix}log_comments_seq; - -# -# Table structure for table `log_comments` -# - -CREATE TABLE {$db_prefix}log_comments ( - id_comment int DEFAULT nextval('{$db_prefix}log_comments_seq'), - id_member int NOT NULL DEFAULT '0', - member_name varchar(80) NOT NULL DEFAULT '', - comment_type varchar(8) NOT NULL DEFAULT 'warning', - id_recipient int NOT NULL DEFAULT '0', - recipient_name varchar(255) NOT NULL DEFAULT '', - log_time bigint NOT NULL DEFAULT '0', - id_notice int NOT NULL DEFAULT '0', - counter smallint NOT NULL DEFAULT '0', - body text NOT NULL, - PRIMARY KEY (id_comment) -); - -# -# Indexes for table `log_comments` -# - -CREATE INDEX {$db_prefix}log_comments_id_recipient ON {$db_prefix}log_comments (id_recipient); -CREATE INDEX {$db_prefix}log_comments_log_time ON {$db_prefix}log_comments (log_time); -CREATE INDEX {$db_prefix}log_comments_comment_type ON {$db_prefix}log_comments (comment_type varchar_pattern_ops); - -# -# Table structure for table `log_digest` -# - -CREATE TABLE {$db_prefix}log_digest ( - id_topic int NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - note_type varchar(10) NOT NULL DEFAULT 'post', - daily smallint NOT NULL DEFAULT '0', - exclude int NOT NULL DEFAULT '0' -); - -# -# Sequence for table `log_errors` -# - -CREATE SEQUENCE {$db_prefix}log_errors_seq; - -# -# Table structure for table `log_errors` -# - -CREATE TABLE {$db_prefix}log_errors ( - id_error int DEFAULT nextval('{$db_prefix}log_errors_seq'), - log_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - ip inet, - url text NOT NULL, - message text NOT NULL, - session varchar(128) NOT NULL DEFAULT ' ', - error_type varchar(15) NOT NULL DEFAULT 'general', - file varchar(255) NOT NULL DEFAULT '', - line int NOT NULL DEFAULT '0', - backtrace text NOT NULL DEFAULT '', - PRIMARY KEY (id_error) -); - -# -# Indexes for table `log_errors` -# - -CREATE INDEX {$db_prefix}log_errors_log_time ON {$db_prefix}log_errors (log_time); -CREATE INDEX {$db_prefix}log_errors_id_member ON {$db_prefix}log_errors (id_member); -CREATE INDEX {$db_prefix}log_errors_ip ON {$db_prefix}log_errors (ip); - -# -# Table structure for table `log_floodcontrol` -# - -CREATE UNLOGGED TABLE {$db_prefix}log_floodcontrol ( - ip inet, - log_time bigint NOT NULL DEFAULT '0', - log_type varchar(30) NOT NULL DEFAULT 'post', - PRIMARY KEY (ip, log_type) -); - -# -# Sequence for table `log_group_requests` -# - -CREATE SEQUENCE {$db_prefix}log_group_requests_seq; - -# -# Table structure for table `log_group_requests` -# - -CREATE TABLE {$db_prefix}log_group_requests ( - id_request int DEFAULT nextval('{$db_prefix}log_group_requests_seq'), - id_member int NOT NULL DEFAULT '0', - id_group smallint NOT NULL DEFAULT '0', - time_applied bigint NOT NULL DEFAULT '0', - reason text NOT NULL, - status smallint NOT NULL DEFAULT '0', - id_member_acted int NOT NULL DEFAULT '0', - member_name_acted varchar(255) NOT NULL DEFAULT '', - time_acted bigint NOT NULL DEFAULT '0', - act_reason text NOT NULL, - PRIMARY KEY (id_request) -); - -# -# Indexes for table `log_group_requests` -# - -CREATE INDEX {$db_prefix}log_group_requests_id_member ON {$db_prefix}log_group_requests (id_member, id_group); - -# -# Table structure for table `log_mark_read` -# - -CREATE TABLE {$db_prefix}log_mark_read ( - id_member int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_board) -); - -# -# Sequence for table `log_member_notices` -# - -CREATE SEQUENCE {$db_prefix}log_member_notices_seq; - -# -# Table structure for table `log_member_notices` -# - -CREATE TABLE {$db_prefix}log_member_notices ( - id_notice int DEFAULT nextval('{$db_prefix}log_member_notices_seq'), - subject varchar(255) NOT NULL DEFAULT '', - body text NOT NULL, - PRIMARY KEY (id_notice) -); - -# -# Table structure for table `log_notify` -# - -CREATE TABLE {$db_prefix}log_notify ( - id_member int NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - sent smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_topic, id_board) -); - -# -# Indexes for table `log_notify` -# - -CREATE INDEX {$db_prefix}log_notify_id_topic ON {$db_prefix}log_notify (id_topic, id_member); -CREATE INDEX {$db_prefix}log_notify_id_board ON {$db_prefix}log_notify (id_board); - -# -# Table structure for table `log_online` -# - -CREATE UNLOGGED TABLE {$db_prefix}log_online ( - session varchar(128) NOT NULL DEFAULT '', - log_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_spider smallint NOT NULL DEFAULT '0', - ip inet, - url varchar(2048) NOT NULL DEFAULT '', - PRIMARY KEY (session) -); - -# -# Indexes for table `log_online` -# - -CREATE INDEX {$db_prefix}log_online_log_time ON {$db_prefix}log_online (log_time); -CREATE INDEX {$db_prefix}log_online_id_member ON {$db_prefix}log_online (id_member); - -# -# Sequence for table `log_packages` -# - -CREATE SEQUENCE {$db_prefix}log_packages_seq; - -# -# Table structure for table `log_packages` -# - -CREATE TABLE {$db_prefix}log_packages ( - id_install int DEFAULT nextval('{$db_prefix}log_packages_seq'), - filename varchar(255) NOT NULL DEFAULT '', - package_id varchar(255) NOT NULL DEFAULT '', - name varchar(255) NOT NULL DEFAULT '', - version varchar(255) NOT NULL DEFAULT '', - id_member_installed int NOT NULL DEFAULT '0', - member_installed varchar(255) NOT NULL, - time_installed int NOT NULL DEFAULT '0', - id_member_removed int NOT NULL DEFAULT '0', - member_removed varchar(255) NOT NULL, - time_removed int NOT NULL DEFAULT '0', - install_state smallint NOT NULL DEFAULT '1', - failed_steps text NOT NULL, - themes_installed varchar(255) NOT NULL DEFAULT '', - db_changes text NOT NULL, - credits text NOT NULL, - sha256_hash TEXT, - smf_version varchar(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_install) -); - -# -# Indexes for table `log_packages` -# - -CREATE INDEX {$db_prefix}log_packages_filename ON {$db_prefix}log_packages (filename varchar_pattern_ops); - -# -# Table structure for table `log_polls` -# - -CREATE TABLE {$db_prefix}log_polls ( - id_poll int NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_choice smallint NOT NULL DEFAULT '0' -); - -# -# Indexes for table `log_polls` -# - -CREATE INDEX {$db_prefix}log_polls_id_poll ON {$db_prefix}log_polls (id_poll, id_member, id_choice); - -# -# Sequence for table `log_reported` -# - -CREATE SEQUENCE {$db_prefix}log_reported_seq; - -# -# Table structure for table `log_reported` -# - -CREATE TABLE {$db_prefix}log_reported ( - id_report int DEFAULT nextval('{$db_prefix}log_reported_seq'), - id_msg bigint NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - membername varchar(255) NOT NULL DEFAULT '', - subject varchar(255) NOT NULL DEFAULT '', - body text NOT NULL, - time_started int NOT NULL DEFAULT '0', - time_updated int NOT NULL DEFAULT '0', - num_reports int NOT NULL DEFAULT '0', - closed smallint NOT NULL DEFAULT '0', - ignore_all smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_report) -); - -# -# Indexes for table `log_reported` -# - -CREATE INDEX {$db_prefix}log_reported_id_member ON {$db_prefix}log_reported (id_member); -CREATE INDEX {$db_prefix}log_reported_id_topic ON {$db_prefix}log_reported (id_topic); -CREATE INDEX {$db_prefix}log_reported_closed ON {$db_prefix}log_reported (closed); -CREATE INDEX {$db_prefix}log_reported_time_started ON {$db_prefix}log_reported (time_started); -CREATE INDEX {$db_prefix}log_reported_id_msg ON {$db_prefix}log_reported (id_msg); - -# -# Sequence for table `log_reported_comments` -# - -CREATE SEQUENCE {$db_prefix}log_reported_comments_seq; - -# -# Table structure for table `log_reported_comments` -# - -CREATE TABLE {$db_prefix}log_reported_comments ( - id_comment int DEFAULT nextval('{$db_prefix}log_reported_comments_seq'), - id_report int NOT NULL DEFAULT '0', - id_member int NOT NULL, - membername varchar(255) NOT NULL DEFAULT '', - member_ip inet, - comment varchar(255) NOT NULL DEFAULT '', - time_sent int NOT NULL, - PRIMARY KEY (id_comment) -); - -# -# Indexes for table `log_reported_comments` -# - -CREATE INDEX {$db_prefix}log_reported_comments_id_report ON {$db_prefix}log_reported_comments (id_report); -CREATE INDEX {$db_prefix}log_reported_comments_id_member ON {$db_prefix}log_reported_comments (id_member); -CREATE INDEX {$db_prefix}log_reported_comments_time_sent ON {$db_prefix}log_reported_comments (time_sent); - -# -# Sequence for table `log_scheduled_tasks` -# - -CREATE SEQUENCE {$db_prefix}log_scheduled_tasks_seq; - -# -# Table structure for table `log_scheduled_tasks` -# - -CREATE TABLE {$db_prefix}log_scheduled_tasks ( - id_log int DEFAULT nextval('{$db_prefix}log_scheduled_tasks_seq'), - id_task smallint NOT NULL DEFAULT '0', - time_run int NOT NULL DEFAULT '0', - time_taken float NOT NULL DEFAULT '0', - PRIMARY KEY (id_log) -); - -# -# Table structure for table `log_search_messages` -# - -CREATE TABLE {$db_prefix}log_search_messages ( - id_search smallint NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_search, id_msg) -); - -# -# Table structure for table `log_search_results` -# - -CREATE TABLE {$db_prefix}log_search_results ( - id_search smallint NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - relevance smallint NOT NULL DEFAULT '0', - num_matches smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_search, id_topic, id_msg) -); - -# -# Table structure for table `log_search_subjects` -# - -CREATE TABLE {$db_prefix}log_search_subjects ( - word varchar(20) NOT NULL DEFAULT '', - id_topic int NOT NULL DEFAULT '0', - PRIMARY KEY (word, id_topic) -); - -# -# Indexes for table `log_search_subjects` -# - -CREATE INDEX {$db_prefix}log_search_subjects_id_topic ON {$db_prefix}log_search_subjects (id_topic); - -# -# Table structure for table `log_search_topics` -# - -CREATE TABLE {$db_prefix}log_search_topics ( - id_search smallint NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - PRIMARY KEY (id_search, id_topic) -); - -# -# Sequence for table `log_spider_hits` -# - -CREATE SEQUENCE {$db_prefix}log_spider_hits_seq; - -# -# Table structure for table `log_spider_hits` -# - -CREATE TABLE {$db_prefix}log_spider_hits ( - id_hit bigint DEFAULT nextval('{$db_prefix}log_spider_hits_seq'), - id_spider smallint NOT NULL DEFAULT '0', - log_time bigint NOT NULL DEFAULT '0', - url varchar(1024) NOT NULL DEFAULT '', - processed smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_hit) -); - -# -# Indexes for table `log_spider_hits` -# - -CREATE INDEX {$db_prefix}log_spider_hits_id_spider ON {$db_prefix}log_spider_hits (id_spider); -CREATE INDEX {$db_prefix}log_spider_hits_log_time ON {$db_prefix}log_spider_hits (log_time); -CREATE INDEX {$db_prefix}log_spider_hits_processed ON {$db_prefix}log_spider_hits (processed); - -# -# Table structure for table `log_spider_stats` -# - -CREATE TABLE {$db_prefix}log_spider_stats ( - id_spider smallint NOT NULL DEFAULT '0', - page_hits int NOT NULL DEFAULT '0', - last_seen bigint NOT NULL DEFAULT '0', - stat_date date NOT NULL DEFAULT '1004-01-01', - PRIMARY KEY (stat_date, id_spider) -); - -# -# Sequence for table `log_subscribed` -# - -CREATE SEQUENCE {$db_prefix}log_subscribed_seq; - -# -# Table structure for table `log_subscribed` -# - -CREATE TABLE {$db_prefix}log_subscribed ( - id_sublog bigint DEFAULT nextval('{$db_prefix}log_subscribed_seq'), - id_subscribe smallint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - old_id_group int NOT NULL DEFAULT '0', - start_time int NOT NULL DEFAULT '0', - end_time int NOT NULL DEFAULT '0', - payments_pending smallint NOT NULL DEFAULT '0', - status smallint NOT NULL DEFAULT '0', - pending_details text NOT NULL, - reminder_sent smallint NOT NULL DEFAULT '0', - vendor_ref varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_sublog) -); - -# -# Indexes for table `log_subscribed` -# - -CREATE INDEX {$db_prefix}log_subscribed_id_subscribe ON {$db_prefix}log_subscribed (id_subscribe, id_member); -CREATE INDEX {$db_prefix}log_subscribed_end_time ON {$db_prefix}log_subscribed (end_time); -CREATE INDEX {$db_prefix}log_subscribed_reminder_sent ON {$db_prefix}log_subscribed (reminder_sent); -CREATE INDEX {$db_prefix}log_subscribed_payments_pending ON {$db_prefix}log_subscribed (payments_pending); -CREATE INDEX {$db_prefix}log_subscribed_status ON {$db_prefix}log_subscribed (status); -CREATE INDEX {$db_prefix}log_subscribed_id_member ON {$db_prefix}log_subscribed (id_member); - -# -# Table structure for table `log_topics` -# - -CREATE TABLE {$db_prefix}log_topics ( - id_member int NOT NULL DEFAULT '0', - id_topic int NOT NULL DEFAULT '0', - id_msg bigint NOT NULL DEFAULT '0', - unwatched int NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, id_topic) -); - -# -# Indexes for table `log_topics` -# - -CREATE INDEX {$db_prefix}log_topics_id_topic ON {$db_prefix}log_topics (id_topic); - -# -# Sequence for table `mail_queue` -# - -CREATE SEQUENCE {$db_prefix}mail_queue_seq; - -# -# Table structure for table `mail_queue` -# - -CREATE TABLE {$db_prefix}mail_queue ( - id_mail bigint DEFAULT nextval('{$db_prefix}mail_queue_seq'), - time_sent int NOT NULL DEFAULT '0', - recipient varchar(255) NOT NULL DEFAULT '', - body text NOT NULL, - subject varchar(255) NOT NULL DEFAULT '', - headers text NOT NULL, - send_html smallint NOT NULL DEFAULT '0', - priority smallint NOT NULL DEFAULT '1', - private smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_mail) -); - -# -# Indexes for table `mail_queue` -# - -CREATE INDEX {$db_prefix}mail_queue_time_sent ON {$db_prefix}mail_queue (time_sent); -CREATE INDEX {$db_prefix}mail_queue_mail_priority ON {$db_prefix}mail_queue (priority, id_mail); - -# -# Sequence for table `membergroups` -# - -CREATE SEQUENCE {$db_prefix}membergroups_seq START WITH 9; - -# -# Table structure for table `membergroups` -# - -CREATE TABLE {$db_prefix}membergroups ( - id_group smallint DEFAULT nextval('{$db_prefix}membergroups_seq'), - group_name varchar(80) NOT NULL DEFAULT '', - description text NOT NULL, - online_color varchar(20) NOT NULL DEFAULT '', - min_posts int NOT NULL DEFAULT '-1', - max_messages smallint NOT NULL DEFAULT '0', - icons varchar(255) NOT NULL DEFAULT '', - group_type smallint NOT NULL DEFAULT '0', - hidden smallint NOT NULL DEFAULT '0', - id_parent smallint NOT NULL DEFAULT '-2', - tfa_required smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_group) -); - -# -# Indexes for table `membergroups` -# - -CREATE INDEX {$db_prefix}membergroups_min_posts ON {$db_prefix}membergroups (min_posts); - -# -# Sequence for table `members` -# - -CREATE SEQUENCE {$db_prefix}members_seq; - -# -# Table structure for table `members` -# - -CREATE TABLE {$db_prefix}members ( - id_member int DEFAULT nextval('{$db_prefix}members_seq'), - member_name varchar(80) NOT NULL DEFAULT '', - date_registered bigint NOT NULL DEFAULT '0', - posts int NOT NULL DEFAULT '0', - id_group smallint NOT NULL DEFAULT '0', - lngfile varchar(255) NOT NULL DEFAULT '', - last_login bigint NOT NULL DEFAULT '0', - real_name varchar(255) NOT NULL DEFAULT '', - instant_messages smallint NOT NULL DEFAULT 0, - unread_messages smallint NOT NULL DEFAULT 0, - new_pm smallint NOT NULL DEFAULT '0', - alerts bigint NOT NULL DEFAULT '0', - buddy_list text NOT NULL, - pm_ignore_list TEXT NULL, - pm_prefs int NOT NULL DEFAULT '0', - mod_prefs varchar(20) NOT NULL DEFAULT '', - passwd varchar(64) NOT NULL DEFAULT '', - email_address varchar(255) NOT NULL DEFAULT '', - personal_text varchar(255) NOT NULL DEFAULT '', - birthdate date NOT NULL DEFAULT '1004-01-01', - website_title varchar(255) NOT NULL DEFAULT '', - website_url varchar(255) NOT NULL DEFAULT '', - show_online smallint NOT NULL DEFAULT '1', - time_format varchar(80) NOT NULL DEFAULT '', - signature text NOT NULL, - time_offset float NOT NULL DEFAULT '0', - avatar varchar(255) NOT NULL DEFAULT '', - usertitle varchar(255) NOT NULL DEFAULT '', - member_ip inet, - member_ip2 inet, - secret_question varchar(255) NOT NULL DEFAULT '', - secret_answer varchar(64) NOT NULL DEFAULT '', - id_theme smallint NOT NULL DEFAULT '0', - is_activated smallint NOT NULL DEFAULT '1', - validation_code varchar(10) NOT NULL DEFAULT '', - id_msg_last_visit int NOT NULL DEFAULT '0', - additional_groups varchar(255) NOT NULL DEFAULT '', - smiley_set varchar(48) NOT NULL DEFAULT '', - id_post_group smallint NOT NULL DEFAULT '0', - total_time_logged_in bigint NOT NULL DEFAULT '0', - password_salt varchar(255) NOT NULL DEFAULT '', - ignore_boards text NOT NULL, - warning smallint NOT NULL DEFAULT '0', - passwd_flood varchar(12) NOT NULL DEFAULT '', - pm_receive_from smallint NOT NULL DEFAULT '1', - timezone varchar(80) NOT NULL DEFAULT '', - tfa_secret varchar(24) NOT NULL DEFAULT '', - tfa_backup varchar(64) NOT NULL DEFAULT '', - spoofdetector_name VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_member) -); - -# -# Indexes for table `members` -# - -CREATE INDEX {$db_prefix}members_member_name ON {$db_prefix}members (member_name varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_real_name ON {$db_prefix}members (real_name varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_email_address ON {$db_prefix}members (email_address varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_date_registered ON {$db_prefix}members (date_registered); -CREATE INDEX {$db_prefix}members_id_group ON {$db_prefix}members (id_group); -CREATE INDEX {$db_prefix}members_birthdate ON {$db_prefix}members (birthdate); -CREATE INDEX {$db_prefix}members_birthdate2 ON {$db_prefix}members (indexable_month_day(birthdate)); -CREATE INDEX {$db_prefix}members_posts ON {$db_prefix}members (posts); -CREATE INDEX {$db_prefix}members_last_login ON {$db_prefix}members (last_login); -CREATE INDEX {$db_prefix}members_lngfile ON {$db_prefix}members (lngfile varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_id_post_group ON {$db_prefix}members (id_post_group); -CREATE INDEX {$db_prefix}members_warning ON {$db_prefix}members (warning); -CREATE INDEX {$db_prefix}members_total_time_logged_in ON {$db_prefix}members (total_time_logged_in); -CREATE INDEX {$db_prefix}members_id_theme ON {$db_prefix}members (id_theme); -CREATE INDEX {$db_prefix}members_member_name_low ON {$db_prefix}members (LOWER(member_name) varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_real_name_low ON {$db_prefix}members (LOWER(real_name) varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_active_real_name ON {$db_prefix}members (is_activated, real_name); -CREATE INDEX {$db_prefix}idx_spoofdetector_name ON {$db_prefix}members (spoofdetector_name); -CREATE INDEX {$db_prefix}idx_spoofdetector_name_id ON {$db_prefix}members (spoofdetector_name, id_member); - -# -# Sequence for table `member_logins` -# - -CREATE SEQUENCE {$db_prefix}member_logins_seq; - -# -# Table structure for table `member_logins` -# - -CREATE TABLE {$db_prefix}member_logins ( - id_login int DEFAULT nextval('{$db_prefix}member_logins_seq'), - id_member int NOT NULL DEFAULT '0', - time int NOT NULL DEFAULT '0', - ip inet, - ip2 inet, - PRIMARY KEY (id_login) -); - -# -# Indexes for table `member_logins` -# -CREATE INDEX {$db_prefix}member_logins_id_member ON {$db_prefix}member_logins (id_member); -CREATE INDEX {$db_prefix}member_logins_time ON {$db_prefix}member_logins (time); - - -# -# Sequence for table `message_icons` -# - -CREATE SEQUENCE {$db_prefix}message_icons_seq; - -# -# Table structure for table `message_icons` -# - -CREATE TABLE {$db_prefix}message_icons ( - id_icon smallint DEFAULT nextval('{$db_prefix}message_icons_seq'), - title varchar(80) NOT NULL DEFAULT '', - filename varchar(80) NOT NULL DEFAULT '', - id_board smallint NOT NULL DEFAULT '0', - icon_order smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_icon) -); - -# -# Indexes for table `message_icons` -# - -CREATE INDEX {$db_prefix}message_icons_id_board ON {$db_prefix}message_icons (id_board); - -# -# Sequence for table `messages` -# - -CREATE SEQUENCE {$db_prefix}messages_seq START WITH 2; - -# -# Table structure for table `messages` -# - -CREATE TABLE {$db_prefix}messages ( - id_msg bigint DEFAULT nextval('{$db_prefix}messages_seq'), - id_topic int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - poster_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_msg_modified int NOT NULL DEFAULT '0', - subject varchar(255) NOT NULL DEFAULT '', - poster_name varchar(255) NOT NULL DEFAULT '', - poster_email varchar(255) NOT NULL DEFAULT '', - poster_ip inet, - smileys_enabled smallint NOT NULL DEFAULT '1', - modified_time int NOT NULL DEFAULT '0', - modified_name varchar(255) NOT NULL, - modified_reason varchar(255) NOT NULL DEFAULT '', - body text NOT NULL, - icon varchar(16) NOT NULL DEFAULT 'xx', - approved smallint NOT NULL DEFAULT '1', - likes smallint NOT NULL DEFAULT '0', - version varchar(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_msg) -); - -# -# Indexes for table `messages` -# - -CREATE UNIQUE INDEX {$db_prefix}messages_id_board ON {$db_prefix}messages (id_board, id_msg, approved); -CREATE UNIQUE INDEX {$db_prefix}messages_id_member ON {$db_prefix}messages (id_member, id_msg); -CREATE INDEX {$db_prefix}messages_ip_index ON {$db_prefix}messages (poster_ip, id_topic); -CREATE INDEX {$db_prefix}messages_participation ON {$db_prefix}messages (id_member, id_topic); -CREATE INDEX {$db_prefix}messages_show_posts ON {$db_prefix}messages (id_member, id_board); -CREATE INDEX {$db_prefix}messages_id_member_msg ON {$db_prefix}messages (id_member, approved, id_msg); -CREATE INDEX {$db_prefix}messages_current_topic ON {$db_prefix}messages (id_topic, id_msg, id_member, approved); -CREATE INDEX {$db_prefix}messages_related_ip ON {$db_prefix}messages (id_member, poster_ip, id_msg); -CREATE INDEX {$db_prefix}messages_likes ON {$db_prefix}messages (likes); -# -# Table structure for table `moderators` -# - -CREATE TABLE {$db_prefix}moderators ( - id_board smallint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - PRIMARY KEY (id_board, id_member) -); - -# -# Table structure for table `moderator_groups` -# - -CREATE TABLE {$db_prefix}moderator_groups ( - id_board smallint NOT NULL DEFAULT '0', - id_group smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_board, id_group) -); - -# -# Sequence for table `package_servers` -# - -CREATE SEQUENCE {$db_prefix}package_servers_seq; - -# -# Table structure for table `package_servers` -# - -CREATE TABLE {$db_prefix}package_servers ( - id_server smallint DEFAULT nextval('{$db_prefix}package_servers_seq'), - name varchar(255) NOT NULL DEFAULT '', - url varchar(255) NOT NULL DEFAULT '', - validation_url varchar(255) NOT NULL DEFAULT '', - extra text, - PRIMARY KEY (id_server) -); - - -# -# Sequence for table `permission_profiles` -# - -CREATE SEQUENCE {$db_prefix}permission_profiles_seq START WITH 5; - -# -# Table structure for table `permission_profiles` -# - -CREATE TABLE {$db_prefix}permission_profiles ( - id_profile smallint DEFAULT nextval('{$db_prefix}permission_profiles_seq'), - profile_name varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_profile) -); - -# -# Table structure for table `permissions` -# - -CREATE TABLE {$db_prefix}permissions ( - id_group smallint NOT NULL DEFAULT '0', - permission varchar(30) NOT NULL DEFAULT '', - add_deny smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_group, permission) -); - -# -# Sequence for table `personal_messages` -# - -CREATE SEQUENCE {$db_prefix}personal_messages_seq; - -# -# Table structure for table `personal_messages` -# - -CREATE TABLE {$db_prefix}personal_messages ( - id_pm bigint DEFAULT nextval('{$db_prefix}personal_messages_seq'), - id_pm_head bigint NOT NULL DEFAULT '0', - id_member_from int NOT NULL DEFAULT '0', - deleted_by_sender smallint NOT NULL DEFAULT '0', - from_name varchar(255) NOT NULL, - msgtime bigint NOT NULL DEFAULT '0', - subject varchar(255) NOT NULL DEFAULT '', - body text NOT NULL, - version varchar(5) NOT NULL DEFAULT '', - PRIMARY KEY (id_pm) -); - -# -# Indexes for table `personal_messages` -# - -CREATE INDEX {$db_prefix}personal_messages_id_member ON {$db_prefix}personal_messages (id_member_from, deleted_by_sender); -CREATE INDEX {$db_prefix}personal_messages_msgtime ON {$db_prefix}personal_messages (msgtime); -CREATE INDEX {$db_prefix}personal_messages_id_pm_head ON {$db_prefix}personal_messages (id_pm_head); - -# -# Sequence for table `pm_labels` -# - -CREATE SEQUENCE {$db_prefix}pm_labels_seq; - -# -# Table structure for table `pm_labels` -# - -CREATE TABLE {$db_prefix}pm_labels ( - id_label bigint NOT NULL DEFAULT nextval('{$db_prefix}pm_labels_seq'), - id_member int NOT NULL DEFAULT '0', - name varchar(30) NOT NULL DEFAULT '', - PRIMARY KEY (id_label) -); - -# -# Table structure for table `pm_labeled_messages` -# - -CREATE TABLE {$db_prefix}pm_labeled_messages ( - id_label bigint NOT NULL DEFAULT '0', - id_pm bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_label, id_pm) -); - -# -# Table structure for table `pm_recipients` -# - -CREATE TABLE {$db_prefix}pm_recipients ( - id_pm bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - bcc smallint NOT NULL DEFAULT '0', - is_read smallint NOT NULL DEFAULT '0', - is_new smallint NOT NULL DEFAULT '0', - deleted smallint NOT NULL DEFAULT '0', - in_inbox smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_pm, id_member) -); - -# -# Indexes for table `pm_recipients` -# - -CREATE UNIQUE INDEX {$db_prefix}pm_recipients_id_member ON {$db_prefix}pm_recipients (id_member, deleted, id_pm); - -# -# Sequence for table `pm_rules` -# - -CREATE SEQUENCE {$db_prefix}pm_rules_seq; - -# -# Table structure for table `pm_rules` -# - -CREATE TABLE {$db_prefix}pm_rules ( - id_rule bigint DEFAULT nextval('{$db_prefix}pm_rules_seq'), - id_member int NOT NULL DEFAULT '0', - rule_name varchar(60) NOT NULL, - criteria text NOT NULL, - actions text NOT NULL, - delete_pm smallint NOT NULL DEFAULT '0', - is_or smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_rule) -); - -# -# Indexes for table `pm_rules` -# - -CREATE INDEX {$db_prefix}pm_rules_id_member ON {$db_prefix}pm_rules (id_member); -CREATE INDEX {$db_prefix}pm_rules_delete_pm ON {$db_prefix}pm_rules (delete_pm); - -# -# Sequence for table `polls` -# - -CREATE SEQUENCE {$db_prefix}polls_seq; - -# -# Table structure for table `polls` -# - -CREATE TABLE {$db_prefix}polls ( - id_poll int DEFAULT nextval('{$db_prefix}polls_seq'), - question varchar(255) NOT NULL DEFAULT '', - voting_locked smallint NOT NULL DEFAULT '0', - max_votes smallint NOT NULL DEFAULT '1', - expire_time int NOT NULL DEFAULT '0', - hide_results smallint NOT NULL DEFAULT '0', - change_vote smallint NOT NULL DEFAULT '0', - guest_vote smallint NOT NULL DEFAULT '0', - num_guest_voters int NOT NULL DEFAULT '0', - reset_poll int NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - poster_name varchar(255) NOT NULL, - PRIMARY KEY (id_poll) -); - -# -# Table structure for table `poll_choices` -# - -CREATE TABLE {$db_prefix}poll_choices ( - id_poll int NOT NULL DEFAULT '0', - id_choice smallint NOT NULL DEFAULT '0', - label varchar(255) NOT NULL DEFAULT '', - votes smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_poll, id_choice) -); - -# -# Sequence for table `qanda` -# - -CREATE SEQUENCE {$db_prefix}qanda_seq; - -# -# Table structure for table `qanda` -# - -CREATE TABLE {$db_prefix}qanda ( - id_question smallint DEFAULT nextval('{$db_prefix}qanda_seq'), - lngfile varchar(255) NOT NULL DEFAULT '', - question varchar(255) NOT NULL DEFAULT '', - answers text NOT NULL, - PRIMARY KEY (id_question) -); - -# -# Indexes for table `qanda` -# - -CREATE INDEX {$db_prefix}qanda_lngfile ON {$db_prefix}qanda (lngfile varchar_pattern_ops); - -# -# Sequence for table `scheduled_tasks` -# - -CREATE SEQUENCE {$db_prefix}scheduled_tasks_seq START WITH 14; - -# -# Table structure for table `scheduled_tasks` -# - -CREATE TABLE {$db_prefix}scheduled_tasks ( - id_task smallint DEFAULT nextval('{$db_prefix}scheduled_tasks_seq'), - next_time int NOT NULL DEFAULT '0', - time_offset int NOT NULL DEFAULT '0', - time_regularity smallint NOT NULL DEFAULT '0', - time_unit varchar(1) NOT NULL DEFAULT 'h', - disabled smallint NOT NULL DEFAULT '0', - task varchar(24) NOT NULL DEFAULT '', - callable varchar(60) NOT NULL DEFAULT '', - PRIMARY KEY (id_task) -); - -# -# Indexes for table `scheduled_tasks` -# - -CREATE INDEX {$db_prefix}scheduled_tasks_next_time ON {$db_prefix}scheduled_tasks (next_time); -CREATE INDEX {$db_prefix}scheduled_tasks_disabled ON {$db_prefix}scheduled_tasks (disabled); -CREATE UNIQUE INDEX {$db_prefix}scheduled_tasks_task ON {$db_prefix}scheduled_tasks (task varchar_pattern_ops); - -# -# Table structure for table `settings` -# - -CREATE TABLE {$db_prefix}settings ( - variable varchar(255) NOT NULL DEFAULT '', - value text NOT NULL, - PRIMARY KEY (variable) -); - -# -# Table structure for table `sessions` -# - -CREATE UNLOGGED TABLE {$db_prefix}sessions ( - session_id varchar(128) NOT NULL DEFAULT '', - last_update bigint NOT NULL DEFAULT '0', - data text NOT NULL, - PRIMARY KEY (session_id) -); - -# -# Sequence for table `smileys` -# - -CREATE SEQUENCE {$db_prefix}smileys_seq; - -# -# Table structure for table `smileys` -# - -CREATE TABLE {$db_prefix}smileys ( - id_smiley smallint DEFAULT nextval('{$db_prefix}smileys_seq'), - code varchar(30) NOT NULL DEFAULT '', - description varchar(80) NOT NULL DEFAULT '', - smiley_row smallint NOT NULL DEFAULT '0', - smiley_order smallint NOT NULL DEFAULT '0', - hidden smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_smiley) -); - -# -# Table structure for table `smiley_files` -# - -CREATE TABLE {$db_prefix}smiley_files -( - id_smiley SMALLINT NOT NULL DEFAULT '0', - smiley_set VARCHAR(48) NOT NULL DEFAULT '', - filename VARCHAR(48) NOT NULL DEFAULT '', - PRIMARY KEY (id_smiley, smiley_set) -); - -# -# Sequence for table `spiders` -# - -CREATE SEQUENCE {$db_prefix}spiders_seq; - -# -# Table structure for table `spiders` -# - -CREATE TABLE {$db_prefix}spiders ( - id_spider smallint NOT NULL DEFAULT nextval('{$db_prefix}spiders_seq'), - spider_name varchar(255) NOT NULL DEFAULT '', - user_agent varchar(255) NOT NULL DEFAULT '', - ip_info varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_spider) -); - -# -# Sequence for table `subscriptions` -# - -CREATE SEQUENCE {$db_prefix}subscriptions_seq; - -# -# Table structure for table `subscriptions` -# - -CREATE TABLE {$db_prefix}subscriptions( - id_subscribe int NOT NULL DEFAULT nextval('{$db_prefix}subscriptions_seq'), - name varchar(60) NOT NULL DEFAULT '', - description varchar(255) NOT NULL DEFAULT '', - cost text NOT NULL, - length varchar(6) NOT NULL DEFAULT '', - id_group int NOT NULL DEFAULT '0', - add_groups varchar(40) NOT NULL DEFAULT '', - active smallint NOT NULL DEFAULT '1', - repeatable smallint NOT NULL DEFAULT '0', - allow_partial smallint NOT NULL DEFAULT '0', - reminder smallint NOT NULL DEFAULT '0', - email_complete text NOT NULL, - PRIMARY KEY (id_subscribe) -); - -# -# Indexes for table `subscriptions` -# - -CREATE INDEX {$db_prefix}subscriptions_active ON {$db_prefix}subscriptions (active); - -# -# Table structure for table `themes` -# - -CREATE TABLE {$db_prefix}themes ( - id_member int DEFAULT '0', - id_theme smallint DEFAULT '1', - variable varchar(255) DEFAULT '', - value text NOT NULL, - PRIMARY KEY (id_theme, id_member, variable) -); - -# -# Indexes for table `themes` -# - -CREATE INDEX {$db_prefix}themes_id_member ON {$db_prefix}themes (id_member); - -# -# Sequence for table `topics` -# - -CREATE SEQUENCE {$db_prefix}topics_seq START WITH 2; - -# -# Table structure for table `topics` -# - -CREATE TABLE {$db_prefix}topics ( - id_topic int DEFAULT nextval('{$db_prefix}topics_seq'), - is_sticky smallint NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_first_msg int NOT NULL DEFAULT '0', - id_last_msg bigint NOT NULL DEFAULT '0', - id_member_started int NOT NULL DEFAULT '0', - id_member_updated int NOT NULL DEFAULT '0', - id_poll int NOT NULL DEFAULT '0', - id_previous_board smallint NOT NULL DEFAULT '0', - id_previous_topic int NOT NULL DEFAULT '0', - num_replies bigint NOT NULL DEFAULT '0', - num_views bigint NOT NULL DEFAULT '0', - locked smallint NOT NULL DEFAULT '0', - redirect_expires int NOT NULL DEFAULT '0', - id_redirect_topic bigint NOT NULL DEFAULT '0', - unapproved_posts smallint NOT NULL DEFAULT '0', - approved smallint NOT NULL DEFAULT '1', - PRIMARY KEY (id_topic) -); - -# -# Indexes for table `topics` -# - -CREATE UNIQUE INDEX {$db_prefix}topics_last_message ON {$db_prefix}topics (id_last_msg, id_board); -CREATE UNIQUE INDEX {$db_prefix}topics_first_message ON {$db_prefix}topics (id_first_msg, id_board); -CREATE UNIQUE INDEX {$db_prefix}topics_poll ON {$db_prefix}topics (id_poll, id_topic); -CREATE INDEX {$db_prefix}topics_is_sticky ON {$db_prefix}topics (is_sticky); -CREATE INDEX {$db_prefix}topics_approved ON {$db_prefix}topics (approved); -CREATE INDEX {$db_prefix}topics_member_started ON {$db_prefix}topics (id_member_started, id_board); -CREATE INDEX {$db_prefix}topics_last_message_sticky ON {$db_prefix}topics (id_board, is_sticky, id_last_msg); -CREATE INDEX {$db_prefix}topics_board_news ON {$db_prefix}topics (id_board, id_first_msg); - -# -# Sequence for table `user_alerts` -# - -CREATE SEQUENCE {$db_prefix}user_alerts_seq; - -# -# Table structure for table `user_alerts` -# - -CREATE TABLE {$db_prefix}user_alerts ( - id_alert bigint DEFAULT nextval('{$db_prefix}user_alerts_seq'), - alert_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_member_started bigint NOT NULL DEFAULT '0', - member_name varchar(255) NOT NULL DEFAULT '', - content_type varchar(255) NOT NULL DEFAULT '', - content_id bigint NOT NULL DEFAULT '0', - content_action varchar(255) NOT NULL DEFAULT '', - is_read bigint NOT NULL DEFAULT '0', - extra text NOT NULL, - PRIMARY KEY (id_alert) -); - -# -# Indexes for table `user_alerts` -# - -CREATE INDEX {$db_prefix}user_alerts_id_member ON {$db_prefix}user_alerts (id_member); -CREATE INDEX {$db_prefix}user_alerts_alert_time ON {$db_prefix}user_alerts (alert_time); - -# -# Table structure for table `user_alerts_prefs` -# - -CREATE TABLE {$db_prefix}user_alerts_prefs ( - id_member int NOT NULL DEFAULT '0', - alert_pref varchar(32) NOT NULL DEFAULT '', - alert_value smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, alert_pref) -); - -# -# Sequence for table `user_drafts` -# - -CREATE SEQUENCE {$db_prefix}user_drafts_seq; - -# -# Table structure for table `user_drafts` -# - -CREATE TABLE {$db_prefix}user_drafts ( - id_draft bigint DEFAULT nextval('{$db_prefix}user_drafts_seq'), - id_topic int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_reply bigint NOT NULL DEFAULT '0', - type smallint NOT NULL DEFAULT '0', - poster_time int NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - subject varchar(255) NOT NULL DEFAULT '', - smileys_enabled smallint NOT NULL DEFAULT '1', - body text NOT NULL, - icon varchar(16) NOT NULL DEFAULT 'xx', - locked smallint NOT NULL DEFAULT '0', - is_sticky smallint NOT NULL DEFAULT '0', - to_list varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_draft) -); - -# -# Indexes for table `user_drafts` -# - -CREATE UNIQUE INDEX {$db_prefix}user_drafts_id_member ON {$db_prefix}user_drafts (id_member, id_draft, type); - -# -# Table structure for table `user_likes` -# - -CREATE TABLE {$db_prefix}user_likes ( - id_member int NOT NULL DEFAULT '0', - content_type char(6) DEFAULT '', - content_id int NOT NULL DEFAULT '0', - like_time int NOT NULL DEFAULT '0', - PRIMARY KEY (content_id, content_type, id_member) -); - -# -# Indexes for table `user_likes` -# - -CREATE INDEX {$db_prefix}user_likes_content ON {$db_prefix}user_likes (content_id, content_type); -CREATE INDEX {$db_prefix}user_likes_liker ON {$db_prefix}user_likes (id_member); - -# -# Table structure for `mentions` -# -CREATE TABLE {$db_prefix}mentions ( - content_id int DEFAULT '0', - content_type varchar(10) DEFAULT '', - id_mentioned int DEFAULT 0, - id_member int NOT NULL DEFAULT 0, - time int NOT NULL DEFAULT 0, - PRIMARY KEY (content_id, content_type, id_mentioned) -); - -# -# Indexes for table `mentions` -# -CREATE INDEX {$db_prefix}mentions_content ON {$db_prefix}mentions (content_id, content_type); -CREATE INDEX {$db_prefix}mentions_mentionee ON {$db_prefix}mentions (id_member); - -# -# Yay for transactions... -# -BEGIN; - -# -# Dumping data for table `admin_info_files` -# - -INSERT INTO {$db_prefix}admin_info_files - (id_file, filename, path, parameters, data, filetype) -VALUES - (1, 'current-version.js', '/smf/', 'version=%3$s', '', 'text/javascript'), - (2, 'detailed-version.js', '/smf/', 'language=%1$s&version=%3$s', '', 'text/javascript'), - (3, 'latest-news.js', '/smf/', 'language=%1$s&format=%2$s', '', 'text/javascript'), - (4, 'latest-versions.txt', '/smf/', 'version=%3$s', '', 'text/plain'); -# -------------------------------------------------------- - -# -# Dumping data for table `board_permissions` -# - -INSERT INTO {$db_prefix}board_permissions - (id_group, id_profile, permission) -VALUES (-1, 1, 'poll_view'), - (0, 1, 'remove_own'), - (0, 1, 'lock_own'), - (0, 1, 'modify_own'), - (0, 1, 'poll_add_own'), - (0, 1, 'poll_edit_own'), - (0, 1, 'poll_lock_own'), - (0, 1, 'poll_post'), - (0, 1, 'poll_view'), - (0, 1, 'poll_vote'), - (0, 1, 'post_attachment'), - (0, 1, 'post_new'), - (0, 1, 'post_draft'), - (0, 1, 'post_reply_any'), - (0, 1, 'post_reply_own'), - (0, 1, 'post_unapproved_topics'), - (0, 1, 'post_unapproved_replies_any'), - (0, 1, 'post_unapproved_replies_own'), - (0, 1, 'post_unapproved_attachments'), - (0, 1, 'delete_own'), - (0, 1, 'report_any'), - (0, 1, 'view_attachments'), - (2, 1, 'moderate_board'), - (2, 1, 'post_new'), - (2, 1, 'post_draft'), - (2, 1, 'post_reply_own'), - (2, 1, 'post_reply_any'), - (2, 1, 'post_unapproved_topics'), - (2, 1, 'post_unapproved_replies_any'), - (2, 1, 'post_unapproved_replies_own'), - (2, 1, 'post_unapproved_attachments'), - (2, 1, 'poll_post'), - (2, 1, 'poll_add_any'), - (2, 1, 'poll_remove_any'), - (2, 1, 'poll_view'), - (2, 1, 'poll_vote'), - (2, 1, 'poll_lock_any'), - (2, 1, 'poll_edit_any'), - (2, 1, 'report_any'), - (2, 1, 'lock_own'), - (2, 1, 'delete_own'), - (2, 1, 'modify_own'), - (2, 1, 'make_sticky'), - (2, 1, 'lock_any'), - (2, 1, 'remove_any'), - (2, 1, 'move_any'), - (2, 1, 'merge_any'), - (2, 1, 'split_any'), - (2, 1, 'delete_any'), - (2, 1, 'modify_any'), - (2, 1, 'approve_posts'), - (2, 1, 'post_attachment'), - (2, 1, 'view_attachments'), - (3, 1, 'moderate_board'), - (3, 1, 'post_new'), - (3, 1, 'post_draft'), - (3, 1, 'post_reply_own'), - (3, 1, 'post_reply_any'), - (3, 1, 'post_unapproved_topics'), - (3, 1, 'post_unapproved_replies_any'), - (3, 1, 'post_unapproved_replies_own'), - (3, 1, 'post_unapproved_attachments'), - (3, 1, 'poll_post'), - (3, 1, 'poll_add_any'), - (3, 1, 'poll_remove_any'), - (3, 1, 'poll_view'), - (3, 1, 'poll_vote'), - (3, 1, 'poll_lock_any'), - (3, 1, 'poll_edit_any'), - (3, 1, 'report_any'), - (3, 1, 'lock_own'), - (3, 1, 'delete_own'), - (3, 1, 'modify_own'), - (3, 1, 'make_sticky'), - (3, 1, 'lock_any'), - (3, 1, 'remove_any'), - (3, 1, 'move_any'), - (3, 1, 'merge_any'), - (3, 1, 'split_any'), - (3, 1, 'delete_any'), - (3, 1, 'modify_any'), - (3, 1, 'approve_posts'), - (3, 1, 'post_attachment'), - (3, 1, 'view_attachments'), - (-1, 2, 'poll_view'), - (0, 2, 'remove_own'), - (0, 2, 'lock_own'), - (0, 2, 'modify_own'), - (0, 2, 'poll_view'), - (0, 2, 'poll_vote'), - (0, 2, 'post_attachment'), - (0, 2, 'post_new'), - (0, 2, 'post_draft'), - (0, 2, 'post_reply_any'), - (0, 2, 'post_reply_own'), - (0, 2, 'post_unapproved_topics'), - (0, 2, 'post_unapproved_replies_any'), - (0, 2, 'post_unapproved_replies_own'), - (0, 2, 'post_unapproved_attachments'), - (0, 2, 'delete_own'), - (0, 2, 'report_any'), - (0, 2, 'view_attachments'), - (2, 2, 'moderate_board'), - (2, 2, 'post_new'), - (2, 2, 'post_draft'), - (2, 2, 'post_reply_own'), - (2, 2, 'post_reply_any'), - (2, 2, 'post_unapproved_topics'), - (2, 2, 'post_unapproved_replies_any'), - (2, 2, 'post_unapproved_replies_own'), - (2, 2, 'post_unapproved_attachments'), - (2, 2, 'poll_post'), - (2, 2, 'poll_add_any'), - (2, 2, 'poll_remove_any'), - (2, 2, 'poll_view'), - (2, 2, 'poll_vote'), - (2, 2, 'poll_lock_any'), - (2, 2, 'poll_edit_any'), - (2, 2, 'report_any'), - (2, 2, 'lock_own'), - (2, 2, 'delete_own'), - (2, 2, 'modify_own'), - (2, 2, 'make_sticky'), - (2, 2, 'lock_any'), - (2, 2, 'remove_any'), - (2, 2, 'move_any'), - (2, 2, 'merge_any'), - (2, 2, 'split_any'), - (2, 2, 'delete_any'), - (2, 2, 'modify_any'), - (2, 2, 'approve_posts'), - (2, 2, 'post_attachment'), - (2, 2, 'view_attachments'), - (3, 2, 'moderate_board'), - (3, 2, 'post_new'), - (3, 2, 'post_draft'), - (3, 2, 'post_reply_own'), - (3, 2, 'post_reply_any'), - (3, 2, 'post_unapproved_topics'), - (3, 2, 'post_unapproved_replies_any'), - (3, 2, 'post_unapproved_replies_own'), - (3, 2, 'post_unapproved_attachments'), - (3, 2, 'poll_post'), - (3, 2, 'poll_add_any'), - (3, 2, 'poll_remove_any'), - (3, 2, 'poll_view'), - (3, 2, 'poll_vote'), - (3, 2, 'poll_lock_any'), - (3, 2, 'poll_edit_any'), - (3, 2, 'report_any'), - (3, 2, 'lock_own'), - (3, 2, 'delete_own'), - (3, 2, 'modify_own'), - (3, 2, 'make_sticky'), - (3, 2, 'lock_any'), - (3, 2, 'remove_any'), - (3, 2, 'move_any'), - (3, 2, 'merge_any'), - (3, 2, 'split_any'), - (3, 2, 'delete_any'), - (3, 2, 'modify_any'), - (3, 2, 'approve_posts'), - (3, 2, 'post_attachment'), - (3, 2, 'view_attachments'), - (-1, 3, 'poll_view'), - (0, 3, 'remove_own'), - (0, 3, 'lock_own'), - (0, 3, 'modify_own'), - (0, 3, 'poll_view'), - (0, 3, 'poll_vote'), - (0, 3, 'post_attachment'), - (0, 3, 'post_reply_any'), - (0, 3, 'post_reply_own'), - (0, 3, 'post_unapproved_replies_any'), - (0, 3, 'post_unapproved_replies_own'), - (0, 3, 'post_unapproved_attachments'), - (0, 3, 'delete_own'), - (0, 3, 'report_any'), - (0, 3, 'view_attachments'), - (2, 3, 'moderate_board'), - (2, 3, 'post_new'), - (2, 3, 'post_draft'), - (2, 3, 'post_reply_own'), - (2, 3, 'post_reply_any'), - (2, 3, 'post_unapproved_topics'), - (2, 3, 'post_unapproved_replies_any'), - (2, 3, 'post_unapproved_replies_own'), - (2, 3, 'post_unapproved_attachments'), - (2, 3, 'poll_post'), - (2, 3, 'poll_add_any'), - (2, 3, 'poll_remove_any'), - (2, 3, 'poll_view'), - (2, 3, 'poll_vote'), - (2, 3, 'poll_lock_any'), - (2, 3, 'poll_edit_any'), - (2, 3, 'report_any'), - (2, 3, 'lock_own'), - (2, 3, 'delete_own'), - (2, 3, 'modify_own'), - (2, 3, 'make_sticky'), - (2, 3, 'lock_any'), - (2, 3, 'remove_any'), - (2, 3, 'move_any'), - (2, 3, 'merge_any'), - (2, 3, 'split_any'), - (2, 3, 'delete_any'), - (2, 3, 'modify_any'), - (2, 3, 'approve_posts'), - (2, 3, 'post_attachment'), - (2, 3, 'view_attachments'), - (3, 3, 'moderate_board'), - (3, 3, 'post_new'), - (3, 3, 'post_draft'), - (3, 3, 'post_reply_own'), - (3, 3, 'post_reply_any'), - (3, 3, 'post_unapproved_topics'), - (3, 3, 'post_unapproved_replies_any'), - (3, 3, 'post_unapproved_replies_own'), - (3, 3, 'post_unapproved_attachments'), - (3, 3, 'poll_post'), - (3, 3, 'poll_add_any'), - (3, 3, 'poll_remove_any'), - (3, 3, 'poll_view'), - (3, 3, 'poll_vote'), - (3, 3, 'poll_lock_any'), - (3, 3, 'poll_edit_any'), - (3, 3, 'report_any'), - (3, 3, 'lock_own'), - (3, 3, 'delete_own'), - (3, 3, 'modify_own'), - (3, 3, 'make_sticky'), - (3, 3, 'lock_any'), - (3, 3, 'remove_any'), - (3, 3, 'move_any'), - (3, 3, 'merge_any'), - (3, 3, 'split_any'), - (3, 3, 'delete_any'), - (3, 3, 'modify_any'), - (3, 3, 'approve_posts'), - (3, 3, 'post_attachment'), - (3, 3, 'view_attachments'), - (-1, 4, 'poll_view'), - (0, 4, 'poll_view'), - (0, 4, 'poll_vote'), - (0, 4, 'report_any'), - (0, 4, 'view_attachments'), - (2, 4, 'moderate_board'), - (2, 4, 'post_new'), - (2, 4, 'post_draft'), - (2, 4, 'post_reply_own'), - (2, 4, 'post_reply_any'), - (2, 4, 'post_unapproved_topics'), - (2, 4, 'post_unapproved_replies_any'), - (2, 4, 'post_unapproved_replies_own'), - (2, 4, 'post_unapproved_attachments'), - (2, 4, 'poll_post'), - (2, 4, 'poll_add_any'), - (2, 4, 'poll_remove_any'), - (2, 4, 'poll_view'), - (2, 4, 'poll_vote'), - (2, 4, 'poll_lock_any'), - (2, 4, 'poll_edit_any'), - (2, 4, 'report_any'), - (2, 4, 'lock_own'), - (2, 4, 'delete_own'), - (2, 4, 'modify_own'), - (2, 4, 'make_sticky'), - (2, 4, 'lock_any'), - (2, 4, 'remove_any'), - (2, 4, 'move_any'), - (2, 4, 'merge_any'), - (2, 4, 'split_any'), - (2, 4, 'delete_any'), - (2, 4, 'modify_any'), - (2, 4, 'approve_posts'), - (2, 4, 'post_attachment'), - (2, 4, 'view_attachments'), - (3, 4, 'moderate_board'), - (3, 4, 'post_new'), - (3, 4, 'post_draft'), - (3, 4, 'post_reply_own'), - (3, 4, 'post_reply_any'), - (3, 4, 'post_unapproved_topics'), - (3, 4, 'post_unapproved_replies_any'), - (3, 4, 'post_unapproved_replies_own'), - (3, 4, 'post_unapproved_attachments'), - (3, 4, 'poll_post'), - (3, 4, 'poll_add_any'), - (3, 4, 'poll_remove_any'), - (3, 4, 'poll_view'), - (3, 4, 'poll_vote'), - (3, 4, 'poll_lock_any'), - (3, 4, 'poll_edit_any'), - (3, 4, 'report_any'), - (3, 4, 'lock_own'), - (3, 4, 'delete_own'), - (3, 4, 'modify_own'), - (3, 4, 'make_sticky'), - (3, 4, 'lock_any'), - (3, 4, 'remove_any'), - (3, 4, 'move_any'), - (3, 4, 'merge_any'), - (3, 4, 'split_any'), - (3, 4, 'delete_any'), - (3, 4, 'modify_any'), - (3, 4, 'approve_posts'), - (3, 4, 'post_attachment'), - (3, 4, 'view_attachments'); -# -------------------------------------------------------- - -# -# Dumping data for table `boards` -# - -INSERT INTO {$db_prefix}boards - (id_board, id_cat, board_order, id_last_msg, id_msg_updated, name, description, num_topics, num_posts, member_groups) -VALUES (1, 1, 1, 1, 1, '{$default_board_name}', '{$default_board_description}', 1, 1, '-1,0,2'); -# -------------------------------------------------------- - -# -# Dumping data for table `board_permissions_view` -# - -INSERT INTO {$db_prefix}board_permissions_view - (id_group, id_board, deny) -VALUES (-1,1,0), (0,1,0), (2,1,0); -# -------------------------------------------------------- - -# -# Dumping data for table `calendar` -# - -INSERT INTO {$db_prefix}calendar - (title, start_date, end_date, start_time, timezone, location, duration, rrule, rdates, exdates, type, enabled) -VALUES - ('April Fools'' Day', '2000-04-01', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Christmas', '2000-12-25', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Cinco de Mayo', '2000-05-05', '9999-12-31', NULL, NULL, 'Mexico, USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('D-Day', '2000-06-06', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Easter', '2000-04-23', '9999-12-31', NULL, NULL, '', 'P1D', 'EASTER_W', '', '', 1, 1), - ('Earth Day', '2000-04-22', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Father''s Day', '2000-06-19', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY;BYMONTH=6;BYDAY=3SU', '', '', 1, 1), - ('Flag Day', '2000-06-14', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Good Friday', '2000-04-21', '9999-12-31', NULL, NULL, '', 'P1D', 'EASTER_W-P2D', '', '', 1, 1), - ('Groundhog Day', '2000-02-02', '9999-12-31', NULL, NULL, 'Canada, USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Halloween', '2000-10-31', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Independence Day', '2000-07-04', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Labor Day', '2000-09-03', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', '', '', 1, 1), - ('Labour Day', '2000-09-03', '9999-12-31', NULL, NULL, 'Canada', 'P1D', 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', '', '', 1, 1), - ('Memorial Day', '2000-05-31', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=5;BYDAY=-1MO', '', '', 1, 1), - ('Mother''s Day', '2000-05-08', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY;BYMONTH=5;BYDAY=2SU', '', '', 1, 1), - ('New Year''s Day', '2000-01-01', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Remembrance Day', '2000-11-11', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('St. Patrick''s Day', '2000-03-17', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Thanksgiving', '2000-11-26', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY;BYMONTH=11;BYDAY=4TH', '', '', 1, 1), - ('United Nations Day', '2000-10-24', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Valentine''s Day', '2000-02-14', '9999-12-31', NULL, NULL, '', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Veterans Day', '2000-11-11', '9999-12-31', NULL, NULL, 'USA', 'P1D', 'FREQ=YEARLY', '', '', 1, 1), - ('Vernal Equinox', '2000-03-20', '9999-12-31', '07:30:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Summer Solstice', '2000-06-21', '9999-12-31', '01:44:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Autumnal Equinox', '2000-09-22', '9999-12-31', '17:16:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1), - ('Winter Solstice', '2000-12-21', '9999-12-31', '13:27:00', 'UTC', '', 'PT1M', 'FREQ=YEARLY;COUNT=1', '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', '', 1, 1); -# -------------------------------------------------------- - -# -# Dumping data for table `categories` -# - -INSERT INTO {$db_prefix}categories -VALUES (1, 0, '{$default_category_name}', '', 1); -# -------------------------------------------------------- - -# -# Dumping data for table `custom_fields` -# - -INSERT INTO {$db_prefix}custom_fields - (col_name, field_name, field_desc, field_type, field_length, field_options, field_order, mask, show_reg, show_display, show_mlist, show_profile, private, active, bbc, can_search, default_value, enclose, placement) -VALUES ('cust_skype', '{skype}', '{skype_desc}', 'text', 32, '', 2, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '{INPUT} ', 1), - ('cust_loca', '{location}', '{location_desc}', 'text', 50, '', 4, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '', 0), - ('cust_gender', '{gender}', '{gender_desc}', 'radio', 255, '{gender_0},{gender_1},{gender_2}', 5, 'nohtml', 1, 1, 0, 'forumprofile', 0, 1, 0, 0, '{gender_0}', '', 1); - -# -------------------------------------------------------- - -# -# Dumping data for table `membergroups` -# - -INSERT INTO {$db_prefix}membergroups - (id_group, group_name, description, online_color, min_posts, icons, group_type) -VALUES (1, '{$default_administrator_group}', '', '#FF0000', -1, '5#iconadmin.png', 1), - (2, '{$default_global_moderator_group}', '', '#0000FF', -1, '5#icongmod.png', 0), - (3, '{$default_moderator_group}', '', '', -1, '5#iconmod.png', 0), - (4, '{$default_newbie_group}', '', '', 0, '1#icon.png', 0), - (5, '{$default_junior_group}', '', '', 50, '2#icon.png', 0), - (6, '{$default_full_group}', '', '', 100, '3#icon.png', 0), - (7, '{$default_senior_group}', '', '', 250, '4#icon.png', 0), - (8, '{$default_hero_group}', '', '', 500, '5#icon.png', 0); -# -------------------------------------------------------- - -# -# Dumping data for table `message_icons` -# - -# // !!! i18n -INSERT INTO {$db_prefix}message_icons - (filename, title, icon_order) -VALUES ('xx', 'Standard', '0'), - ('thumbup', 'Thumb Up', '1'), - ('thumbdown', 'Thumb Down', '2'), - ('exclamation', 'Exclamation point', '3'), - ('question', 'Question mark', '4'), - ('lamp', 'Lamp', '5'), - ('smiley', 'Smiley', '6'), - ('angry', 'Angry', '7'), - ('cheesy', 'Cheesy', '8'), - ('grin', 'Grin', '9'), - ('sad', 'Sad', '10'), - ('wink', 'Wink', '11'), - ('poll', 'Poll', '12'); -# -------------------------------------------------------- - -# -# Dumping data for table `messages` -# - -INSERT INTO {$db_prefix}messages - (id_msg, id_msg_modified, id_topic, id_board, poster_time, subject, poster_name, poster_email, modified_name, body, icon) -VALUES (1, 1, 1, 1, {$current_time}, '{$default_topic_subject}', 'Simple Machines', 'info@simplemachines.org', '', '{$default_topic_message}', 'xx'); -# -------------------------------------------------------- - -# -# Dumping data for table `package_servers` -# - -INSERT INTO {$db_prefix}package_servers - (name, url, validation_url) -VALUES ('Simple Machines Third-party Mod Site', 'https://custom.simplemachines.org/packages/mods', 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}'), - ('Simple Machines Downloads 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}'); -# -------------------------------------------------------- - -# -# Dumping data for table `permission_profiles` -# - -INSERT INTO {$db_prefix}permission_profiles - (id_profile, profile_name) -VALUES (1, 'default'), (2, 'no_polls'), (3, 'reply_only'), (4, 'read_only'); -# -------------------------------------------------------- - -# -# Dumping data for table `permissions` -# - -INSERT INTO {$db_prefix}permissions - (id_group, permission) -VALUES (-1, 'search_posts'), - (-1, 'calendar_view'), - (-1, 'view_stats'), - (0, 'view_mlist'), - (0, 'search_posts'), - (0, 'profile_view'), - (0, 'pm_read'), - (0, 'pm_send'), - (0, 'pm_draft'), - (0, 'calendar_view'), - (0, 'view_stats'), - (0, 'who_view'), - (0, 'profile_identity_own'), - (0, 'profile_password_own'), - (0, 'profile_blurb_own'), - (0, 'profile_displayed_name_own'), - (0, 'profile_signature_own'), - (0, 'profile_website_own'), - (0, 'profile_forum_own'), - (0, 'profile_extra_own'), - (0, 'profile_remove_own'), - (0, 'profile_server_avatar'), - (0, 'profile_upload_avatar'), - (0, 'profile_remote_avatar'), - (2, 'view_mlist'), - (2, 'search_posts'), - (2, 'profile_view'), - (2, 'pm_read'), - (2, 'pm_send'), - (2, 'pm_draft'), - (2, 'calendar_view'), - (2, 'view_stats'), - (2, 'who_view'), - (2, 'profile_identity_own'), - (2, 'profile_password_own'), - (2, 'profile_blurb_own'), - (2, 'profile_displayed_name_own'), - (2, 'profile_signature_own'), - (2, 'profile_website_own'), - (2, 'profile_forum_own'), - (2, 'profile_extra_own'), - (2, 'profile_remove_own'), - (2, 'profile_server_avatar'), - (2, 'profile_upload_avatar'), - (2, 'profile_remote_avatar'), - (2, 'profile_title_own'), - (2, 'calendar_post'), - (2, 'calendar_edit_any'), - (2, 'access_mod_center'); -# -------------------------------------------------------- - -# -# Dumping data for table `scheduled_tasks` -# - -INSERT INTO {$db_prefix}scheduled_tasks - (id_task, next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (3, 0, 60, 1, 'd', 0, 'daily_maintenance', ''), - (5, 0, 0, 1, 'd', 0, 'daily_digest', ''), - (6, 0, 0, 1, 'w', 0, 'weekly_digest', ''), - (7, 0, {$sched_task_offset}, 1, 'd', 0, 'fetchSMfiles', ''), - (8, 0, 0, 1, 'd', 1, 'birthdayemails', ''), - (9, 0, 0, 1, 'w', 0, 'weekly_maintenance', ''), - (10, 0, 120, 1, 'd', 1, 'paid_subscriptions', ''), - (11, 0, 120, 1, 'd', 0, 'remove_temp_attachments', ''), - (12, 0, 180, 1, 'd', 0, 'remove_topic_redirect', ''), - (13, 0, 240, 1, 'd', 0, 'remove_old_drafts', ''), - (14, 0, 0, 1, 'w', 1, 'prune_log_topics', ''); - -# -------------------------------------------------------- - -# -# Dumping data for table `settings` -# - -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES ('smfVersion', '{$smf_version}'), - ('news', '{$default_news}'), - ('compactTopicPagesContiguous', '5'), - ('compactTopicPagesEnable', '1'), - ('todayMod', '1'), - ('enablePreviousNext', '1'), - ('pollMode', '1'), - ('enableCompressedOutput', '{$enableCompressedOutput}'), - ('attachmentSizeLimit', '128'), - ('attachmentPostLimit', '192'), - ('attachmentNumPerPostLimit', '4'), - ('attachmentDirSizeLimit', '10240'), - ('attachmentDirFileLimit', '1000'), - ('attachmentUploadDir', '{$attachdir}'), - ('attachmentExtensions', 'doc,gif,jpg,mpg,pdf,png,txt,zip'), - ('attachmentCheckExtensions', '0'), - ('attachmentShowImages', '1'), - ('attachmentEnable', '1'), - ('attachmentThumbnails', '1'), - ('attachmentThumbWidth', '150'), - ('attachmentThumbHeight', '150'), - ('use_subdirectories_for_attachments', '1'), - ('currentAttachmentUploadDir', 1), - ('censorIgnoreCase', '1'), - ('spoofdetector_censor', '1'), - ('mostOnline', '1'), - ('mostOnlineToday', '1'), - ('mostDate', {$current_time}), - ('trackStats', '1'), - ('userLanguage', '1'), - ('titlesEnable', '1'), - ('topicSummaryPosts', '15'), - ('enableErrorLogging', '1'), - ('max_image_width', '0'), - ('max_image_height', '0'), - ('onlineEnable', '0'), - ('boardindex_max_depth', '5'), - ('cal_enabled', '0'), - ('cal_showInTopic', '1'), - ('cal_maxyear', '2040'), - ('cal_minyear', '2018'), - ('cal_daysaslink', '0'), - ('cal_defaultboard', ''), - ('cal_showholidays', '1'), - ('cal_showbdays', '1'), - ('cal_showevents', '1'), - ('cal_maxspan', '0'), - ('cal_disable_prev_next', '0'), - ('cal_display_type', '0'), - ('cal_week_links', '2'), - ('cal_prev_next_links', '1'), - ('cal_short_days', '0'), - ('cal_short_months', '0'), - ('smtp_host', ''), - ('smtp_port', '25'), - ('smtp_username', ''), - ('smtp_password', ''), - ('mail_type', '0'), - ('timeLoadPageEnable', '0'), - ('totalMembers', '0'), - ('totalTopics', '1'), - ('totalMessages', '1'), - ('censor_vulgar', ''), - ('censor_proper', ''), - ('enablePostHTML', '0'), - ('theme_allow', '1'), - ('theme_default', '1'), - ('theme_guests', '1'), - ('xmlnews_enable', '1'), - ('xmlnews_maxlen', '255'), - ('registration_method', '{$registration_method}'), - ('send_validation_onChange', '0'), - ('send_welcomeEmail', '1'), - ('allow_editDisplayName', '1'), - ('allow_hideOnline', '1'), - ('spamWaitTime', '5'), - ('pm_spam_settings', '10,5,20'), - ('reserveWord', '0'), - ('reserveCase', '1'), - ('reserveUser', '1'), - ('reserveName', '1'), - ('reserveNames', E'{$default_reserved_names}'), - ('autoLinkUrls', '1'), - ('banLastUpdated', '0'), - ('smileys_dir', '{$boarddir}/Smileys'), - ('smileys_url', '{$boardurl}/Smileys'), - ('custom_avatar_dir', '{$boarddir}/custom_avatar'), - ('custom_avatar_url', '{$boardurl}/custom_avatar'), - ('avatar_directory', '{$boarddir}/avatars'), - ('avatar_url', '{$boardurl}/avatars'), - ('avatar_max_height_external', '65'), - ('avatar_max_width_external', '65'), - ('avatar_action_too_large', 'option_css_resize'), - ('avatar_max_height_upload', '65'), - ('avatar_max_width_upload', '65'), - ('avatar_resize_upload', '1'), - ('avatar_download_png', '1'), - ('failed_login_threshold', '3'), - ('oldTopicDays', '120'), - ('edit_wait_time', '90'), - ('edit_disable_time', '0'), - ('autoFixDatabase', '1'), - ('allow_guestAccess', '1'), - ('time_format', '{$default_time_format}'), - ('number_format', '1234.00'), - ('enableBBC', '1'), - ('max_messageLength', '20000'), - ('signature_settings', '1,300,0,0,0,0,0,0:'), - ('defaultMaxMessages', '15'), - ('defaultMaxTopics', '20'), - ('defaultMaxMembers', '30'), - ('enableParticipation', '1'), - ('recycle_enable', '0'), - ('recycle_board', '0'), - ('maxMsgID', '1'), - ('enableAllMessages', '0'), - ('knownThemes', '1'), - ('enableThemes', '1'), - ('who_enabled', '1'), - ('lastActive', '15'), - ('smiley_sets_known', 'fugue,alienine'), - ('smiley_sets_names', '{$default_fugue_smileyset_name}'||E'\n'||'{$default_alienine_smileyset_name}'), - ('smiley_sets_default', 'fugue'), - ('cal_days_for_index', '7'), - ('requireAgreement', '1'), - ('requirePolicyAgreement', '0'), - ('unapprovedMembers', '0'), - ('default_personal_text', ''), - ('package_make_backups', '1'), - ('databaseSession_enable', '{$databaseSession_enable}'), - ('databaseSession_loose', '1'), - ('databaseSession_lifetime', '2880'), - ('search_cache_size', '50'), - ('search_results_per_page', '30'), - ('search_weight_frequency', '30'), - ('search_weight_age', '25'), - ('search_weight_length', '20'), - ('search_weight_subject', '15'), - ('search_weight_first_message', '10'), - ('search_max_results', '1200'), - ('search_floodcontrol_time', '5'), - ('permission_enable_deny', '0'), - ('permission_enable_postgroups', '0'), - ('mail_next_send', '0'), - ('mail_recent', '0000000000|0'), - ('settings_updated', '0'), - ('next_task_time', '1'), - ('warning_settings', '1,20,0'), - ('warning_watch', '10'), - ('warning_moderate', '35'), - ('warning_mute', '60'), - ('last_mod_report_action', '0'), - ('pruningOptions', '30,180,180,180,30,0'), - ('mark_read_beyond', '90'), - ('mark_read_delete_beyond', '365'), - ('mark_read_max_users', '500'), - ('modlog_enabled', '1'), - ('adminlog_enabled', '1'), - ('reg_verification', '1'), - ('visual_verification_type', '3'), - ('enable_buddylist', '1'), - ('birthday_email', 'happy_birthday'), - ('dont_repeat_theme_core', '1'), - ('dont_repeat_smileys_20', '1'), - ('dont_repeat_buddylists', '1'), - ('attachment_image_reencode', '1'), - ('attachment_image_paranoid', '0'), - ('attachment_thumb_png', '1'), - ('avatar_reencode', '1'), - ('avatar_paranoid', '0'), - ('drafts_post_enabled', '1'), - ('drafts_pm_enabled', '1'), - ('drafts_autosave_enabled', '1'), - ('drafts_show_saved_enabled', '1'), - ('drafts_keep_days', '7'), - ('topic_move_any', '0'), - ('mail_limit', '5'), - ('mail_quantity', '5'), - ('additional_options_collapsable', '1'), - ('show_modify', '1'), - ('show_user_images', '1'), - ('show_blurb', '1'), - ('show_profile_buttons', '1'), - ('enable_ajax_alerts', '1'), - ('alerts_auto_purge', '30'), - ('gravatarEnabled', '1'), - ('gravatarOverride', '0'), - ('gravatarAllowExtraEmail', '1'), - ('gravatarMaxRating', 'PG'), - ('defaultMaxListItems', '15'), - ('loginHistoryDays', '30'), - ('httponlyCookies', '1'), - ('samesiteCookies', 'lax'), - ('tfa_mode', '1'), - ('export_dir', '{$boarddir}/exports'), - ('export_expiry', '7'), - ('export_min_diskspace_pct', '5'), - ('export_rate', '250'), - ('allow_expire_redirect', '1'), - ('json_done', '1'), - ('attachments_21_done', '1'), - ('displayFields', '[{"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"]}]'), - ('minimize_files', '1'), - ('securityDisable_moderate', '1'); -# -------------------------------------------------------- - -# -# Dumping data for table `smileys` -# - -INSERT INTO {$db_prefix}smileys - (code, description, smiley_order, hidden) -VALUES (':)', '{$default_smiley_smiley}', 0, 0), - (';)', '{$default_wink_smiley}', 1, 0), - (':D', '{$default_cheesy_smiley}', 2, 0), - (';D', '{$default_grin_smiley}', 3, 0), - ('>:(', '{$default_angry_smiley}', 4, 0), - (':(', '{$default_sad_smiley}', 5, 0), - (':o', '{$default_shocked_smiley}', 6, 0), - ('8)', '{$default_cool_smiley}', 7, 0), - ('???', '{$default_huh_smiley}', 8, 0), - ('::)', '{$default_roll_eyes_smiley}', 9, 0), - (':P', '{$default_tongue_smiley}', 10, 0), - (':-[', '{$default_embarrassed_smiley}', 11, 0), - (':-X', '{$default_lips_sealed_smiley}', 12, 0), - (':-\', '{$default_undecided_smiley}', 13, 0), - (':-*', '{$default_kiss_smiley}', 14, 0), - (':''(', '{$default_cry_smiley}', 15, 0), - ('>:D', '{$default_evil_smiley}', 16, 1), - ('^-^', '{$default_azn_smiley}', 17, 1), - ('O0', '{$default_afro_smiley}', 18, 1), - (':))', '{$default_laugh_smiley}', 19, 1), - ('C:-)', '{$default_police_smiley}', 20, 1), - ('O:-)', '{$default_angel_smiley}', 21, 1); -# -------------------------------------------------------- - -# -# Dumping data for table `spiders` -# - -INSERT INTO {$db_prefix}spiders - (spider_name, user_agent, ip_info) -VALUES ('Google', 'googlebot', ''), - ('Yahoo!', 'slurp', ''), - ('Bing', 'bingbot', ''), - ('Google (Mobile)', 'Googlebot-Mobile', ''), - ('Google (Image)', 'Googlebot-Image', ''), - ('Google (AdSense)', 'Mediapartners-Google', ''), - ('Google (Adwords)', 'AdsBot-Google', ''), - ('Yahoo! (Mobile)', 'YahooSeeker/M1A1-R2D2', ''), - ('Yahoo! (Image)', 'Yahoo-MMCrawler', ''), - ('Bing (Preview)', 'BingPreview', ''), - ('Bing (Ads)', 'adidxbot', ''), - ('Bing (MSNBot)', 'msnbot', ''), - ('Bing (Media)', 'msnbot-media', ''), - ('Cuil', 'twiceler', ''), - ('Ask', 'Teoma', ''), - ('Baidu', 'Baiduspider', ''), - ('Gigablast', 'Gigabot', ''), - ('InternetArchive', 'ia_archiver-web.archive.org', ''), - ('Alexa', 'ia_archiver', ''), - ('Omgili', 'omgilibot', ''), - ('EntireWeb', 'Speedy Spider', ''), - ('Yandex', 'yandex', ''); -#--------------------------------------------------------- - -# -# Dumping data for table `themes` -# - -INSERT INTO {$db_prefix}themes - (id_theme, variable, value) -VALUES (1, 'name', '{$default_theme_name}'), - (1, 'theme_url', '{$boardurl}/Themes/default'), - (1, 'images_url', '{$boardurl}/Themes/default/images'), - (1, 'theme_dir', '{$boarddir}/Themes/default'), - (1, 'show_latest_member', '1'), - (1, 'show_newsfader', '0'), - (1, 'number_recent_posts', '0'), - (1, 'show_stats_index', '1'), - (1, 'newsfader_time', '3000'), - (1, 'use_image_buttons', '1'), - (1, 'enable_news', '1'); - -INSERT INTO {$db_prefix}themes - (id_member, id_theme, variable, value) -VALUES (-1, 1, 'posts_apply_ignore_list', '1'), - (-1, 1, 'drafts_show_saved_enabled', '1'), - (-1, 1, 'return_to_post', '1'); -# -------------------------------------------------------- - -# -# Dumping data for table `topics` -# - -INSERT INTO {$db_prefix}topics - (id_topic, id_board, id_first_msg, id_last_msg, id_member_started, id_member_updated) -VALUES (1, 1, 1, 1, 0, 0); -# -------------------------------------------------------- - -# -# Dumping data for table `user_alerts_prefs` -# - -INSERT INTO {$db_prefix}user_alerts_prefs - (id_member, alert_pref, alert_value) -VALUES (0, 'alert_timeout', 10), - (0, 'announcements', 0), - (0, 'birthday', 2), - (0, 'board_notify', 1), - (0, 'buddy_request', 1), - (0, 'groupr_approved', 3), - (0, 'groupr_rejected', 3), - (0, 'member_group_request', 1), - (0, 'member_register', 1), - (0, 'member_report', 3), - (0, 'member_report_reply', 3), - (0, 'msg_auto_notify', 0), - (0, 'msg_like', 1), - (0, 'msg_mention', 1), - (0, 'msg_notify_pref', 1), - (0, 'msg_notify_type', 1), - (0, 'msg_quote', 1), - (0, 'msg_receive_body', 0), - (0, 'msg_report', 1), - (0, 'msg_report_reply', 1), - (0, 'pm_new', 1), - (0, 'pm_notify', 1), - (0, 'pm_reply', 1), - (0, 'request_group', 1), - (0, 'topic_notify', 1), - (0, 'unapproved_attachment', 1), - (0, 'unapproved_reply', 3), - (0, 'unapproved_post', 1), - (0, 'warn_any', 1); -# -------------------------------------------------------- - -# -# Now we push all this through... -# -COMMIT; diff --git a/other/upgrade-helper.php b/other/upgrade-helper.php deleted file mode 100644 index 1dd0f02459..0000000000 --- a/other/upgrade-helper.php +++ /dev/null @@ -1,498 +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 ba1386f3ca..7f5ee03532 100644 --- a/other/upgrade.php +++ b/other/upgrade.php @@ -11,5937 +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'] . '/Install.php')) { - $_SESSION['upgrader_lang'] = $upcontext['language']; - } elseif (isset($upcontext['lang']) && file_exists($lang_dir . '/' . $upcontext['lang'] . '/Install.php')) { - $_SESSION['upgrader_lang'] = $upcontext['lang']; - } elseif (isset($current_language) && file_exists($lang_dir . '/' . $current_language . '/Install.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') { - 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 "Install" 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 . '/' . 'Install.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 "Install" 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'] . '/Install.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+Install', $_SESSION['upgrader_lang']); - - // Remember what we've done - $loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Install.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 . '/Install.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( - '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, - ], - identifier: 'substring', - ); - } - - 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'] . '/Install.php')) { - print_error('Error: Install language is missing for selected language.', true); - } - - // Otherwise include it! - require_once $lang_dir . '/' . $upcontext['language'] . '/Install.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); diff --git a/other/upgrade_2-1_MySQL.sql b/other/upgrade_2-1_MySQL.sql deleted file mode 100644 index 9485a5754d..0000000000 --- a/other/upgrade_2-1_MySQL.sql +++ /dev/null @@ -1,3902 +0,0 @@ -/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ - -/******************************************************************************/ ---- Fixing dates... -/******************************************************************************/ - ----# Updating old values -UPDATE {$db_prefix}calendar -SET start_date = DATE(CONCAT(1004, '-', MONTH(start_date), '-', DAY(start_date))) -WHERE YEAR(start_date) < 1004; - -UPDATE {$db_prefix}calendar -SET end_date = DATE(CONCAT(1004, '-', MONTH(end_date), '-', DAY(end_date))) -WHERE YEAR(end_date) < 1004; - -UPDATE {$db_prefix}calendar_holidays -SET event_date = DATE(CONCAT(1004, '-', MONTH(event_date), '-', DAY(event_date))) -WHERE YEAR(event_date) < 1004; - -UPDATE {$db_prefix}log_spider_stats -SET stat_date = DATE(CONCAT(1004, '-', MONTH(stat_date), '-', DAY(stat_date))) -WHERE YEAR(stat_date) < 1004; - -UPDATE {$db_prefix}members -SET birthdate = DATE(CONCAT(IF(YEAR(birthdate) < 1004, 1004, YEAR(birthdate)), '-', IF(MONTH(birthdate) < 1, 1, MONTH(birthdate)), '-', IF(DAY(birthdate) < 1, 1, DAY(birthdate)))) -WHERE YEAR(birthdate) < 1004 OR MONTH(birthdate) < 1 OR DAY(birthdate) < 1; ----# - ----# Changing default values -ALTER TABLE {$db_prefix}calendar CHANGE start_date start_date date NOT NULL DEFAULT '1004-01-01'; -ALTER TABLE {$db_prefix}calendar CHANGE end_date end_date date NOT NULL DEFAULT '1004-01-01'; -ALTER TABLE {$db_prefix}calendar_holidays CHANGE event_date event_date date NOT NULL DEFAULT '1004-01-01'; -ALTER TABLE {$db_prefix}log_spider_stats CHANGE stat_date stat_date date NOT NULL DEFAULT '1004-01-01'; -ALTER TABLE {$db_prefix}members CHANGE birthdate birthdate date NOT NULL DEFAULT '1004-01-01'; -ALTER TABLE {$db_prefix}log_activity CHANGE DATE DATE date NOT NULL; ----# - -/******************************************************************************/ ---- Removing karma -/******************************************************************************/ - ----# Removing all karma data, if selected ----{ -if (!empty($upcontext['delete_karma'])) -{ - // Delete old settings vars. - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable IN ({array_string:karma_vars})', - array( - 'karma_vars' => array('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->query( - 'ALTER TABLE {db_prefix}members - DROP karma_good', - array() - ); - - // Does karma bad was enable? - if (in_array('karma_bad', $member_columns)) - Db::$db->query( - 'ALTER TABLE {db_prefix}members - DROP karma_bad', - array() - ); - - // Cleaning up old karma permissions. - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {string:karma_vars}', - array( - 'karma_vars' => 'karma_edit', - ) - ); - - // Cleaning up old log_karma table - Db::$db->query( - 'DROP TABLE IF EXISTS {db_prefix}log_karma', - array() - ); -} ----} ----# - -/******************************************************************************/ ---- Emptying error log -/******************************************************************************/ - ----# Emptying error log, if selected ----{ -if (!empty($upcontext['empty_error'])) -{ - Db::$db->query( - 'TRUNCATE {db_prefix}log_errors', - array( - ), - identifier: 'truncate_table', - ); -} ----} ----# - -/******************************************************************************/ ---- Adding new settings... -/******************************************************************************/ ----# Adding login history... -CREATE TABLE IF NOT EXISTS {$db_prefix}member_logins ( - id_login INT AUTO_INCREMENT, - id_member MEDIUMINT NOT NULL DEFAULT '0', - time INT NOT NULL DEFAULT '0', - ip VARBINARY(16), - ip2 VARBINARY(16), - PRIMARY KEY id_login(id_login), - INDEX idx_id_member (id_member), - INDEX idx_time (time) -) ENGINE=MyISAM; ----# - ----# Copying the current package backup setting... ----{ -if (!isset(Config::$modSettings['package_make_full_backups']) && isset(Config::$modSettings['package_make_backups'])) - upgrade_query(" - INSERT INTO {$db_prefix}settings - (variable, value) - VALUES - ('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 = upgrade_query(" - SELECT value - FROM {$db_prefix}themes - WHERE variable='allow_no_censored' - AND id_theme = 1 OR id_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) - { - upgrade_query(" - INSERT INTO {$db_prefix}settings - VALUES ('allow_no_censored', 1) - "); - - // Don't do this twice... - break; - } - } -} ----} ----# - ----# Converting collapsed categories... ----{ -// We cannot do this twice -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $request = Db::$db->query( - 'SELECT id_member, id_cat - FROM {db_prefix}collapsed_categories'); - - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - $inserts[] = array($row['id_member'], 1, 'collapse_category_' . $row['id_cat'], $row['id_cat']); - Db::$db->free_result($request); - - if (!empty($inserts)) - Db::$db->insert('replace', - '{db_prefix}themes', - array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), - $inserts, - array('id_theme', 'id_member', 'variable') - ); -} ----} ----# - ----# Parsing board descriptions and names ----{ -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $request = Db::$db->query( - 'SELECT name, description, id_board - FROM {db_prefix}boards'); - - $inserts = array(); - - Db::$db->free_result($request); - - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array( - 'name' => Utils::htmlspecialchars(strip_tags(SMF\Parser::transform($row['name'], SMF\Parser::OUTPUT_BBC))), - 'description' => Utils::htmlspecialchars(strip_tags(SMF\Parser::transform($row['description'], SMF\Parser::OUTPUT_BBC))), - 'id' => $row['id'], - ); - } - - if (!empty($inserts)) - { - foreach ($inserts as $insert) - { - Db::$db->query( - 'UPDATE {db_prefix}boards - SET name = {string:name}, description = {string:description} - WHERE id = {int:id}', - $insert - ); - } - } -} ----} ----# - ----# Dropping "collapsed_categories" -DROP TABLE IF EXISTS {$db_prefix}collapsed_categories; ----# - ----# Adding new "topic_move_any" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('topic_move_any', '1'); ----# - ----# Adding new "enable_ajax_alerts" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('enable_ajax_alerts', '1'); ----# - ----# Adding new "alerts_auto_purge" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('alerts_auto_purge', '30'); ----# - ----# Adding new "minimize_files" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('minimize_files', '1'); ----# - ----# Collapse object -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('additional_options_collapsable', '1'); ----# - ----# Adding new "DEFAULTMaxListItems" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('defaultMaxListItems', '15'); ----# - ----# Adding new "loginHistoryDays" setting ----{ - if (!isset(Config::$modSettings['loginHistoryDays'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['loginHistoryDays', '30']), - array('variable') - ); ----} ----# - ----# Enable some settings we ripped from Theme settings ----{ - $ripped_settings = array('show_modify', 'show_user_images', 'show_blurb', 'show_profile_buttons', 'subject_toggle', 'hide_post_group'); - - $request = Db::$db->query( - 'SELECT variable, value - FROM {db_prefix}themes - WHERE variable IN({array_string:ripped_settings}) - AND id_member = 0 - AND id_theme = 1', - array( - 'ripped_settings' => $ripped_settings, - ) - ); - - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - $inserts[] = array($row['variable'], $row['value']); - - Db::$db->free_result($request); - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - $inserts, - array('variable') - ); ----} ----# - ----# Adding new "httponlyCookies" setting ----{ - if (!isset(Config::$modSettings['httponlyCookies'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['httponlyCookies', '1']), - array() - ); ----} ----# - ----# Adding new "samesiteCookies" setting ----{ - if (!isset(Config::$modSettings['samesiteCookies'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['samesiteCookies', 'lax']), - array() - ); ----} ----# - ----# Calculate appropriate hash cost ----{ - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['bcrypt_hash_cost', Security::hashBenchmark()]), - array('variable') - ); ----} - ----# Disable Moderation Center Security if it doesn't exist ----{ - if (!isset(Config::$modSettings['securityDisable_moderate'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['securityDisable_moderate', '1']), - array('variable') - ); ----} ----# - ----# Adding new profile data export settings -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_dir', '{$boarddir}/exports'); -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_expiry', '7'); -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_min_diskspace_pct', '5'); -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_rate', '250'); ----# - ----# Adding settings for marking boards as read ----{ - if (!isset(Config::$modSettings['mark_read_beyond'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_beyond', '90']), - array() - ); - if (!isset(Config::$modSettings['mark_read_delete_beyond'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_delete_beyond', '365']), - array() - ); - if (!isset(Config::$modSettings['mark_read_max_users'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_max_users', '500']), - array() - ); ----} ----# - -/******************************************************************************/ ---- Updating legacy attachments... -/******************************************************************************/ - ----# Adding more space to the mime_type column. -ALTER TABLE {$db_prefix}attachments -CHANGE `mime_type` `mime_type` VARCHAR(128) NOT NULL DEFAULT ''; ----# - ----# Converting legacy attachments. ----{ -// 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 = array(0755, 0775, 0777); - - foreach($chmod_values as $val) - { - // If it's writable, break out of the loop - if (is_writable($custom_av_dir)) - break; - else - @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'); -} - -$request = upgrade_query(" - SELECT COUNT(*) - FROM {$db_prefix}attachments - WHERE attachment_type != 1"); -list ($step_progress['total']) = Db::$db->fetch_row($request); -Db::$db->free_result($request); - -$_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; -$step_progress['name'] = 'Converting legacy attachments'; -$step_progress['current'] = $_GET['a']; - -// We may be using multiple attachment directories. -// Allow for reruns - it's possible it's json... -if (!empty(Config::$modSettings['currentAttachmentUploadDir']) && !is_array(Config::$modSettings['attachmentUploadDir'])) - if (empty(Config::$modSettings['json_done'])) - Config::$modSettings['attachmentUploadDir'] = @unserialize(Config::$modSettings['attachmentUploadDir']); - else - Config::$modSettings['attachmentUploadDir'] = @json_decode(Config::$modSettings['attachmentUploadDir'], true); - -// No need to do this if we already did it previously... Unless requested... -if (empty(Config::$modSettings['attachments_21_done']) || !empty($upcontext['reprocess_attachments'])) - $is_done = false; -else - $is_done = true; - -while (!$is_done) -{ - nextSubStep($substep); - - $request = upgrade_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 $_GET[a], 100"); - - // 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 (!str_starts_with(Db::$db->detect_charset('attachments', 'filename'), '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'], array("\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(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $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'])) - { - upgrade_query(" - UPDATE {$db_prefix}attachments - SET file_hash = '', attachment_type = 1 - WHERE id_attach = $row[id_attach]"); - $_GET['a'] -= 1; - } - } - // 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)) - upgrade_query(" - UPDATE {$db_prefix}attachments - SET file_hash = '$fileHash' - WHERE id_attach = $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'])) - Db::$db->query( - 'UPDATE {db_prefix}attachments - SET mime_type = {string:mime_type} - WHERE id_attach = {int:id_attach}', - array( - 'id_attach' => $row['id_attach'], - 'mime_type' => substr($size['mime'], 0, 20), - ) - ); - } - } - Db::$db->free_result($request); - - $_GET['a'] += 100; - $step_progress['current'] = $_GET['a']; -} - -unset($_GET['a']); ----} ----# - ----# Note attachment conversion complete -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachments_21_done', '1'); ----# - ----# Fixing invalid sizes on attachments ----{ -$attachs = array(); -// 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 = Db::$db->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)) - Db::$db->query( - 'UPDATE {db_prefix}attachments - SET width = 0, - height = 0 - WHERE id_attach IN ({array_int:attachs})', - array( - 'attachs' => $attachs, - ) - ); ----} ----# - ----# Fixing attachment directory setting... ----{ -// If it's a directory or an array, ensure it is stored as a serialized string (prep for later serial_to_json conversion) -// Also ensure currentAttachmentUploadDir is set even for single directories -// Make sure to do it in memory and in db... -if (empty(Config::$modSettings['json_done'])) -{ - if (!is_array(Config::$modSettings['attachmentUploadDir']) && is_dir(Config::$modSettings['attachmentUploadDir'])) - { - Config::$modSettings['attachmentUploadDir'] = serialize(array(1 => Config::$modSettings['attachmentUploadDir'])); - Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:attach_dir} - WHERE variable = {string:uploadDir}', - array( - 'attach_dir' => Config::$modSettings['attachmentUploadDir'], - 'uploadDir' => 'attachmentUploadDir' - ) - ); - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['currentAttachmentUploadDir', '1']), - array('variable') - ); - } - elseif (is_array(Config::$modSettings['attachmentUploadDir'])) - { - Config::$modSettings['attachmentUploadDir'] = serialize(Config::$modSettings['attachmentUploadDir']); - Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:attach_dir} - WHERE variable = {string:uploadDir}', - array( - 'attach_dir' => Config::$modSettings['attachmentUploadDir'], - 'uploadDir' => 'attachmentUploadDir' - ) - ); - // Assume currentAttachmentUploadDir is already set - } -} ----} ----# - -/******************************************************************************/ ---- Adding support for logging who fulfils a group request. -/******************************************************************************/ ----# Adding new columns to log_group_requests -ALTER TABLE {$db_prefix}log_group_requests -ADD COLUMN status TINYINT UNSIGNED NOT NULL DEFAULT '0', -ADD COLUMN id_member_acted MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', -ADD COLUMN member_name_acted VARCHAR(255) NOT NULL DEFAULT '', -ADD COLUMN time_acted INT UNSIGNED NOT NULL DEFAULT '0', -ADD COLUMN act_reason TEXT NOT NULL; ----# - ----# Adjusting the indexes for log_group_requests -ALTER TABLE {$db_prefix}log_group_requests -DROP INDEX `id_member`, -ADD INDEX `idx_id_member` (`id_member`, `id_group`); ----# - -/******************************************************************************/ ---- Package Manager New Features -/******************************************************************************/ ----# Adding support for tag in package manager -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN credits TEXT NOT NULL; ----# - ----# Adding support for package hashes -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN sha256_hash TEXT; ----# - ----# Adding support for validation servers -ALTER TABLE {$db_prefix}package_servers -ADD COLUMN validation_url VARCHAR(255) DEFAULT '', -ADD COLUMN extra TEXT; ----# - ----# Add Package Validation to Downloads Site ----{ - $request = Db::$db->query( - 'SELECT id_server - FROM {db_prefix}package_servers - WHERE url LIKE {string:downloads_site}', - array( - '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}package_servers', - array('name' => 'string', 'url' => 'string', 'validation_url' => 'string'), - array(['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}']), - array('id_server') - ); ----} ----# - ----# Ensure The Simple Machines Customize Site is https -UPDATE {$db_prefix}package_servers -SET url = 'https://custom.simplemachines.org/packages/mods' -WHERE url = 'http://custom.simplemachines.org/packages/mods'; ----# - ----# Add validation to Simple Machines Customize Site -UPDATE {$db_prefix}package_servers -SET validation_url = 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}' -WHERE url = 'https://custom.simplemachines.org/packages/mods'; ----# - -/******************************************************************************/ ---- Adding more space for session ids -/******************************************************************************/ ----# Altering the session_id columns... -ALTER TABLE {$db_prefix}log_online -CHANGE `session` `session` VARCHAR(128) NOT NULL DEFAULT ''; - -ALTER TABLE {$db_prefix}log_errors -CHANGE `session` `session` VARCHAR(128) NOT NULL DEFAULT ' '; - -ALTER TABLE {$db_prefix}sessions -CHANGE `session_id` `session_id` VARCHAR(128) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Adding support for MOVED topics enhancements -/******************************************************************************/ ----# Adding new columns to topics .. -ALTER TABLE {$db_prefix}topics -ADD COLUMN redirect_expires INT UNSIGNED NOT NULL DEFAULT '0', -ADD COLUMN id_redirect_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0'; ----# - -/******************************************************************************/ ---- Adding new scheduled tasks -/******************************************************************************/ ----# Adding a new column "callable" to scheduled_tasks table -ALTER TABLE {$db_prefix}scheduled_tasks -ADD COLUMN callable VARCHAR(60) NOT NULL DEFAULT ''; ----# - ----# Adding new scheduled tasks -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 120, 1, 'd', 0, 'remove_temp_attachments', ''); -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 180, 1, 'd', 0, 'remove_topic_redirect', ''); -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 240, 1, 'd', 0, 'remove_old_drafts', ''); -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 0, 1, 'w', 1, 'prune_log_topics', ''); ----# - ----# Adding a new task-related setting... ----{ - if (!isset(Config::$modSettings['allow_expire_redirect'])) - { - $get_info = Db::$db->query( - 'SELECT disabled - FROM {db_prefix}scheduled_tasks - WHERE task = {string:remove_redirect}', - array( - 'remove_redirect' => 'remove_topic_redirect' - ) - ); - - list($task_disabled) = Db::$db->fetch_row($get_info); - Db::$db->free_result($get_info); - - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['allow_expire_redirect', !$task_disabled]), - array('variable') - ); - } ----} ----# - ----# Remove old tasks added by modifications... ----{ - $vanilla_tasks = array( - 'birthdayemails', - 'daily_digest', - 'daily_maintenance', - 'fetchSMfiles', - 'paid_subscriptions', - 'remove_temp_attachments', - 'remove_topic_redirect', - 'remove_old_drafts', - 'prune_log_topics', - 'weekly_digest', - 'weekly_maintenance'); - - Db::$db->query( - 'DELETE FROM {db_prefix}scheduled_tasks - WHERE task NOT IN ({array_string:keep_tasks});', - array( - 'keep_tasks' => $vanilla_tasks - ) - ); ----} ----# - -/******************************************************************************/ ----- Adding background tasks support -/******************************************************************************/ ----# Adding the new table -CREATE TABLE IF NOT EXISTS {$db_prefix}background_tasks ( - id_task INT UNSIGNED AUTO_INCREMENT, - task_file VARCHAR(255) NOT NULL DEFAULT '', - task_class VARCHAR(255) NOT NULL DEFAULT '', - task_data mediumtext NOT NULL, - claimed_time INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_task) -) ENGINE=MyISAM; ----# - -/******************************************************************************/ ---- Adding support for deny boards access -/******************************************************************************/ ----# Adding new columns to boards... -ALTER TABLE {$db_prefix}boards -ADD COLUMN deny_member_groups VARCHAR(255) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Adding setting for max depth of sub-boards to check for new posts, etc. -/******************************************************************************/ ----# Adding the boardindex_max_depth setting. -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('boardindex_max_depth', '1'); ----# - -/******************************************************************************/ ---- Removing manage_boards permission from anyone who shouldn't have it -/******************************************************************************/ ----# Removing manage_boards permission ----{ -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $board_managers = array(); - - $request = Db::$db->query( - 'SELECT id_group - FROM {db_prefix}permissions - WHERE permission = {string:permission}', - array( - 'permission' => 'manage_boards', - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - $board_managers[$row['id_group']] = 0; - } - Db::$db->free_result($request); - - $request = Db::$db->query( - 'SELECT member_groups - FROM {db_prefix}boards', - array() - ); - $num_boards = Db::$db->num_rows($request); - while ($row = Db::$db->fetch_assoc($request)) - { - $groups = explode(',', $row['member_groups']); - foreach ($groups as $group) - if (array_key_exists($group, $board_managers)) - ++$board_managers[$group]; - } - Db::$db->free_result($request); - - $ex_board_managers = array(); - foreach ($board_managers as $id_group => $board_count) - if ($board_count < $num_boards) - $ex_board_managers[] = $id_group; - - if (!empty($ex_board_managers)) - { - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {string:permission} - AND id_group IN ({array_int:ex_board_managers})', - array( - 'permission' => 'manage_boards', - 'ex_board_managers' => $ex_board_managers, - ) - ); - } -} ----} ----# - -/******************************************************************************/ ---- Adding support for category descriptions -/******************************************************************************/ ----# Adding new columns to categories... -ALTER TABLE {$db_prefix}categories -ADD COLUMN description TEXT NOT NULL; ----# - -/******************************************************************************/ ---- Adding support for alerts -/******************************************************************************/ ----# Adding the count to the members table... -ALTER TABLE {$db_prefix}members -ADD COLUMN alerts INT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Adding the new table for alerts. -CREATE TABLE IF NOT EXISTS {$db_prefix}user_alerts ( - id_alert INT UNSIGNED AUTO_INCREMENT, - alert_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_member_started MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - member_name VARCHAR(255) NOT NULL DEFAULT '', - content_type VARCHAR(255) NOT NULL DEFAULT '', - content_id INT UNSIGNED NOT NULL DEFAULT '0', - content_action VARCHAR(255) NOT NULL DEFAULT '', - is_read INT UNSIGNED NOT NULL DEFAULT '0', - extra TEXT NOT NULL, - PRIMARY KEY (id_alert), - INDEX idx_id_member (id_member), - INDEX idx_alert_time (alert_time) -) ENGINE=MyISAM; ----# - ----# Adding alert preferences. -CREATE TABLE IF NOT EXISTS {$db_prefix}user_alerts_prefs ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - alert_pref VARCHAR(32) DEFAULT '', - alert_value TINYINT NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, alert_pref) -) ENGINE=MyISAM; - -INSERT INTO {$db_prefix}user_alerts_prefs - (id_member, alert_pref, alert_value) -VALUES (0, 'alert_timeout', 10), - (0, 'announcements', 0), - (0, 'birthday', 2), - (0, 'board_notify', 1), - (0, 'buddy_request', 1), - (0, 'groupr_approved', 3), - (0, 'groupr_rejected', 3), - (0, 'member_group_request', 1), - (0, 'member_register', 1), - (0, 'member_report', 3), - (0, 'member_report_reply', 3), - (0, 'msg_auto_notify', 0), - (0, 'msg_like', 1), - (0, 'msg_mention', 1), - (0, 'msg_notify_pref', 1), - (0, 'msg_notify_type', 1), - (0, 'msg_quote', 1), - (0, 'msg_receive_body', 0), - (0, 'msg_report', 1), - (0, 'msg_report_reply', 1), - (0, 'pm_new', 1), - (0, 'pm_notify', 1), - (0, 'pm_reply', 1), - (0, 'request_group', 1), - (0, 'topic_notify', 1), - (0, 'unapproved_attachment', 1), - (0, 'unapproved_reply', 3), - (0, 'unapproved_post', 1), - (0, 'warn_any', 1); ----# - ----# Upgrading post notification settings ----{ -// First see if we still have a notify_regularity column -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('notify_regularity', $results)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Upgrading post notification settings'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // Skip errors here so we don't croak if the columns don't exist... - $request = Db::$db->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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_member'], 'msg_receive_body', !empty($row['notify_send_body']) ? 1 : 0); - $inserts[] = array($row['id_member'], 'msg_notify_pref', intval($row['notify_regularity']) + 1); - $inserts[] = array($row['id_member'], 'msg_notify_type', $row['notify_types']); - $inserts[] = array($row['id_member'], 'announcements', !empty($row['notify_announcements']) ? 1 : 0); - } - Db::$db->free_result($request); - } - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - unset($_GET['a']); -} ----} ----# - ----# Dropping old notification fields from the members table -ALTER TABLE {$db_prefix}members - DROP notify_send_body, - DROP notify_types, - DROP notify_regularity, - DROP notify_announcements; ----# - ----# Upgrading auto notify setting ----{ -$_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; -$step_progress['name'] = 'Upgrading auto notify setting'; -$step_progress['current'] = $_GET['a']; - -$limit = 10000; -$is_done = false; - -$request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}themes - WHERE variable = {string:auto_notify}', - array( - 'auto_notify' => 'auto_notify', - ) -); -list($maxMembers) = Db::$db->fetch_row($request); -Db::$db->free_result($request); - -while (!$is_done) -{ - nextSubStep($substep); - $inserts = array(); - - // This setting is stored over in the themes table in 2.0... - $request = Db::$db->query( - 'SELECT id_member, value - FROM {db_prefix}themes - WHERE variable = {string:auto_notify} - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'auto_notify' => 'auto_notify', - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($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', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; -} -unset($_GET['a']); ----} ----# - ----# Dropping old auto notify settings from the themes table -DELETE FROM {$db_prefix}themes - WHERE variable = 'auto_notify'; ----# - ----# Creating alert prefs for watched topics ----{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Creating alert preferences for watched topics'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}log_notify WHERE id_member <> 0 AND id_topic <> 0'); - list($maxTopics) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, (\'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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - $inserts = Db::$db->fetch_all($request); - } - Db::$db->free_result($request); - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxTopics) - $is_done = true; - } - unset($_GET['a']); ----} ----# - ----# Creating alert prefs for watched boards ----{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Creating alert preferences for watched boards'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}log_notify WHERE id_member <> 0 AND id_board <> 0'); - list($maxBoards) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, (\'board_notify_\' || id_board) 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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - $inserts = Db::$db->fetch_all($request); - } - Db::$db->free_result($request); - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxBoards) - $is_done = true; - } - unset($_GET['a']); ----} ----# - ----# Updating obsolete alerts from before RC3 -UPDATE {$db_prefix}user_alerts -SET content_type = 'member', content_id = id_member_started -WHERE content_type = 'buddy'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'member' -WHERE content_type = 'profile'; - -UPDATE {$db_prefix}user_alerts -SET content_id = id_member_started -WHERE content_type = 'member' AND content_action LIKE 'register_%'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_topic' -WHERE content_type = 'unapproved' AND content_action = 'topic'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_reply' -WHERE content_type = 'unapproved' AND content_action = 'reply'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_post' -WHERE content_type = 'unapproved' AND content_action = 'post'; - -UPDATE {$db_prefix}user_alerts AS a -JOIN {$db_prefix}attachments AS f ON (f.id_attach = a.content_id) -SET a.content_type = 'msg', a.content_action = 'unapproved_attachment', a.content_id = f.id_msg -WHERE content_type = 'unapproved' AND content_action = 'attachment'; ----# - ----# Adding index on id_board to log_notify table -ALTER TABLE {$db_prefix}log_notify -ADD INDEX id_board (id_board); ----# - -/******************************************************************************/ ---- Adding support for topic unwatch -/******************************************************************************/ ----# Adding new column to log_topics... -ALTER TABLE {$db_prefix}log_topics -ADD COLUMN unwatched TINYINT NOT NULL DEFAULT 0; ----# - ----# Fixing column name change... -ALTER TABLE {$db_prefix}log_topics -DROP COLUMN disregarded; ----# - -/******************************************************************************/ ---- Fixing mail queue for long messages -/******************************************************************************/ ----# Altering mil_queue table... -ALTER TABLE {$db_prefix}mail_queue -CHANGE body body mediumtext NOT NULL; ----# - -/******************************************************************************/ ---- Name changes -/******************************************************************************/ ----# Altering the membergroup stars to icons -ALTER TABLE {$db_prefix}membergroups -CHANGE `stars` `icons` VARCHAR(255) NOT NULL DEFAULT ''; ----# - ----# Renaming default theme... -UPDATE {$db_prefix}themes -SET value = 'SMF Default Theme - Curve2' -WHERE value LIKE 'SMF Default Theme%'; ----# - ----# Fader time update -UPDATE {$db_prefix}themes -SET value = '3000' -WHERE variable = 'newsfader_time'; ----# - ----# Adding the enableThemes setting. -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('enableThemes', '1'); ----# - ----# Setting "default" as the default... -UPDATE {$db_prefix}settings -SET value = '1' -WHERE variable = 'theme_guests'; - -UPDATE {$db_prefix}boards -SET id_theme = 0; - -UPDATE {$db_prefix}members -SET id_theme = 0; ----# - -/******************************************************************************/ ---- Membergroup icons changes -/******************************************************************************/ ----# Check the current saved names for icons and change them to the new name. ----{ -$request = Db::$db->query( - 'SELECT icons - FROM {db_prefix}membergroups - WHERE icons != {string:blank}', - array( - 'blank' => '', - ) -); -$toMove = array(); -$toChange = array(); -while ($row = Db::$db->fetch_assoc($request)) -{ - if (strpos($row['icons'], 'star.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('star.gif', 'icon.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'starmod.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('starmod.gif', 'iconmod.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'stargmod.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('stargmod.gif', 'icongmod.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'staradmin.gif') !== false) - $toChange[] = array( - '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) - Db::$db->query( - 'UPDATE {db_prefix}membergroups - SET icons = {string:new} - WHERE icons = {string:old}', - array( - '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); -} ----} ----# -/******************************************************************************/ ---- Cleaning up after old themes... -/******************************************************************************/ ----# Clean up settings for unused themes ----{ -// Fetch list of theme directories -$request = Db::$db->query( - 'SELECT id_theme, variable, value - FROM {db_prefix}themes - WHERE variable = {string:theme_dir} - AND id_theme != {int:default_theme};', - array( - '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 = array('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 -Db::$db->query( - 'DELETE FROM {db_prefix}themes - WHERE id_theme NOT IN ({array_int:known_themes});', - array( - 'known_themes' => $known_themes, - ) -); -// Set knownThemes -$known_themes = implode(',', $known_themes); -Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:known_themes} - WHERE variable = {string:known_theme_str};', - array( - 'known_theme_str' => 'knownThemes', - 'known_themes' => $known_themes, - ) -); ----} ----# - -/******************************************************************************/ ---- Messenger fields -/******************************************************************************/ ----# Adding new field_order column... -ALTER TABLE {$db_prefix}custom_fields -ADD COLUMN field_order SMALLINT NOT NULL DEFAULT '0'; ----# - ----# Adding new show_mlist column... -ALTER TABLE {$db_prefix}custom_fields -ADD COLUMN show_mlist SMALLINT NOT NULL DEFAULT '0'; ----# - ----# Insert fields -INSERT INTO `{$db_prefix}custom_fields` (`col_name`, `field_name`, `field_desc`, `field_type`, `field_length`, `field_options`, `field_order`, `mask`, `show_reg`, `show_display`, `show_mlist`, `show_profile`, `private`, `active`, `bbc`, `can_search`, `default_value`, `enclose`, `placement`) VALUES -('cust_skype', '{skype}', '{skype_desc}', 'text', 32, '', 2, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '{INPUT} ', 1), -('cust_loca', '{location}', '{location_desc}', 'text', 50, '', 4, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '', 0), -('cust_gender', '{gender}', '{gender_desc}', 'radio', 255, '{gender_0},{gender_1},{gender_2}', 5, 'nohtml', 1, 1, 0, 'forumprofile', 0, 1, 0, 0, '{gender_0}', '', 1); ----# - ----# Add an order value to each existing cust profile field. ----{ - $ocf = Db::$db->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; - - Db::$db->query( - 'UPDATE {db_prefix}custom_fields - SET field_order = {int:field_count} - WHERE id_field = {int:id_field}', - array( - 'field_count' => $fields_count, - 'id_field' => $row['id_field'], - ) - ); - } - Db::$db->free_result($ocf); ----} ----# - ----# Converting member values... ----{ -// We cannot do this twice -// See which columns we have -$results = Db::$db->list_columns('{db_prefix}members'); -$possible_columns = array('msn', 'location', 'gender'); - -// Find values that are in both arrays -$select_columns = array_intersect($possible_columns, $results); - -if (!empty($select_columns)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Converting member values'; - $step_progress['current'] = $_GET['a']; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - - $limit = 10000; - $is_done = false; - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, '. implode(',', $select_columns) .' - FROM {db_prefix}members - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'start' => $_GET['a'], - 'limit' => $limit, - )); - - while ($row = Db::$db->fetch_assoc($request)) - { - if (!empty($row['msn'])) - $inserts[] = array($row['id_member'], 1, 'cust_skype', $row['msn']); - - if (!empty($row['location'])) - $inserts[] = array($row['id_member'], 1, 'cust_loca', $row['location']); - - if (!empty($row['gender'])) - $inserts[] = array($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', - array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), - $inserts, - array('id_theme', 'id_member', 'variable') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } -} -unset($_GET['a']); ----} ----# - ----# Dropping old fields -ALTER TABLE `{$db_prefix}members` - DROP `icq`, - DROP `aim`, - DROP `yim`, - DROP `msn`, - DROP `location`, - DROP `gender`; ----# - ----# Create the displayFields setting ----{ - if (empty(Config::$modSettings['displayFields'])) - { - $request = Db::$db->query( - 'SELECT col_name, field_name, field_type, field_order, bbc, enclose, placement, show_mlist - FROM {db_prefix}custom_fields', - array() - ); - - $fields = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $fields[] = array( - 'col_name' => strtr($row['col_name'], array('|' => '', ';' => '')), - 'title' => strtr($row['field_name'], array('|' => '', ';' => '')), - '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); - - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['displayFields', json_encode($fields)]), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Adding support for drafts -/******************************************************************************/ ----# Creating draft table -CREATE TABLE IF NOT EXISTS {$db_prefix}user_drafts ( - id_draft INT UNSIGNED AUTO_INCREMENT, - id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL DEFAULT '0', - id_reply INT UNSIGNED NOT NULL DEFAULT '0', - type TINYINT NOT NULL DEFAULT '0', - poster_time INT UNSIGNED NOT NULL DEFAULT '0', - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - subject VARCHAR(255) NOT NULL DEFAULT '', - smileys_enabled TINYINT NOT NULL DEFAULT '1', - body mediumtext NOT NULL, - icon VARCHAR(16) NOT NULL DEFAULT 'xx', - locked TINYINT NOT NULL DEFAULT '0', - is_sticky TINYINT NOT NULL DEFAULT '0', - to_list VARCHAR(255) NOT NULL DEFAULT '', - PRIMARY KEY id_draft(id_draft), - UNIQUE idx_id_member (id_member, id_draft, type) -) ENGINE=MyISAM; ----# - ----# Adding draft permissions... ----{ -// We cannot do this twice -if (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 = upgrade_query(" - SELECT id_group, id_board, add_deny, permission - FROM {$db_prefix}board_permissions - WHERE permission = 'post_unapproved_topics'"); - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = "($row[id_group], $row[id_board], 'post_draft', $row[add_deny])"; - } - Db::$db->free_result($request); - - if (!empty($inserts)) - upgrade_query(" - INSERT IGNORE INTO {$db_prefix}board_permissions - (id_group, id_board, permission, add_deny) - VALUES - " . implode(',', $inserts)); - - // Next we find people who can send PMs, and assume they can save pm_drafts as well - $request = upgrade_query(" - SELECT id_group, add_deny, permission - FROM {$db_prefix}permissions - WHERE permission = 'pm_send'"); - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = "($row[id_group], 'pm_draft', $row[add_deny])"; - } - Db::$db->free_result($request); - - if (!empty($inserts)) - upgrade_query(" - INSERT IGNORE INTO {$db_prefix}permissions - (id_group, permission, add_deny) - VALUES - " . implode(',', $inserts)); -} ----} -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('drafts_autosave_enabled', '1'), - ('drafts_show_saved_enabled', '1'), - ('drafts_keep_days', '7'); - -INSERT INTO {$db_prefix}themes - (id_member, id_theme, variable, value) -VALUES - (-1, '1', 'drafts_show_saved_enabled', '1'); ----# - -/******************************************************************************/ ---- Adding support for likes -/******************************************************************************/ ----# Creating likes table. -CREATE TABLE IF NOT EXISTS {$db_prefix}user_likes ( - id_member MEDIUMINT UNSIGNED DEFAULT '0', - content_type CHAR(6) DEFAULT '', - content_id INT UNSIGNED DEFAULT '0', - like_time INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (content_id, content_type, id_member), - INDEX idx_content (content_id, content_type), - INDEX idx_liker (id_member) -) ENGINE=MyISAM; ----# - ----# Adding likes column to the messages table. (May take a while) -ALTER TABLE {$db_prefix}messages -ADD COLUMN likes SMALLINT UNSIGNED NOT NULL DEFAULT '0'; ----# - -/******************************************************************************/ ---- Adding support for mentions -/******************************************************************************/ ----# Creating mentions table -CREATE TABLE IF NOT EXISTS {$db_prefix}mentions ( - content_id INT DEFAULT '0', - content_type VARCHAR(10) DEFAULT '', - id_mentioned INT DEFAULT 0, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, - `time` INT NOT NULL DEFAULT 0, - PRIMARY KEY (content_id, content_type, id_mentioned), - INDEX idx_content (content_id, content_type), - INDEX idx_mentionee (id_member) -) ENGINE=MyISAM; ----# - -/******************************************************************************/ ---- Adding support for group-based board moderation -/******************************************************************************/ ----# Creating moderator_groups table -CREATE TABLE IF NOT EXISTS {$db_prefix}moderator_groups ( - id_board SMALLINT UNSIGNED DEFAULT '0', - id_group SMALLINT UNSIGNED DEFAULT '0', - PRIMARY KEY (id_board, id_group) -) ENGINE=MyISAM; ----# - -/******************************************************************************/ ---- Cleaning up integration hooks -/******************************************************************************/ ----# Deleting integration hooks -DELETE FROM {$db_prefix}settings -WHERE variable LIKE 'integrate_%'; ----# - -/******************************************************************************/ ---- Cleaning up old settings -/******************************************************************************/ ----# Fixing a deprecated option. -UPDATE {$db_prefix}settings -SET value = 'option_css_resize' -WHERE variable = 'avatar_action_too_large' - AND (value = 'option_html_resize' OR value = 'option_js_resize'); ----# - ----# Cleaning up the old Core Features page. ----{ - // First get the original value - $request = Db::$db->query( - 'SELECT value - FROM {db_prefix}settings - WHERE variable = {literal:admin_features}' - ); - if (Db::$db->num_rows($request) > 0 && $row = Db::$db->fetch_assoc($request)) - { - // Some of these *should* already be set but you never know. - $new_settings = array(); - $admin_features = explode(',', $row['value']); - - // 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)) - { - $new_settings[] = array('adminlog_enabled', '1'); - $new_settings[] = array('userlog_enabled', '1'); - } - - // Post moderation - if (in_array('pm', $admin_features)) - { - $new_settings[] = array('postmod_active', '1'); - } - - // And now actually apply it. - if (!empty($new_settings)) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - $new_settings, - array('variable') - ); - } - } - Db::$db->free_result($request); ----} ----# - ----# Cleaning up old settings. -DELETE FROM {$db_prefix}settings -WHERE variable IN ('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'); ----# - ----# Cleaning up old theme settings. -DELETE FROM {$db_prefix}themes -WHERE variable IN ('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'); ----# - ----# Update the SM Stat collection. ----{ - // First get the original value - $request = Db::$db->query( - 'SELECT value - FROM {db_prefix}settings - WHERE variable = {literal:allow_sm_stats}' - ); - if (Db::$db->num_rows($request) > 0 && $row = Db::$db->fetch_assoc($request)) - { - if (!empty($row['value'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array( - array('sm_stats_key', $row['value']), - array('enable_sm_stats', '1'), - ), - array('variable') - ); - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {literal:allow_sm_stats}'); - } - } - Db::$db->free_result($request); ----} ----# - -/******************************************************************************/ ---- Updating files that fetched from simplemachines.org -/******************************************************************************/ ----# We no longer call on several files. -DELETE FROM {$db_prefix}admin_info_files -WHERE filename IN ('latest-packages.js', 'latest-smileys.js', 'latest-support.js', 'latest-themes.js') - AND path = '/smf/'; ----# - ----# But we do need new files. ----{ -// Don't insert the info if it's already there... -$file_check = Db::$db->query( - 'SELECT id_file - FROM {db_prefix}admin_info_files - WHERE filename = {string:latest-versions}', - array( - 'latest-versions' => 'latest-versions.txt', - ) -); - -if (Db::$db->num_rows($file_check) == 0) -{ - Db::$db->insert('', - '{db_prefix}admin_info_files', - array('filename' => 'string', 'path' => 'string', 'parameters' => 'string', 'data' => 'string', 'filetype' => 'string'), - array(['latest-versions.txt', '/smf/', 'version=%3$s', '', 'text/plain']), - array('id_file') - ); -} - -Db::$db->free_result($file_check); ----} ----# - -/******************************************************************************/ ---- Upgrading "verification questions" feature -/******************************************************************************/ ----# Creating qanda table -CREATE TABLE IF NOT EXISTS {$db_prefix}qanda ( - id_question SMALLINT UNSIGNED AUTO_INCREMENT, - lngfile VARCHAR(255) NOT NULL DEFAULT '', - question VARCHAR(255) NOT NULL DEFAULT '', - answers TEXT NOT NULL, - PRIMARY KEY (id_question), - INDEX idx_lngfile (lngfile) -) ENGINE=MyISAM; ----# - ----# Moving questions and answers to the new table ----{ -$questions = array(); -$get_questions = upgrade_query(" - SELECT body AS question, recipient_name AS answer - FROM {$db_prefix}log_comments - WHERE comment_type = 'ver_test'"); - - while ($row = Db::$db->fetch_assoc($get_questions)) - $questions[] = array($upcontext['language'], $row['question'], serialize(array($row['answer']))); - - Db::$db->free_result($get_questions); - - if (!empty($questions)) - { - Db::$db->insert('', - '{db_prefix}qanda', - array('lngfile' => 'string', 'question' => 'string', 'answers' => 'string'), - $questions, - array('id_question') - ); - - // Delete the questions from log_comments now - upgrade_query(" - DELETE FROM {$db_prefix}log_comments - WHERE comment_type = 'ver_test' - "); - } ----} ----# - -/******************************************************************************/ ---- Marking packages as uninstalled... -/******************************************************************************/ ----# Updating log_packages -UPDATE {$db_prefix}log_packages -SET install_state = 0; ----# - -/******************************************************************************/ ---- Updating profile permissions... -/******************************************************************************/ ----# Removing the old "view your own profile" permission -DELETE FROM {$db_prefix}permissions -WHERE permission = 'profile_view_own'; ----# - ----# Updating the old "view any profile" permission -UPDATE {$db_prefix}permissions -SET permission = 'profile_view' -WHERE permission = 'profile_view_any'; ----# - ----# Removing the old notification permissions -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'mark_notify' OR permission = 'mark_any_notify'; ----# - ----# Removing the send-topic permission -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'send_topic'; ----# - ----# Removing the draft "autosave" permissions -DELETE FROM {$db_prefix}permissions -WHERE permission = 'post_autosave_draft' OR permission = 'pm_autosave_draft'; - -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'post_autosave_draft'; ----# - ----# Adding "profile_password_own" ----{ -$inserts = array(); - -$request = upgrade_query(" - SELECT id_group, add_deny - FROM {$db_prefix}permissions - WHERE permission = 'profile_identity_own'"); - - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = "($row[id_group], 'profile_password_own', $row[add_deny])"; - } - - Db::$db->free_result($request); - - if (!empty($inserts)) - { - upgrade_query(" - INSERT INTO {$db_prefix}permissions - (id_group, permission, add_deny) - VALUES - " . implode(',', $inserts) - ); - } ----} ----# - ----# Adding "view_warning_own" and "view_warning_any" permissions ----{ -if (isset(Config::$modSettings['warning_show'])) -{ - $can_view_warning_own = array(); - $can_view_warning_any = array(); - - if (Config::$modSettings['warning_show'] >= 1) - { - $can_view_warning_own[] = 0; - - $request = Db::$db->query( - 'SELECT id_group - FROM {db_prefix}membergroups - WHERE min_posts = {int:not_post_based}', - array( - 'not_post_based' => -1, - ) - ); - while ($row = Db::$db->fetch_assoc($request)) - { - if (in_array($row['id_group'], array(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 = Db::$db->query( - 'SELECT id_group, add_deny - FROM {db_prefix}permissions - WHERE permission = {string:perm}', - array( - 'perm' => 'issue_warning', - ) - ); - while ($row = Db::$db->fetch_assoc($request)) - { - if (in_array($row['id_group'], array(-1, 1, 3)) || $row['add_deny'] != 1) - continue; - - $can_view_warning_any[] = $row['id_group']; - } - Db::$db->free_result($request); - } - - $inserts = array(); - - foreach ($can_view_warning_own as $id_group) - $inserts[] = array($id_group, 'view_warning_own', 1); - - foreach ($can_view_warning_any as $id_group) - $inserts[] = array($id_group, 'view_warning_any', 1); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}permissions', - array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), - $inserts, - array('id_group', 'permission') - ); - } - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {string:warning_show}', - array( - 'warning_show' => 'warning_show', - ) - ); -} ----} ----# - ----# Adding other profile permissions ----{ -$inserts = array(); - -$request = upgrade_query(" - SELECT id_group, add_deny - FROM {$db_prefix}permissions - WHERE permission = '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)) - { - upgrade_query(" - INSERT INTO {$db_prefix}permissions - (id_group, permission, add_deny) - VALUES - " . implode(',', $inserts) - ); - } ----} ----# - -/******************************************************************************/ ---- Upgrading PM labels... -/******************************************************************************/ ----# Adding pm_labels table... -CREATE TABLE IF NOT EXISTS {$db_prefix}pm_labels ( - id_label INT UNSIGNED AUTO_INCREMENT, - id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', - name VARCHAR(30) NOT NULL DEFAULT '', - PRIMARY KEY (id_label) -) ENGINE=MyISAM; ----# - ----# Adding pm_labeled_messages table... -CREATE TABLE IF NOT EXISTS {$db_prefix}pm_labeled_messages ( - id_label INT UNSIGNED NOT NULL DEFAULT '0', - id_pm INT UNSIGNED NOT NULL DEFAULT '0', - PRIMARY KEY (id_label, id_pm) -) ENGINE=MyISAM; ----# - ----# Adding "in_inbox" column to pm_recipients -ALTER TABLE {$db_prefix}pm_recipients -ADD COLUMN in_inbox TINYINT NOT NULL DEFAULT '1'; ----# - ----# Moving label info to new tables and updating rules (May be slow!!!) ----{ - // First see if we still have a message_labels column - $results = Db::$db->list_columns('{db_prefix}members'); - if (in_array('message_labels', $results)) - { - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Moving pm labels'; - $step_progress['current'] = $_GET['a']; - - $request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}members - WHERE message_labels != {string:blank}', - array( - 'blank' => '', - ) - ); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - if ($maxMembers > 0) - { - $limit = 5000; - $is_done = false; - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // 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:start}, {int:limit}', - array( - 'blank' => '', - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - - $label_info = array(); - $member_list = array(); - 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[] = array($id_member, $label); - } - } - - if (!empty($inserts)) - { - Db::$db->insert('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array()); - - // Clear this out for our next query below - $inserts = array(); - } - - // 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})', - array( - '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})', - array( - 'member_list' => $member_list, - ) - ); - - $label_info_2 = array(); - 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})', - array( - '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[] = array($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', array('id_pm' => 'int', 'id_label' => 'int'), $inserts, array()); - } - - // 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})', - array( - '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}', - array( - '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})', - array( - 'blank' => '', - 'member_list' => $member_list, - ) - ); - - Db::$db->free_result($get_pm_rules); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - - // Lastly, we drop the old columns - Db::$db->remove_column('{db_prefix}members', 'message_labels'); - Db::$db->remove_column('{db_prefix}pm_recipients', 'labels'); - } - } - unset($_GET['a']); ----} ----# - -/******************************************************************************/ ---- Adding support for edit reasons (May take a while) -/******************************************************************************/ ----# Adding "modified_reason" column to messages (May take a while) -ALTER TABLE {$db_prefix}messages -ADD COLUMN modified_reason VARCHAR(255) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Cleaning up guest permissions -/******************************************************************************/ ----# Removing permissions guests can no longer have... ----{ - $illegal_board_permissions = array( - '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' - ); - - $illegal_permissions = array('calendar_edit_any', 'moderate_board', 'moderate_forum', 'send_email_to_members'); - - Db::$db->query( - 'DELETE FROM {db_prefix}board_permissions - WHERE id_group = {int:guests} - AND permission IN ({array_string:illegal_board_perms})', - array( - 'guests' => -1, - 'illegal_board_perms' => $illegal_board_permissions, - ) - ); - - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE id_group = {int:guests} - AND permission IN ({array_string:illegal_perms})', - array( - 'guests' => -1, - 'illegal_perms' => $illegal_permissions, - ) - ); ----} ----# - -/******************************************************************************/ ---- Adding mail queue settings -/******************************************************************************/ ----# Adding DEFAULT settings for the mail queue ----{ - if (empty(Config::$modSettings['mail_limit'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('mail_limit', '5'), - array('mail_quantity', '5'), - ), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Adding gravatar settings -/******************************************************************************/ ----# Adding DEFAULT gravatar settings ----{ - if (empty(Config::$modSettings['gravatarEnabled'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('gravatarEnabled', '1'), - array('gravatarOverride', '0'), - array('gravatarAllowExtraEmail', '1'), - array('gravatarMaxRating', 'PG'), - ), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Adding timezone support -/******************************************************************************/ ----# Adding the "timezone" column to the members table -ALTER TABLE {$db_prefix}members ADD timezone VARCHAR(80) NOT NULL DEFAULT ''; ----# - ----# Converting time offset to timezone ----{ - 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, date_format($now, 'I'))) !== false) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('default_timezone', $new_tzid), - ), - array('variable') - ); - - Config::$modSettings['default_timezone'] = $new_tzid; - } - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {literal:time_offset}', - array() - ); - } ----} ----# - -/******************************************************************************/ ---- Cleaning up old email settings -/******************************************************************************/ ----# Removing the "send_email_to_members" permission ----{ - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {literal:send_email_to_members}', - array() - ); ----} ----# - ----# Dropping the "hide_email" column from the members table -ALTER TABLE {$db_prefix}members -DROP hide_email; ----# - ----# Dropping the "email_address" column from log_reported_comments -ALTER TABLE {$db_prefix}log_reported_comments -DROP email_address; ----# - -/******************************************************************************/ ---- Deleting the "Auto Optimize" task -/******************************************************************************/ ----# Removing the task and associated data -DELETE FROM {$db_prefix}scheduled_tasks -WHERE id_task = '2'; - -DELETE FROM {$db_prefix}log_scheduled_tasks -WHERE id_task = '2'; - -DELETE FROM {$db_prefix}settings -WHERE variable = 'autoOptMaxOnline'; ----# - -/******************************************************************************/ ---- Removing OpenID-related things... -/******************************************************************************/ ----# Removing the openid_uri column in the members table -ALTER TABLE {$db_prefix}members -DROP openid_uri; ----# - ----# Dropping the openid_assoc table -DROP TABLE IF EXISTS {$db_prefix}openid_assoc; ----# - ----# Removing related settings -DELETE FROM {$db_prefix}settings -WHERE variable='enableOpenID' OR variable='dh_keys'; ----# - -/******************************************************************************/ ---- Fixing the url column in the log_spider_hits and log_online tables -/******************************************************************************/ ----# Changing url column size in log_spider_hits from 255 to 1024 -ALTER TABLE {$db_prefix}log_spider_hits -CHANGE `url` `url` VARCHAR(1024) NOT NULL DEFAULT ''; ----# - ----# Changing url column in log_online from TEXT to VARCHAR(1024) -ALTER TABLE {$db_prefix}log_online -CHANGE `url` `url` VARCHAR(2048) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Adding support for 2FA -/******************************************************************************/ ----# Adding the secret column to members table -ALTER TABLE {$db_prefix}members -ADD tfa_secret VARCHAR(24) NOT NULL DEFAULT ''; ----# - ----# Adding the backup column to members table -ALTER TABLE {$db_prefix}members -ADD tfa_backup VARCHAR(64) NOT NULL DEFAULT ''; ----# - ----# Force 2FA per membergroup -ALTER TABLE {$db_prefix}membergroups -ADD COLUMN tfa_required TINYINT NOT NULL DEFAULT '0'; ----# - - ----# Add tfa_mode setting ----{ - if (!isset(Config::$modSettings['tfa_mode'])) - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['tfa_mode', '1']), - array('variable') - ); ----} ----# - -/******************************************************************************/ ---- Remove redundant indexes -/******************************************************************************/ ----# Duplicates to messages_current_topic -DROP INDEX idx_id_topic on {$db_prefix}messages; -DROP INDEX idx_topic on {$db_prefix}messages; ----# - ----# Duplicate to topics_last_message_sticky and topics_board_news -DROP INDEX idx_id_board on {$db_prefix}topics; ----# - -/******************************************************************************/ ---- Update ban ip with ipv6 support -/******************************************************************************/ ----# Add columns to ban_items -ALTER TABLE {$db_prefix}ban_items -ADD COLUMN ip_low varbinary(16), -ADD COLUMN ip_high varbinary(16); ----# - ----# Convert data for ban_items -UPDATE IGNORE {$db_prefix}ban_items -SET ip_low = - UNHEX( - hex( - INET_ATON(concat(ip_low1,'.',ip_low2,'.',ip_low3,'.',ip_low4)) - ) - ), -ip_high = - UNHEX( - hex( - INET_ATON(concat(ip_high1,'.',ip_high2,'.',ip_high3,'.',ip_high4)) - ) - ) -where ip_low1 > 0; ----# - ----# Create new index on ban_items -CREATE INDEX idx_ban_items_iplow_high ON {$db_prefix}ban_items(ip_low,ip_high); ----# - ----# Dropping columns from ban_items -ALTER TABLE {$db_prefix}ban_items -DROP ip_low1, -DROP ip_low2, -DROP ip_low3, -DROP ip_low4, -DROP ip_high1, -DROP ip_high2, -DROP ip_high3, -DROP ip_high4; ----# - -/******************************************************************************/ ---- Update log_action ip with ipv6 support without converting -/******************************************************************************/ ----# Remove the old ip column ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_actions', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}log_actions DROP COLUMN ip;"); ----} ----# - ----# Add the new one -ALTER TABLE {$db_prefix}log_actions ADD COLUMN ip VARBINARY(16); ----# - -/******************************************************************************/ ---- Update log_banned ip with ipv6 support without converting -/******************************************************************************/ ----# Delete old column log banned ip ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_banned', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}log_banned DROP COLUMN ip;"); ----} ----# - ----# Add the new log banned ip -ALTER TABLE {$db_prefix}log_banned ADD COLUMN ip VARBINARY(16); ----# - -/******************************************************************************/ ---- Update log_errors ip with ipv6 support -/******************************************************************************/ ----# Delete old log errors ip column ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_errors', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}log_errors DROP COLUMN ip;"); ----} ----# - ----# Add the new ip columns to log errors -ALTER TABLE {$db_prefix}log_errors ADD COLUMN ip VARBINARY(16); ----# - ----# Add the ip index for log errors -CREATE INDEX idx_ip ON {$db_prefix}log_errors (ip); ----# - -/******************************************************************************/ ---- Update members ip with ipv6 support -/******************************************************************************/ ----# Rename old ip columns on members ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}members', 'member_ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) -{ - upgrade_query("ALTER TABLE {$db_prefix}members CHANGE member_ip member_ip_old varchar(200);"); - upgrade_query("ALTER TABLE {$db_prefix}members CHANGE member_ip2 member_ip2_old varchar(200);"); -} ----} ----# - ----# Add the new ip columns to members -ALTER TABLE {$db_prefix}members -ADD COLUMN member_ip VARBINARY(16), -ADD COLUMN member_ip2 VARBINARY(16); ----# - ----# Create an ip index for old ips ----{ -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('member_ip_old', $results)) -{ - upgrade_query("CREATE INDEX {$db_prefix}temp_old_ip ON {$db_prefix}members (member_ip_old);"); - upgrade_query("CREATE INDEX {$db_prefix}temp_old_ip2 ON {$db_prefix}members (member_ip2_old);"); -} ----} ----# - ----# Initialize new ip columns ----{ -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('member_ip_old', $results)) -{ - upgrade_query("UPDATE {$db_prefix}members SET member_ip = '', member_ip2 = '';"); -} ----} ----# - ----# Convert member ips ----{ -MySQLConvertOldIp('members','member_ip_old','member_ip'); ----} ----# - ----# Convert member ips2 ----{ -MySQLConvertOldIp('members','member_ip2_old','member_ip2'); ----} ----# - ----# Remove the temporary ip indexes -DROP INDEX temp_old_ip on {$db_prefix}members; -DROP INDEX temp_old_ip2 on {$db_prefix}members; ----# - ----# Remove the old member columns -ALTER TABLE {$db_prefix}members DROP COLUMN member_ip_old; -ALTER TABLE {$db_prefix}members DROP COLUMN member_ip2_old; ----# - -/******************************************************************************/ ---- Update messages poster_ip with ipv6 support (May take a while) -/******************************************************************************/ ----# Rename old ip column on messages ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}messages', 'poster_ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}messages CHANGE poster_ip poster_ip_old varchar(255);"); ----} ----# - ----# Add the new ip column to messages -ALTER TABLE {$db_prefix}messages ADD COLUMN poster_ip VARBINARY(16); ----# - ----# Create an ip index for old ips ----{ -$doChange = true; -$results = Db::$db->list_columns('{db_prefix}messages'); -if (!in_array('poster_ip_old', $results)) - $doChange = false; - -if ($doChange) - upgrade_query("CREATE INDEX {$db_prefix}temp_old_poster_ip ON {$db_prefix}messages (poster_ip_old);"); ----} ----# - ----# Initialize new ip column ----{ -$results = Db::$db->list_columns('{db_prefix}messages'); -if (in_array('poster_ip_old', $results)) -{ - upgrade_query("UPDATE {$db_prefix}messages SET poster_ip = '';"); -} ----} ----# - ----# Convert ips on messages ----{ -MySQLConvertOldIp('messages','poster_ip_old','poster_ip'); ----} ----# - ----# Remove the temporary ip indexes -DROP INDEX temp_old_poster_ip on {$db_prefix}messages; ----# - ----# Drop old column to messages -ALTER TABLE {$db_prefix}messages DROP COLUMN poster_ip_old; ----# - ----# Add the index again to messages poster ip topic -CREATE INDEX idx_ip_index ON {$db_prefix}messages (poster_ip, id_topic); ----# - ----# Add the index again to messages poster ip msg -CREATE INDEX idx_related_ip ON {$db_prefix}messages (id_member, poster_ip, id_msg); ----# - -/******************************************************************************/ ---- Update log_floodcontrol ip with ipv6 support without converting -/******************************************************************************/ ----# Prep floodcontrol ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_floodcontrol', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) -{ - upgrade_query("TRUNCATE TABLE {$db_prefix}log_floodcontrol;"); - upgrade_query("ALTER TABLE {$db_prefix}log_floodcontrol DROP PRIMARY KEY;"); - upgrade_query("ALTER TABLE {$db_prefix}log_floodcontrol DROP COLUMN ip;"); -} ----} ----# - ----# Add the new floodcontrol ip column -ALTER TABLE {$db_prefix}log_floodcontrol ADD COLUMN ip VARBINARY(16); ----# - ----# Modify log_type size -ALTER TABLE {$db_prefix}log_floodcontrol MODIFY log_type VARCHAR(30) NOT NULL DEFAULT 'post'; ----# - ----# Create primary key for floodcontrol -ALTER TABLE {$db_prefix}log_floodcontrol ADD PRIMARY KEY (ip,log_type); ----# - -/******************************************************************************/ ---- Update log_online ip with ipv6 support without converting -/******************************************************************************/ ----# Delete the old ip column for log online ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_online', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}log_online DROP COLUMN ip;"); ----} ----# - ----# Add the new ip column for log online -ALTER TABLE {$db_prefix}log_online ADD COLUMN ip VARBINARY(16); ----# - -/******************************************************************************/ ---- Update log_reported_comments member_ip with ipv6 support without converting -/******************************************************************************/ ----# Drop old ip column for reported comments ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}log_reported_comments', 'member_ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) - upgrade_query("ALTER TABLE {$db_prefix}log_reported_comments DROP COLUMN member_ip;"); ----} ----# - ----# Add the new ip column for reported comments -ALTER TABLE {$db_prefix}log_reported_comments ADD COLUMN member_ip VARBINARY(16); ----# - -/******************************************************************************/ ---- Update member_logins ip with ipv6 support without converting -/******************************************************************************/ ----# Drop old ip columns for member logins ----{ -$doChange = true; -$column_info = upgradeGetColumnInfo('{db_prefix}member_logins', 'ip'); -if (stripos($column_info['type'], 'varbinary') !== false) - $doChange = false; - -if ($doChange) -{ - upgrade_query("ALTER TABLE {$db_prefix}member_logins DROP COLUMN ip;"); - upgrade_query("ALTER TABLE {$db_prefix}member_logins DROP COLUMN ip2;"); -} ----} ----# - ----# Add the new ip columns for member logins -ALTER TABLE {$db_prefix}member_logins ADD COLUMN ip VARBINARY(16); -ALTER TABLE {$db_prefix}member_logins ADD COLUMN ip2 VARBINARY(16); ----# - -/******************************************************************************/ ---- Renaming the "profile_other" permission... -/******************************************************************************/ ----# Changing the "profile_other" permission to "profile_website" -UPDATE {$db_prefix}permissions SET permission = 'profile_website_own' WHERE permission = 'profile_other_own'; -UPDATE {$db_prefix}permissions SET permission = 'profile_website_any' WHERE permission = 'profile_other_any'; ----# - -/******************************************************************************/ ---- Migrating pm notification settings -/******************************************************************************/ ----# Upgrading pm notification settings ----{ -// First see if we still have a pm_email_notify column -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('pm_email_notify', $results)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Upgrading pm notification settings'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // Skip errors here so we don't croak if the columns don't exist... - $request = Db::$db->query( - 'SELECT id_member, pm_email_notify - FROM {db_prefix}members - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_member'], 'pm_new', !empty($row['pm_email_notify']) ? 2 : 0); - $inserts[] = array($row['id_member'], 'pm_notify', $row['pm_email_notify'] == 2 ? 2 : 1); - } - Db::$db->free_result($request); - } - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - unset($_GET['a']); -} ----} ----# - ----# drop column pm_email_notify on table members -ALTER TABLE {$db_prefix}members DROP COLUMN pm_email_notify; ----# - -/******************************************************************************/ ---- Adding support for start and end times on calendar events -/******************************************************************************/ ----# Add start_time end_time, and timezone columns to calendar table -ALTER TABLE {$db_prefix}calendar -ADD COLUMN start_time time, -ADD COLUMN end_time time, -ADD COLUMN timezone VARCHAR(80); ----# - ----# Update cal_maxspan and drop obsolete cal_allowspan setting ----{ - if (!isset(Config::$modSettings['cal_allowspan'])) - $cal_maxspan = 0; - elseif (Config::$modSettings['cal_allowspan'] == false) - $cal_maxspan = 1; - else - $cal_maxspan = (Config::$modSettings['cal_maxspan'] > 1) ? Config::$modSettings['cal_maxspan'] : 0; - - upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '$cal_maxspan' - WHERE variable = 'cal_maxspan'"); - - if (isset(Config::$modSettings['cal_allowspan'])) - upgrade_query(" - DELETE FROM {$db_prefix}settings - WHERE variable = 'cal_allowspan'"); ----} ----# - -/******************************************************************************/ ---- Adding location support for calendar events -/******************************************************************************/ ----# Add location column to calendar table -ALTER TABLE {$db_prefix}calendar -ADD COLUMN location VARCHAR(255) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Updating various calendar settings -/******************************************************************************/ ----# Update the max year for the calendar -UPDATE {$db_prefix}settings -SET value = '2030' -WHERE variable = 'cal_maxyear'; ----# - ----# Adding various calendar settings -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('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'); ----# - -/******************************************************************************/ ---- Cleaning up after old UTF-8 languages -/******************************************************************************/ ----# Update the members' languages -UPDATE {$db_prefix}members -SET lngfile = REPLACE(lngfile, '-utf8', ''); ----# - -/******************************************************************************/ ---- Create index for messages likes -/******************************************************************************/ ----# Add Index for messages likes -DROP INDEX idx_likes ON {$db_prefix}messages; -CREATE INDEX idx_likes ON {$db_prefix}messages (likes); ----# - -/******************************************************************************/ ---- Aligning legacy column data -/******************************************************************************/ ----# Updating board_permissions -ALTER TABLE {$db_prefix}board_permissions -MODIFY COLUMN id_profile SMALLINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating log_digest id_topic -ALTER TABLE {$db_prefix}log_digest -MODIFY COLUMN id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating log_digest id_msg -ALTER TABLE {$db_prefix}log_digest -MODIFY COLUMN id_msg INT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating log_reported -ALTER TABLE {$db_prefix}log_reported -MODIFY COLUMN body MEDIUMTEXT NOT NULL; ----# - ----# Updating log_spider_hits -ALTER TABLE {$db_prefix}log_spider_hits -MODIFY COLUMN processed TINYINT NOT NULL DEFAULT '0'; ----# - ----# Updating members new_pm -ALTER TABLE {$db_prefix}members -MODIFY COLUMN new_pm TINYINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating members pm_ignore_list -ALTER TABLE {$db_prefix}members -MODIFY COLUMN pm_ignore_list TEXT NULL; ----# - ----# Updating password_salt -ALTER TABLE {$db_prefix}members -MODIFY COLUMN password_salt VARCHAR(255) NOT NULL DEFAULT ''; ----# - ----# Updating member_logins id_member -ALTER TABLE {$db_prefix}member_logins -MODIFY COLUMN id_member MEDIUMINT NOT NULL DEFAULT '0'; ----# - ----# Updating member_logins time -ALTER TABLE {$db_prefix}member_logins -MODIFY COLUMN time INT NOT NULL DEFAULT '0'; ----# - ----# Updating pm_recipients is_new -ALTER TABLE {$db_prefix}pm_recipients -MODIFY COLUMN is_new TINYINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating pm_rules id_member -ALTER TABLE {$db_prefix}pm_rules -MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating polls guest_vote -ALTER TABLE {$db_prefix}polls -MODIFY COLUMN guest_vote TINYINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating polls id_member -ALTER TABLE {$db_prefix}polls -MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT '0'; ----# - ----# Updating sessions last_update -ALTER TABLE {$db_prefix}sessions -MODIFY COLUMN last_update INT UNSIGNED NOT NULL DEFAULT '0'; ----# - -/******************************************************************************/ ---- Clean up indexes -/******************************************************************************/ ----# Updating log_actions -ALTER TABLE {$db_prefix}log_actions -ADD INDEX id_topic_id_log (id_topic, id_log); ----# - ----# Updating log_activity mostOn -ALTER TABLE {$db_prefix}log_activity -DROP INDEX mostOn; ----# - ----# Updating log_activity most_on -ALTER TABLE {$db_prefix}log_activity -DROP INDEX most_on; ----# - ----# Updating log_subscribed -ALTER TABLE {$db_prefix}log_subscribed -ADD INDEX status (status); ----# - ----# Updating members email_address -ALTER TABLE {$db_prefix}members -ADD INDEX email_address (email_address); ----# - ----# Updating members drop memberName -ALTER TABLE {$db_prefix}members -DROP INDEX memberName; ----# - ----# Updating members active_real_name (drop) -ALTER TABLE {$db_prefix}members -DROP INDEX idx_active_real_name; ----# - ----# Updating members active_real_name (add) -ALTER TABLE {$db_prefix}members -ADD INDEX idx_active_real_name (is_activated, real_name); ----# - ----# Updating messages drop old ipIndex -ALTER TABLE {$db_prefix}messages -DROP INDEX ipIndex; ----# - ----# Updating messages drop old ip_index -ALTER TABLE {$db_prefix}messages -DROP INDEX ip_index; ----# - ----# Updating messages drop old related_ip -ALTER TABLE {$db_prefix}messages -DROP INDEX related_ip; ----# - ----# Updating messages drop old topic ix -ALTER TABLE {$db_prefix}messages -DROP INDEX topic; ----# - ----# Updating messages drop another old topic ix -ALTER TABLE {$db_prefix}messages -DROP INDEX id_topic; ----# - ----# Updating messages drop approved ix -ALTER TABLE {$db_prefix}messages -DROP INDEX approved; ----# - ----# Updating messages drop approved ix alt name -ALTER TABLE {$db_prefix}messages -DROP INDEX idx_approved; ----# - ----# Updating messages drop id_board ix -ALTER TABLE {$db_prefix}messages -DROP INDEX id_board; ----# - ----# Updating messages drop id_board ix alt name -ALTER TABLE {$db_prefix}messages -DROP INDEX idx_id_board; ----# - ----# Updating messages add new id_board ix -ALTER TABLE {$db_prefix}messages -ADD UNIQUE INDEX idx_id_board (id_board, id_msg, approved); ----# - ----# Updating topics drop old id_board ix -ALTER TABLE {$db_prefix}topics -DROP INDEX id_board; ----# - -/******************************************************************************/ ---- Update smileys -/******************************************************************************/ ----# Adding the new `smiley_files` table -CREATE TABLE IF NOT EXISTS {$db_prefix}smiley_files -( - id_smiley SMALLINT NOT NULL DEFAULT '0', - smiley_set VARCHAR(48) NOT NULL DEFAULT '', - filename VARCHAR(48) NOT NULL DEFAULT '', - PRIMARY KEY (id_smiley, smiley_set) -) ENGINE=MyISAM; ----# - ----# Cleaning up unused smiley sets and adding the lovely new ones ----{ -// 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 = array(); -foreach ($dirs AS $ix => $dir) -{ - if (!empty($setnames[$ix])) - $combined[$dir] = array($setnames[$ix], ''); -} - -// Add our lovely new 2.1 smiley sets if not already there -$combined['fugue'] = array(Lang::$txt['default_fugue_smileyset_name'], 'png'); -$combined['alienine'] = array(Lang::$txt['default_alienine_smileyset_name'], 'png'); - -// Add/fix our 2.0 sets (to correct past problems where these got corrupted) -$combined['default'] = array(Lang::$txt['default_legacy_smileyset_name'], 'gif'); -$combined['aaron'] = array(Lang::$txt['default_aaron_smileyset_name'], 'gif'); -$combined['akyhne'] = array(Lang::$txt['default_akyhne_smileyset_name'], 'gif'); - -// Confirm they exist in the filesystem -$filtered = array(); -foreach ($combined as $dir => $attrs) -{ - if (is_dir(Config::$modSettings['smileys_dir'] . '/' . $dir . '/')) - $filtered[$dir] = $attrs[0]; -} - -// Update the Settings Table... -upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . Db::$db->escape_string(implode(',', array_keys($filtered))) . "' - WHERE variable = 'smiley_sets_known'"); - -upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . Db::$db->escape_string(implode("\n", $filtered)) . "' - WHERE variable = 'smiley_sets_names'"); - -// Populate the smiley_files table -$smileys_columns = Db::$db->list_columns('{db_prefix}smileys'); -if (in_array('filename', $smileys_columns)) -{ - $inserts = array(); - - $request = upgrade_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[] = array($row['id_smiley'], $set, $pathinfo['filename'] . '.' . $ext); - } - } - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}smiley_files', - array('id_smiley' => 'int', 'smiley_set' => 'string-48', 'filename' => 'string-48'), - $inserts, - array('id_smiley', 'smiley_set') - ); - - // Unless something went horrifically wrong, drop the defunct column - if (count($inserts) == Db::$db->affected_rows()) - upgrade_query(" - ALTER TABLE {$db_prefix}smileys - DROP COLUMN filename;"); - } -} - -// 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)) - $newdefault = reset(array_keys($filtered)); - else - $newdefault = ''; - upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . $newdefault . "' - WHERE variable = 'smiley_sets_default'"); -} - ----} ----# - -/******************************************************************************/ ---- Add backtrace to log_error -/******************************************************************************/ ----# add backtrace column -ALTER TABLE {$db_prefix}log_errors -ADD COLUMN backtrace varchar(10000) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Update permissions system board_permissions_view -/******************************************************************************/ ----# Create table board_permissions_view -CREATE TABLE IF NOT EXISTS {$db_prefix}board_permissions_view -( - id_group SMALLINT NOT NULL DEFAULT '0', - id_board SMALLINT UNSIGNED NOT NULL, - deny smallint NOT NULL, - PRIMARY KEY (id_group, id_board, deny) -) ENGINE=MyISAM; - ----# upgrade check ----{ - // 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'); -$upcontext['skip_db_substeps'] = !in_array('id_group', $table_columns) || !in_array('member_groups', $table_columns2) || !in_array('deny_member_groups', $table_columns2); ----} ----# - ----# -TRUNCATE {$db_prefix}board_permissions_view; ----# - ----# Update board_permissions_view table with membergroups -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) 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); ----# - ----# Update board_permissions_view table with -1 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, -1, 0 -FROM {$db_prefix}boards b -where (FIND_IN_SET(-1, b.member_groups) != 0); ----# - ----# Update board_permissions_view table with 0 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, 0, 0 -FROM {$db_prefix}boards b -where (FIND_IN_SET(0, b.member_groups) != 0); ----# - ----# Update deny board_permissions_view table with membergroups -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) 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); ----# - ----# Update deny board_permissions_view table with -1 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, -1, 1 -FROM {$db_prefix}boards b -where (FIND_IN_SET(-1, b.deny_member_groups) != 0); ----# - ----# Update deny board_permissions_view table with 0 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, 0, 1 -FROM {$db_prefix}boards b -where (FIND_IN_SET(0, b.deny_member_groups) != 0); ----# - -/******************************************************************************/ ---- Update holidays -/******************************************************************************/ ----# Delete all the dates -DELETE FROM {$db_prefix}calendar_holidays WHERE title in -('Mother''s Day','Father''s Day', 'Summer Solstice', 'Vernal Equinox', 'Winter Solstice', 'Autumnal Equinox', - 'Thanksgiving', 'Memorial Day', 'Labor Day', 'New Year''s', 'Christmas', 'Valentine''s Day', 'St. Patrick''s Day', - 'April Fools', 'Earth Day', 'United Nations Day', 'Halloween', 'Independence Day', 'Cinco de Mayo', 'Flag Day', - 'Veterans Day', 'Groundhog Day', 'D-Day'); ----# - ----# Insert the updated dates -INSERT INTO {$db_prefix}calendar_holidays - (title, event_date) -VALUES ('New Year''s', '1004-01-01'), - ('Christmas', '1004-12-25'), - ('Valentine''s Day', '1004-02-14'), - ('St. Patrick''s Day', '1004-03-17'), - ('April Fools', '1004-04-01'), - ('Earth Day', '1004-04-22'), - ('United Nations Day', '1004-10-24'), - ('Halloween', '1004-10-31'), - ('Mother''s Day', '2010-05-09'), - ('Mother''s Day', '2011-05-08'), - ('Mother''s Day', '2012-05-13'), - ('Mother''s Day', '2013-05-12'), - ('Mother''s Day', '2014-05-11'), - ('Mother''s Day', '2015-05-10'), - ('Mother''s Day', '2016-05-08'), - ('Mother''s Day', '2017-05-14'), - ('Mother''s Day', '2018-05-13'), - ('Mother''s Day', '2019-05-12'), - ('Mother''s Day', '2020-05-10'), - ('Mother''s Day', '2021-05-09'), - ('Mother''s Day', '2022-05-08'), - ('Mother''s Day', '2023-05-14'), - ('Mother''s Day', '2024-05-12'), - ('Mother''s Day', '2025-05-11'), - ('Mother''s Day', '2026-05-10'), - ('Mother''s Day', '2027-05-09'), - ('Mother''s Day', '2028-05-14'), - ('Mother''s Day', '2029-05-13'), - ('Mother''s Day', '2030-05-12'), - ('Father''s Day', '2010-06-20'), - ('Father''s Day', '2011-06-19'), - ('Father''s Day', '2012-06-17'), - ('Father''s Day', '2013-06-16'), - ('Father''s Day', '2014-06-15'), - ('Father''s Day', '2015-06-21'), - ('Father''s Day', '2016-06-19'), - ('Father''s Day', '2017-06-18'), - ('Father''s Day', '2018-06-17'), - ('Father''s Day', '2019-06-16'), - ('Father''s Day', '2020-06-21'), - ('Father''s Day', '2021-06-20'), - ('Father''s Day', '2022-06-19'), - ('Father''s Day', '2023-06-18'), - ('Father''s Day', '2024-06-16'), - ('Father''s Day', '2025-06-15'), - ('Father''s Day', '2026-06-21'), - ('Father''s Day', '2027-06-20'), - ('Father''s Day', '2028-06-18'), - ('Father''s Day', '2029-06-17'), - ('Father''s Day', '2030-06-16'), - ('Summer Solstice', '2010-06-21'), - ('Summer Solstice', '2011-06-21'), - ('Summer Solstice', '2012-06-20'), - ('Summer Solstice', '2013-06-21'), - ('Summer Solstice', '2014-06-21'), - ('Summer Solstice', '2015-06-21'), - ('Summer Solstice', '2016-06-20'), - ('Summer Solstice', '2017-06-20'), - ('Summer Solstice', '2018-06-21'), - ('Summer Solstice', '2019-06-21'), - ('Summer Solstice', '2020-06-20'), - ('Summer Solstice', '2021-06-21'), - ('Summer Solstice', '2022-06-21'), - ('Summer Solstice', '2023-06-21'), - ('Summer Solstice', '2024-06-20'), - ('Summer Solstice', '2025-06-21'), - ('Summer Solstice', '2026-06-21'), - ('Summer Solstice', '2027-06-21'), - ('Summer Solstice', '2028-06-20'), - ('Summer Solstice', '2029-06-21'), - ('Summer Solstice', '2030-06-21'), - ('Vernal Equinox', '2010-03-20'), - ('Vernal Equinox', '2011-03-20'), - ('Vernal Equinox', '2012-03-20'), - ('Vernal Equinox', '2013-03-20'), - ('Vernal Equinox', '2014-03-20'), - ('Vernal Equinox', '2015-03-20'), - ('Vernal Equinox', '2016-03-20'), - ('Vernal Equinox', '2017-03-20'), - ('Vernal Equinox', '2018-03-20'), - ('Vernal Equinox', '2019-03-20'), - ('Vernal Equinox', '2020-03-20'), - ('Vernal Equinox', '2021-03-20'), - ('Vernal Equinox', '2022-03-20'), - ('Vernal Equinox', '2023-03-20'), - ('Vernal Equinox', '2024-03-20'), - ('Vernal Equinox', '2025-03-20'), - ('Vernal Equinox', '2026-03-20'), - ('Vernal Equinox', '2027-03-20'), - ('Vernal Equinox', '2028-03-20'), - ('Vernal Equinox', '2029-03-20'), - ('Vernal Equinox', '2030-03-20'), - ('Winter Solstice', '2010-12-21'), - ('Winter Solstice', '2011-12-22'), - ('Winter Solstice', '2012-12-21'), - ('Winter Solstice', '2013-12-21'), - ('Winter Solstice', '2014-12-21'), - ('Winter Solstice', '2015-12-22'), - ('Winter Solstice', '2016-12-21'), - ('Winter Solstice', '2017-12-21'), - ('Winter Solstice', '2018-12-21'), - ('Winter Solstice', '2019-12-22'), - ('Winter Solstice', '2020-12-21'), - ('Winter Solstice', '2021-12-21'), - ('Winter Solstice', '2022-12-21'), - ('Winter Solstice', '2023-12-22'), - ('Winter Solstice', '2024-12-21'), - ('Winter Solstice', '2025-12-21'), - ('Winter Solstice', '2026-12-21'), - ('Winter Solstice', '2027-12-22'), - ('Winter Solstice', '2028-12-21'), - ('Winter Solstice', '2029-12-21'), - ('Winter Solstice', '2030-12-21'), - ('Autumnal Equinox', '2010-09-23'), - ('Autumnal Equinox', '2011-09-23'), - ('Autumnal Equinox', '2012-09-22'), - ('Autumnal Equinox', '2013-09-22'), - ('Autumnal Equinox', '2014-09-23'), - ('Autumnal Equinox', '2015-09-23'), - ('Autumnal Equinox', '2016-09-22'), - ('Autumnal Equinox', '2017-09-22'), - ('Autumnal Equinox', '2018-09-23'), - ('Autumnal Equinox', '2019-09-23'), - ('Autumnal Equinox', '2020-09-22'), - ('Autumnal Equinox', '2021-09-22'), - ('Autumnal Equinox', '2022-09-23'), - ('Autumnal Equinox', '2023-09-23'), - ('Autumnal Equinox', '2024-09-22'), - ('Autumnal Equinox', '2025-09-22'), - ('Autumnal Equinox', '2026-09-23'), - ('Autumnal Equinox', '2027-09-23'), - ('Autumnal Equinox', '2028-09-22'), - ('Autumnal Equinox', '2029-09-22'), - ('Autumnal Equinox', '2030-09-22'); - -INSERT INTO {$db_prefix}calendar_holidays - (title, event_date) -VALUES ('Independence Day', '1004-07-04'), - ('Cinco de Mayo', '1004-05-05'), - ('Flag Day', '1004-06-14'), - ('Veterans Day', '1004-11-11'), - ('Groundhog Day', '1004-02-02'), - ('Thanksgiving', '2010-11-25'), - ('Thanksgiving', '2011-11-24'), - ('Thanksgiving', '2012-11-22'), - ('Thanksgiving', '2013-11-28'), - ('Thanksgiving', '2014-11-27'), - ('Thanksgiving', '2015-11-26'), - ('Thanksgiving', '2016-11-24'), - ('Thanksgiving', '2017-11-23'), - ('Thanksgiving', '2018-11-22'), - ('Thanksgiving', '2019-11-28'), - ('Thanksgiving', '2020-11-26'), - ('Thanksgiving', '2021-11-25'), - ('Thanksgiving', '2022-11-24'), - ('Thanksgiving', '2023-11-23'), - ('Thanksgiving', '2024-11-28'), - ('Thanksgiving', '2025-11-27'), - ('Thanksgiving', '2026-11-26'), - ('Thanksgiving', '2027-11-25'), - ('Thanksgiving', '2028-11-23'), - ('Thanksgiving', '2029-11-22'), - ('Thanksgiving', '2030-11-28'), - ('Memorial Day', '2010-05-31'), - ('Memorial Day', '2011-05-30'), - ('Memorial Day', '2012-05-28'), - ('Memorial Day', '2013-05-27'), - ('Memorial Day', '2014-05-26'), - ('Memorial Day', '2015-05-25'), - ('Memorial Day', '2016-05-30'), - ('Memorial Day', '2017-05-29'), - ('Memorial Day', '2018-05-28'), - ('Memorial Day', '2019-05-27'), - ('Memorial Day', '2020-05-25'), - ('Memorial Day', '2021-05-31'), - ('Memorial Day', '2022-05-30'), - ('Memorial Day', '2023-05-29'), - ('Memorial Day', '2024-05-27'), - ('Memorial Day', '2025-05-26'), - ('Memorial Day', '2026-05-25'), - ('Memorial Day', '2027-05-31'), - ('Memorial Day', '2028-05-29'), - ('Memorial Day', '2029-05-28'), - ('Memorial Day', '2030-05-27'), - ('Labor Day', '2010-09-06'), - ('Labor Day', '2011-09-05'), - ('Labor Day', '2012-09-03'), - ('Labor Day', '2013-09-02'), - ('Labor Day', '2014-09-01'), - ('Labor Day', '2015-09-07'), - ('Labor Day', '2016-09-05'), - ('Labor Day', '2017-09-04'), - ('Labor Day', '2018-09-03'), - ('Labor Day', '2019-09-02'), - ('Labor Day', '2020-09-07'), - ('Labor Day', '2021-09-06'), - ('Labor Day', '2022-09-05'), - ('Labor Day', '2023-09-04'), - ('Labor Day', '2024-09-02'), - ('Labor Day', '2025-09-01'), - ('Labor Day', '2026-09-07'), - ('Labor Day', '2027-09-06'), - ('Labor Day', '2028-09-04'), - ('Labor Day', '2029-09-03'), - ('Labor Day', '2030-09-02'), - ('D-Day', '1004-06-06'); ----# - -/******************************************************************************/ ---- Add Attachments index -/******************************************************************************/ ----# Create new index on Attachments -CREATE INDEX idx_id_thumb ON {$db_prefix}attachments (id_thumb); ----# - -/******************************************************************************/ ---- Fix mods columns -/******************************************************************************/ ----# make members mod col nullable ----{ -$request = upgrade_query(" - SELECT COLUMN_NAME, COLUMN_TYPE - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = '" . Config::$db_name . "' AND TABLE_NAME = '" . Config::$db_prefix . "members' AND - COLUMN_DEFAULT IS NULL AND COLUMN_KEY <> 'PRI' AND IS_NULLABLE = 'NO' AND - COLUMN_NAME NOT IN ('buddy_list', 'signature', 'ignore_boards') - "); - - -while ($row = Db::$db->fetch_assoc($request)) -{ - upgrade_query(" - ALTER TABLE {$db_prefix}members - MODIFY " . $row['COLUMN_NAME'] . " " . $row['COLUMN_TYPE'] . " NULL - "); -} ----} ----# - ----# make boards mod col nullable ----{ -$request = upgrade_query(" - SELECT COLUMN_NAME, COLUMN_TYPE - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = '" . Config::$db_name . "' AND TABLE_NAME = '" . Config::$db_prefix . "boards' AND - COLUMN_DEFAULT IS NULL AND COLUMN_KEY <> 'PRI' AND IS_NULLABLE = 'NO' AND - COLUMN_NAME NOT IN ('description') - "); - - -while ($row = Db::$db->fetch_assoc($request)) -{ - upgrade_query(" - ALTER TABLE {$db_prefix}boards - MODIFY " . $row['COLUMN_NAME'] . " " . $row['COLUMN_TYPE'] . " NULL - "); -} ----} ----# - ----# make topics mod col nullable ----{ -$request = upgrade_query(" - SELECT COLUMN_NAME, COLUMN_TYPE - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = '" . Config::$db_name . "' AND TABLE_NAME = '" . Config::$db_prefix . "topics' AND - COLUMN_DEFAULT IS NULL AND COLUMN_KEY <> 'PRI' AND IS_NULLABLE = 'NO' - "); - - -while ($row = Db::$db->fetch_assoc($request)) -{ - upgrade_query(" - ALTER TABLE {$db_prefix}topics - MODIFY " . $row['COLUMN_NAME'] . " " . $row['COLUMN_TYPE'] . " NULL - "); -} ----} ----# - -/******************************************************************************/ ---- Update log_spider_stats -/******************************************************************************/ ----# Allow for hyper aggressive crawlers -ALTER TABLE {$db_prefix}log_spider_stats CHANGE page_hits page_hits INT NOT NULL DEFAULT '0'; ----# - -/******************************************************************************/ ---- Update policy & agreement settings -/******************************************************************************/ ----# Strip -utf8 from policy settings ----{ -$utf8_policy_settings = array(); -foreach(Config::$modSettings AS $k => $v) -{ - if ((substr($k, 0, 7) === 'policy_') && (substr($k, -5) === '-utf8')) - $utf8_policy_settings[$k] = $v; -} -$adds = array(); -$deletes = array(); -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)) - { - $adds[] = '(\'policy_' . $language . '\', \'' . Db::$db->escape_string($val) . '\')'; - $deletes[] = '\'' . $var . '\''; - } -} -if (!empty($adds)) -{ - upgrade_query(" - INSERT INTO {$db_prefix}settings (variable, value) - VALUES " . implode(', ', $adds) - ); -} -if (!empty($deletes)) -{ - upgrade_query(" - DELETE FROM {$db_prefix}settings - WHERE variable IN (" . implode(', ', $deletes) . ") - "); -} - ----} ----# - ----# 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); -} - ----} ----# - ----# Fix missing values in log_actions ----{ - $current_substep = !isset($_GET['substep']) ? 0 : (int) $_GET['substep']; - - // Setup progress bar - if (!isset($_GET['total_fixes']) || !isset($_GET['a']) || !isset($_GET['last_action_id'])) - { - $request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}log_actions - WHERE id_member = {int:blank_id} - AND action IN ({array_string:target_actions})', - array( - 'blank_id' => 0, - 'target_actions' => array('policy_accepted', 'agreement_accepted'), - ) - ); - list ($step_progress['total']) = Db::$db->fetch_row($request); - $_GET['total_fixes'] = $step_progress['total']; - Db::$db->free_result($request); - - $_GET['a'] = 0; - $_GET['last_action_id'] = 0; - } - - $step_progress['name'] = 'Fixing missing IDs in log_actions'; - $step_progress['current'] = $_GET['a']; - $step_progress['total'] = $_GET['total_fixes']; - - // Main process loop - $limit = 10000; - $is_done = false; - while (!$is_done) - { - // Keep looping at the current step. - nextSubstep($current_substep); - - $extras = array(); - $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}', - array( - 'blank_id' => 0, - 'target_actions' => array('policy_accepted', 'agreement_accepted'), - 'last' => $_GET['last_action_id'], - 'limit' => $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 - $_GET['last_action_id'] = max(array_keys($extras)); - - foreach ($extras AS $id => $extra_ser) - { - $extra = 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}', - array( - 'id_member' => $extra['applicator'], - 'id_action' => $id, - ) - ); - } - } - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - } - - $step_progress = array(); - unset($_GET['a']); - unset($_GET['last_action_id']); - unset($_GET['total_fixes']); ----} ----# diff --git a/other/upgrade_2-1_PostgreSQL.sql b/other/upgrade_2-1_PostgreSQL.sql deleted file mode 100644 index b82cf84fb6..0000000000 --- a/other/upgrade_2-1_PostgreSQL.sql +++ /dev/null @@ -1,4263 +0,0 @@ -/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ - -/******************************************************************************/ ---- Removing karma -/******************************************************************************/ - ----# Removing all karma data, if selected ----{ -if (!empty($upcontext['delete_karma'])) -{ - // Delete old settings vars. - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable IN ({array_string:karma_vars})', - array( - 'karma_vars' => array('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->query( - 'ALTER TABLE {db_prefix}members - DROP karma_good', - array() - ); - - // Does karma bad was enable? - if (in_array('karma_bad', $member_columns)) - Db::$db->query( - 'ALTER TABLE {db_prefix}members - DROP karma_bad', - array() - ); - - // Cleaning up old karma permissions. - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {string:karma_vars}', - array( - 'karma_vars' => 'karma_edit', - ) - ); - - // Cleaning up old log_karma table - Db::$db->query( - 'DROP TABLE IF EXISTS {db_prefix}log_karma', - array() - ); -} ----} ----# - -/******************************************************************************/ ---- Emptying error log -/******************************************************************************/ - ----# Emptying error log, if selected ----{ -if (!empty($upcontext['empty_error'])) -{ - Db::$db->query( - 'TRUNCATE {db_prefix}log_errors', - array( - ), - identifier: 'truncate_table', - ); -} ----} ----# - -/******************************************************************************/ ---- Fixing sequences -/******************************************************************************/ - ----# Updating old sequences ----{ - $sequences = array( - 'admin_info_files_seq' => array( - 'table' => 'admin_info_files', - 'field' => 'id_file', - ), - 'attachments_seq' => array( - 'table' => 'attachments', - 'field' => 'id_attach', - ), - 'ban_groups_seq' => array( - 'table' => 'ban_groups', - 'field' => 'id_ban_group', - ), - 'ban_items_seq' => array( - 'table' => 'ban_items', - 'field' => 'id_ban', - ), - 'boards_seq' => array( - 'table' => 'boards', - 'field' => 'id_board', - ), - 'calendar_seq' => array( - 'table' => 'calendar', - 'field' => 'id_event', - ), - 'calendar_holidays_seq' => array( - 'table' => 'calendar_holidays', - 'field' => 'id_holiday', - ), - 'categories_seq' => array( - 'table' => 'categories', - 'field' => 'id_cat', - ), - 'custom_fields_seq' => array( - 'table' => 'custom_fields', - 'field' => 'id_field', - ), - 'log_actions_seq' => array( - 'table' => 'log_actions', - 'field' => 'id_action', - ), - 'log_banned_seq' => array( - 'table' => 'log_banned', - 'field' => 'id_ban_log', - ), - 'log_comments_seq' => array( - 'table' => 'log_comments', - 'field' => 'id_comment', - ), - 'log_errors_seq' => array( - 'table' => 'log_errors', - 'field' => 'id_error', - ), - 'log_group_requests_seq' => array( - 'table' => 'log_group_requests', - 'field' => 'id_request', - ), - 'log_member_notices_seq' => array( - 'table' => 'log_member_notices', - 'field' => 'id_notice', - ), - 'log_packages_seq' => array( - 'table' => 'log_packages', - 'field' => 'id_install', - ), - 'log_reported_seq' => array( - 'table' => 'log_reported', - 'field' => 'id_report', - ), - 'log_reported_comments_seq' => array( - 'table' => 'log_reported_comments', - 'field' => 'id_comment', - ), - 'log_scheduled_tasks_seq' => array( - 'table' => 'log_scheduled_tasks', - 'field' => 'id_log', - ), - 'log_spider_hits_seq' => array( - 'table' => 'log_spider_hits', - 'field' => 'id_hit', - ), - 'log_subscribed_seq' => array( - 'table' => 'log_subscribed', - 'field' => 'id_sublog', - ), - 'mail_queue_seq' => array( - 'table' => 'mail_queue', - 'field' => 'id_mail', - ), - 'membergroups_seq' => array( - 'table' => 'membergroups', - 'field' => 'id_group', - ), - 'members_seq' => array( - 'table' => 'members', - 'field' => 'id_member', - ), - 'message_icons_seq' => array( - 'table' => 'message_icons', - 'field' => 'id_icon', - ), - 'messages_seq' => array( - 'table' => 'messages', - 'field' => 'id_msg', - ), - 'package_servers_seq' => array( - 'table' => 'package_servers', - 'field' => 'id_server', - ), - 'permission_profiles_seq' => array( - 'table' => 'permission_profiles', - 'field' => 'id_profile', - ), - 'personal_messages_seq' => array( - 'table' => 'personal_messages', - 'field' => 'id_pm', - ), - 'pm_rules_seq' => array( - 'table' => 'pm_rules', - 'field' => 'id_rule', - ), - 'polls_seq' => array( - 'table' => 'polls', - 'field' => 'id_poll', - ), - 'scheduled_tasks_seq' => array( - 'table' => 'scheduled_tasks', - 'field' => 'id_task', - ), - 'smileys_seq' => array( - 'table' => 'smileys', - 'field' => 'id_smiley', - ), - 'spiders_seq' => array( - 'table' => 'spiders', - 'field' => 'id_spider', - ), - 'subscriptions_seq' => array( - 'table' => 'subscriptions', - 'field' => 'id_subscribe', - ), - 'topics_seq' => array( - 'table' => 'topics', - 'field' => 'id_topic', - ), - ); - - foreach ($sequences as $key => $value) - { - upgrade_query(" - SELECT setval('{$db_prefix}" . $key . "', (SELECT COALESCE(MAX(" . $value['field'] ."),1) FROM {$db_prefix}" . $value['table'] .")) - "); - } ----} ----# - -/******************************************************************************/ ---- add find_in_set function -/******************************************************************************/ ----# add find_in_set function ----{ - upgrade_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'; -"); ----} ----# - -/******************************************************************************/ ---- Fixing dates... -/******************************************************************************/ ----# Updating old values -UPDATE {$db_prefix}calendar -SET start_date = concat_ws('-', 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; - -UPDATE {$db_prefix}calendar -SET end_date = concat_ws('-', 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; - -UPDATE {$db_prefix}calendar_holidays -SET event_date = concat_ws('-', 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; - -UPDATE {$db_prefix}log_spider_stats -SET stat_date = concat_ws('-', 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; - -ALTER TABLE {$db_prefix}log_spider_stats -ALTER stat_date SET DEFAULT '1004-01-01'; - -UPDATE {$db_prefix}members -SET birthdate = concat_ws('-', 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; ----# - ----# Changing default values -ALTER TABLE {$db_prefix}calendar ALTER COLUMN start_date SET DEFAULT '1004-01-01'::date; -ALTER TABLE {$db_prefix}calendar ALTER COLUMN end_date SET DEFAULT '1004-01-01'::date; -ALTER TABLE {$db_prefix}calendar_holidays ALTER COLUMN event_date SET DEFAULT '1004-01-01'::date; -ALTER TABLE {$db_prefix}log_spider_stats ALTER COLUMN stat_date SET DEFAULT '1004-01-01'::date; -ALTER TABLE {$db_prefix}members ALTER COLUMN birthdate SET DEFAULT '1004-01-01'::date; -ALTER TABLE {$db_prefix}log_activity ALTER COLUMN date DROP DEFAULT; ----# - -/******************************************************************************/ ---- Adding new settings... -/******************************************************************************/ - ----# Creating login history sequence. -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}member_logins_seq; ----# - ----# Creating login history table. -CREATE TABLE IF NOT EXISTS {$db_prefix}member_logins ( - id_login int DEFAULT nextval('{$db_prefix}member_logins_seq'), - id_member int NOT NULL DEFAULT '0', - time int NOT NULL DEFAULT '0', - ip inet, - ip2 inet, - PRIMARY KEY (id_login) -); - -DROP INDEX IF EXISTS {$db_prefix}member_logins_id_member; -DROP INDEX IF EXISTS {$db_prefix}member_logins_time; - -CREATE INDEX {$db_prefix}member_logins_id_member ON {$db_prefix}member_logins (id_member); -CREATE INDEX {$db_prefix}member_logins_time ON {$db_prefix}member_logins (time); ----# - ----# Copying the current package backup setting... ----{ -if (!isset(Config::$modSettings['package_make_full_backups']) && isset(Config::$modSettings['package_make_backups'])) - upgrade_query(" - INSERT INTO {$db_prefix}settings - (variable, value) - VALUES - ('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 = upgrade_query(" - SELECT value - FROM {$db_prefix}themes - WHERE variable='allow_no_censored' - AND id_theme = 1 OR id_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) - { - upgrade_query(" - INSERT INTO {$db_prefix}settings - VALUES ('allow_no_censored', 1) - "); - - // Don't do this twice... - break; - } - } -} ----} ----# - ----# Converting collapsed categories... ----{ -// We cannot do this twice -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $request = Db::$db->query( - 'SELECT id_member, id_cat - FROM {db_prefix}collapsed_categories'); - - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - $inserts[] = array($row['id_member'], 1, 'collapse_category_' . $row['id_cat'], $row['id_cat']); - Db::$db->free_result($request); - - if (!empty($inserts)) - Db::$db->insert('replace', - '{db_prefix}themes', - array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), - $inserts, - array('id_theme', 'id_member', 'variable') - ); -} ----} ----# - ----# Parsing board descriptions and names ----{ -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $request = Db::$db->query( - 'SELECT name, description, id_board - FROM {db_prefix}boards'); - - $inserts = array(); - - Db::$db->free_result($request); - - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array( - 'name' => Utils::htmlspecialchars(strip_tags(SMF\Parser::transform($row['name'], SMF\Parser::OUTPUT_BBC))), - 'description' => Utils::htmlspecialchars(strip_tags(SMF\Parser::transform($row['description'], SMF\Parser::OUTPUT_BBC))), - 'id' => $row['id'], - ); - } - - if (!empty($inserts)) - { - foreach ($inserts as $insert) - { - Db::$db->query( - 'UPDATE {db_prefix}boards - SET name = {string:name}, description = {string:description} - WHERE id = {int:id}', - $insert - ); - } - } -} ----} ----# - ----# Dropping "collapsed_categories" -DROP TABLE IF EXISTS {$db_prefix}collapsed_categories; ----# - ----# Adding new "topic_move_any" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('topic_move_any', '1') ON CONFLICT DO NOTHING; ----# - ----# Adding new "enable_ajax_alerts" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('enable_ajax_alerts', '1') ON CONFLICT DO NOTHING; ----# - ----# Adding new "alerts_auto_purge" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('alerts_auto_purge', '30') ON CONFLICT DO NOTHING; ----# - ----# Adding new "minimize_files" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('minimize_files', '1') ON CONFLICT DO NOTHING; ----# - ----# Collapse object -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('additional_options_collapsable', '1') ON CONFLICT DO NOTHING; ----# - ----# Adding new "defaultMaxListItems" setting -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('defaultMaxListItems', '15') ON CONFLICT DO NOTHING; ----# - ----# Adding new "loginHistoryDays" setting ----{ - if (!isset(Config::$modSettings['loginHistoryDays'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['loginHistoryDays', '30']), - array('variable') - ); ----} ----# - ----# Enable some settings we ripped from Theme settings ----{ - $ripped_settings = array('show_modify', 'show_user_images', 'show_blurb', 'show_profile_buttons', 'subject_toggle', 'hide_post_group'); - - $request = Db::$db->query( - 'SELECT variable, value - FROM {db_prefix}themes - WHERE variable IN({array_string:ripped_settings}) - AND id_member = 0 - AND id_theme = 1', - array( - 'ripped_settings' => $ripped_settings, - ) - ); - - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - $inserts[] = array($row['variable'], $row['value']); - - Db::$db->free_result($request); - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - $inserts, - array('variable') - ); ----} ----# - ----# Disable Moderation Center Security if it doesn't exist ----{ - if (!isset(Config::$modSettings['securityDisable_moderate'])) - Db::$db->insert('ignore', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['securityDisable_moderate', '1']), - array('variable') - ); ----} ----# - ----# Adding new profile data export settings -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_dir', '{$boarddir}/exports') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_expiry', '7') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_min_diskspace_pct', '5') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('export_rate', '250') ON CONFLICT DO NOTHING; ----# - ----# Adding settings for marking boards as read ----{ - if (!isset(Config::$modSettings['mark_read_beyond'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_beyond', '90']), - array() - ); - if (!isset(Config::$modSettings['mark_read_delete_beyond'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_delete_beyond', '365']), - array() - ); - if (!isset(Config::$modSettings['mark_read_max_users'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['mark_read_max_users', '500']), - array() - ); ----} ----# - -/******************************************************************************/ ---- Updating legacy attachments... -/******************************************************************************/ - ----# Adding more space to the mime_type column. -ALTER TABLE {$db_prefix}attachments - ALTER COLUMN mime_type TYPE VARCHAR(128); ----# - ----# Converting legacy attachments. ----{ - -// 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 = array(0755, 0775, 0777); - - foreach($chmod_values as $val) - { - // If it's writable, break out of the loop - if (is_writable($custom_av_dir)) - break; - else - @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'); -} - -$request = upgrade_query(" - SELECT COUNT(*) - FROM {$db_prefix}attachments - WHERE attachment_type != 1"); -list ($step_progress['total']) = Db::$db->fetch_row($request); -Db::$db->free_result($request); - -$_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; -$step_progress['name'] = 'Converting legacy attachments'; -$step_progress['current'] = $_GET['a']; - -// 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']); - -// No need to do this if we already did it previously... -if (empty(Config::$modSettings['attachments_21_done'])) - $is_done = false; -else - $is_done = true; - -while (!$is_done) -{ - nextSubStep($substep); - - $request = upgrade_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 $_GET[a], 100"); - - // 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 (!str_starts_with(Db::$db->detect_charset('attachments', 'filename'), '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'], array("\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(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $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'])) - { - upgrade_query(" - UPDATE {$db_prefix}attachments - SET file_hash = '', attachment_type = 1 - WHERE id_attach = $row[id_attach]"); - $_GET['a'] -= 1; - } - } - // 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)) - upgrade_query(" - UPDATE {$db_prefix}attachments - SET file_hash = '$fileHash' - WHERE id_attach = $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'])) - Db::$db->query( - 'UPDATE {db_prefix}attachments - SET mime_type = {string:mime_type} - WHERE id_attach = {int:id_attach}', - array( - 'id_attach' => $row['id_attach'], - 'mime_type' => substr($size['mime'], 0, 20), - ) - ); - } - } - Db::$db->free_result($request); - - $_GET['a'] += 100; - $step_progress['current'] = $_GET['a']; -} - -unset($_GET['a']); ----} ----# - ----# Note attachment conversion complete -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('attachments_21_done', '1') ON CONFLICT DO NOTHING; ----# - ----# Fixing invalid sizes on attachments ----{ -$attachs = array(); -// 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 = Db::$db->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)) - Db::$db->query( - 'UPDATE {db_prefix}attachments - SET width = 0, - height = 0 - WHERE id_attach IN ({array_int:attachs})', - array( - 'attachs' => $attachs, - ) - ); ----} ----# - ----# Fixing attachment directory setting... ----{ -// If it's a directory or an array, ensure it is stored as a serialized string (prep for later serial_to_json conversion) -// Also ensure currentAttachmentUploadDir is set even for single directories -// Make sure to do it in memory and in db... -if (empty(Config::$modSettings['json_done'])) -{ - if (!is_array(Config::$modSettings['attachmentUploadDir']) && is_dir(Config::$modSettings['attachmentUploadDir'])) - { - Config::$modSettings['attachmentUploadDir'] = serialize(array(1 => Config::$modSettings['attachmentUploadDir'])); - Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:attach_dir} - WHERE variable = {string:uploadDir}', - array( - 'attach_dir' => Config::$modSettings['attachmentUploadDir'], - 'uploadDir' => 'attachmentUploadDir' - ) - ); - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['currentAttachmentUploadDir', '1']), - array('variable') - ); - } - elseif (is_array(Config::$modSettings['attachmentUploadDir'])) - { - Config::$modSettings['attachmentUploadDir'] = serialize(Config::$modSettings['attachmentUploadDir']); - Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:attach_dir} - WHERE variable = {string:uploadDir}', - array( - 'attach_dir' => Config::$modSettings['attachmentUploadDir'], - 'uploadDir' => 'attachmentUploadDir' - ) - ); - // Assume currentAttachmentUploadDir is already set - } -} ----} ----# - -/******************************************************************************/ ---- Adding support for logging who fulfils a group request. -/******************************************************************************/ - ----# Adding new columns to log_group_requests -ALTER TABLE {$db_prefix}log_group_requests -ADD COLUMN IF NOT EXISTS status smallint NOT NULL default '0', -ADD COLUMN IF NOT EXISTS id_member_acted int NOT NULL default '0', -ADD COLUMN IF NOT EXISTS member_name_acted varchar(255) NOT NULL default '', -ADD COLUMN IF NOT EXISTS time_acted int NOT NULL default '0', -ADD COLUMN IF NOT EXISTS act_reason text NOT NULL default ''; ----# - ----# Adding new columns to log_group_requests - drop defaults now that existing rows have been set -ALTER TABLE {$db_prefix}log_group_requests -ALTER COLUMN act_reason DROP DEFAULT; ----# - ----# Adjusting the indexes for log_group_requests -DROP INDEX IF EXISTS {$db_prefix}log_group_requests_id_member; -CREATE INDEX {$db_prefix}log_group_requests_id_member ON {$db_prefix}log_group_requests (id_member, id_group); ----# - -/******************************************************************************/ ---- Package Manager New Features -/******************************************************************************/ ----# Adding support for tag in package manager -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN IF NOT EXISTS credits TEXT NOT NULL default ''; ----# - ----# Adding support for - drop default now that existing rows have been set -ALTER TABLE {$db_prefix}log_packages -ALTER COLUMN credits DROP DEFAULT; ----# - ----# Adding support for package hashes -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN IF NOT EXISTS sha256_hash TEXT; ----# - ----# Adding support for validation servers -ALTER TABLE {$db_prefix}package_servers -ADD COLUMN IF NOT EXISTS validation_url VARCHAR(255) DEFAULT '', -ADD COLUMN IF NOT EXISTS extra TEXT; ----# - ----# Add Package Validation to Downloads Site ----{ - $request = Db::$db->query( - 'SELECT id_server - FROM {db_prefix}package_servers - WHERE url LIKE {string:downloads_site}', - array( - '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}package_servers', - array('name' => 'string', 'url' => 'string', 'validation_url' => 'string'), - array(['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}']), - array('id_server') - ); ----} ----# - ----# Ensure The Simple Machines Customize Site is https -UPDATE {$db_prefix}package_servers -SET url = 'https://custom.simplemachines.org/packages/mods' -WHERE url = 'http://custom.simplemachines.org/packages/mods'; ----# - ----# Add validation to Simple Machines Customize Site -UPDATE {$db_prefix}package_servers -SET validation_url = 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}' -WHERE url = 'https://custom.simplemachines.org/packages/mods'; ----# - -/******************************************************************************/ ---- Adding more space for session ids -/******************************************************************************/ ----# Altering the session_id columns... ----{ -upgrade_query(" - ALTER TABLE {$db_prefix}log_online - ALTER COLUMN session type varchar(128); - - ALTER TABLE {$db_prefix}log_errors - ALTER COLUMN session type varchar(128); - - ALTER TABLE {$db_prefix}sessions - ALTER COLUMN session_id type varchar(128);"); - -upgrade_query(" - ALTER TABLE {$db_prefix}log_online - ALTER COLUMN session SET DEFAULT ''; - - ALTER TABLE {$db_prefix}log_errors - ALTER COLUMN session SET default ' ';"); -upgrade_query(" - ALTER TABLE {$db_prefix}log_online - ALTER COLUMN session SET NOT NULL; - - ALTER TABLE {$db_prefix}log_errors - ALTER COLUMN session SET NOT NULL; - - ALTER TABLE {$db_prefix}sessions - ALTER COLUMN session_id SET NOT NULL;"); ----} ----# - -/******************************************************************************/ ---- Adding support for MOVED topics enhancements -/******************************************************************************/ ----# Adding new columns to topics table -ALTER TABLE {$db_prefix}topics -ADD COLUMN IF NOT EXISTS redirect_expires int NOT NULL DEFAULT '0'; - -ALTER TABLE {$db_prefix}topics -ADD COLUMN IF NOT EXISTS id_redirect_topic int NOT NULL DEFAULT '0'; ----# - -/******************************************************************************/ ---- Adding new scheduled tasks -/******************************************************************************/ ----# Adding a new column "callable" to scheduled_tasks table -ALTER TABLE {$db_prefix}scheduled_tasks -ADD COLUMN IF NOT EXISTS callable varchar(60) NOT NULL default ''; ----# - ----# Adding new scheduled tasks -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 120, 1, 'd', 0, 'remove_temp_attachments', '') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 180, 1, 'd', 0, 'remove_topic_redirect', '') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 240, 1, 'd', 0, 'remove_old_drafts', '') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}scheduled_tasks - (next_time, time_offset, time_regularity, time_unit, disabled, task, callable) -VALUES - (0, 0, 1, 'w', 1, 'prune_log_topics', '') ON CONFLICT DO NOTHING; ----# - ----# Adding a new task-related setting... ----{ - if (!isset(Config::$modSettings['allow_expire_redirect'])) - { - $get_info = Db::$db->query( - 'SELECT disabled - FROM {db_prefix}scheduled_tasks - WHERE task = {string:remove_redirect}', - array( - 'remove_redirect' => 'remove_topic_redirect' - ) - ); - - list($task_disabled) = Db::$db->fetch_row($get_info); - Db::$db->free_result($get_info); - - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['allow_expire_redirect', !$task_disabled]), - array('variable') - ); - } ----} ----# - ----# Remove old tasks added by modifications... ----{ - $vanilla_tasks = array( - 'birthdayemails', - 'daily_digest', - 'daily_maintenance', - 'fetchSMfiles', - 'paid_subscriptions', - 'remove_temp_attachments', - 'remove_topic_redirect', - 'remove_old_drafts', - 'prune_log_topics', - 'weekly_digest', - 'weekly_maintenance'); - - Db::$db->query( - 'DELETE FROM {db_prefix}scheduled_tasks - WHERE task NOT IN ({array_string:keep_tasks});', - array( - 'keep_tasks' => $vanilla_tasks - ) - ); ----} ----# - -/******************************************************************************/ ----- Adding background tasks support -/******************************************************************************/ ----# Adding the sequence -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}background_tasks_seq; ----# - ----# Adding the table -CREATE TABLE IF NOT EXISTS {$db_prefix}background_tasks ( - id_task bigint DEFAULT nextval('{$db_prefix}background_tasks_seq'), - task_file varchar(255) NOT NULL DEFAULT '', - task_class varchar(255) NOT NULL DEFAULT '', - task_data text NOT NULL, - claimed_time int NOT NULL DEFAULT '0', - PRIMARY KEY (id_task) -); ----# - -/******************************************************************************/ ---- Adding support for deny boards access -/******************************************************************************/ ----# Adding new columns to boards... -ALTER TABLE {$db_prefix}boards -ADD COLUMN IF NOT EXISTS deny_member_groups varchar(255) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Adding setting for max depth of sub-boards to check for new posts, etc. -/******************************************************************************/ ----# Adding the boardindex_max_depth setting. -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('boardindex_max_depth', '1') ON CONFLICT DO NOTHING; ----# - -/******************************************************************************/ ---- Removing manage_boards permission from anyone who shouldn't have it -/******************************************************************************/ ----# Removing manage_boards permission ----{ -if (version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) -{ - $board_managers = array(); - - $request = Db::$db->query( - 'SELECT id_group - FROM {db_prefix}permissions - WHERE permission = {string:permission}', - array( - 'permission' => 'manage_boards', - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - $board_managers[$row['id_group']] = 0; - } - Db::$db->free_result($request); - - $request = Db::$db->query( - 'SELECT member_groups - FROM {db_prefix}boards', - array() - ); - $num_boards = Db::$db->num_rows($request); - while ($row = Db::$db->fetch_assoc($request)) - { - $groups = explode(',', $row['member_groups']); - foreach ($groups as $group) - if (array_key_exists($group, $board_managers)) - ++$board_managers[$group]; - } - Db::$db->free_result($request); - - $ex_board_managers = array(); - foreach ($board_managers as $id_group => $board_count) - if ($board_count < $num_boards) - $ex_board_managers[] = $id_group; - - if (!empty($ex_board_managers)) - { - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {string:permission} - AND id_group IN ({array_int:ex_board_managers})', - array( - 'permission' => 'manage_boards', - 'ex_board_managers' => $ex_board_managers, - ) - ); - } -} ----} ----# - -/******************************************************************************/ ---- Adding support for category descriptions -/******************************************************************************/ ----# Adding new columns to categories... -ALTER TABLE {$db_prefix}categories -ADD COLUMN IF NOT EXISTS description text; - - -UPDATE {$db_prefix}categories -SET description = ''; - -ALTER TABLE {$db_prefix}categories -ALTER COLUMN description SET NOT NULL; ----# - -/******************************************************************************/ ---- Adding support for alerts -/******************************************************************************/ ----# Adding the count to the members table... -ALTER TABLE {$db_prefix}members -ADD COLUMN IF NOT EXISTS alerts int NOT NULL default '0'; ----# - ----# Adding the new table for alerts. -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}user_alerts_seq; - -CREATE TABLE IF NOT EXISTS {$db_prefix}user_alerts ( - id_alert bigint DEFAULT nextval('{$db_prefix}user_alerts_seq'), - alert_time bigint NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - id_member_started bigint NOT NULL DEFAULT '0', - member_name varchar(255) NOT NULL DEFAULT '', - content_type varchar(255) NOT NULL DEFAULT '', - content_id bigint NOT NULL DEFAULT '0', - content_action varchar(255) NOT NULL DEFAULT '', - is_read bigint NOT NULL DEFAULT '0', - extra text NOT NULL, - PRIMARY KEY (id_alert) -); - -DROP INDEX IF EXISTS {$db_prefix}user_alerts_id_member; -DROP INDEX IF EXISTS {$db_prefix}user_alerts_alert_time; - -CREATE INDEX {$db_prefix}user_alerts_id_member ON {$db_prefix}user_alerts (id_member); -CREATE INDEX {$db_prefix}user_alerts_alert_time ON {$db_prefix}user_alerts (alert_time); ----# - ----# Adding alert preferences. -CREATE TABLE IF NOT EXISTS {$db_prefix}user_alerts_prefs ( - id_member int NOT NULL DEFAULT '0', - alert_pref varchar(32) NOT NULL DEFAULT '', - alert_value smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_member, alert_pref) -); - -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'alert_timeout', 10) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'announcements', 0) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'birthday', 2) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'board_notify', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'buddy_request', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'groupr_approved', 3) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'groupr_rejected', 3) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'member_group_request', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'member_register', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'member_report', 3) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'member_report_reply', 3) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_auto_notify', 0) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_like', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_mention', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_notify_pref', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_notify_type', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_quote', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_receive_body', 0) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_report', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'msg_report_reply', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'pm_new', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'pm_notify', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'pm_reply', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'request_group', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'topic_notify', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'unapproved_attachment', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'unapproved_reply', 3) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'unapproved_post', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}user_alerts_prefs (id_member, alert_pref, alert_value) VALUES (0, 'warn_any', 1) ON CONFLICT DO NOTHING; - ----# - ----# Upgrading post notification settings ----{ -// First see if we still have a notify_regularity column -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('notify_regularity', $results)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Upgrading post notification settings'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // Skip errors here so we don't croak if the columns don't exist... - $request = Db::$db->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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_member'], 'msg_receive_body', !empty($row['notify_send_body']) ? 1 : 0); - $inserts[] = array($row['id_member'], 'msg_notify_pref', intval($row['notify_regularity']) + 1); - $inserts[] = array($row['id_member'], 'msg_notify_type', $row['notify_types']); - $inserts[] = array($row['id_member'], 'announcements', !empty($row['notify_announcements']) ? 1 : 0); - } - Db::$db->free_result($request); - } - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - unset($_GET['a']); -} ----} ----# - ----# Dropping old notification fields from the members table -ALTER TABLE {$db_prefix}members - DROP IF EXISTS notify_send_body, - DROP IF EXISTS notify_types, - DROP IF EXISTS notify_regularity, - DROP IF EXISTS notify_announcements; ----# - ----# Upgrading auto notify setting ----{ -$_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; -$step_progress['name'] = 'Upgrading auto notify setting'; -$step_progress['current'] = $_GET['a']; - -$limit = 10000; -$is_done = false; - -$request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}themes - WHERE variable = {string:auto_notify}', - array( - 'auto_notify' => 'auto_notify', - ) -); -list($maxMembers) = Db::$db->fetch_row($request); -Db::$db->free_result($request); - -while (!$is_done) -{ - nextSubStep($substep); - $inserts = array(); - - // This setting is stored over in the themes table in 2.0... - $request = Db::$db->query( - 'SELECT id_member, value - FROM {db_prefix}themes - WHERE variable = {string:auto_notify} - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'auto_notify' => 'auto_notify', - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($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', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; -} -unset($_GET['a']); ----} ----# - ----# Dropping old auto notify settings from the themes table -DELETE FROM {$db_prefix}themes - WHERE variable = 'auto_notify'; ----# - ----# Creating alert prefs for watched topics ----{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Creating alert preferences for watched topics'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}log_notify WHERE id_member <> 0 AND id_topic <> 0'); - list($maxTopics) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, (\'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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - $inserts = Db::$db->fetch_all($request); - } - Db::$db->free_result($request); - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxTopics) - $is_done = true; - } - unset($_GET['a']); ----} ----# - ----# Creating alert prefs for watched boards ----{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Creating alert preferences for watched boards'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}log_notify WHERE id_member <> 0 AND id_board <> 0'); - list($maxBoards) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, (\'board_notify_\' || id_board) 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}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - $inserts = Db::$db->fetch_all($request); - } - Db::$db->free_result($request); - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxBoards) - $is_done = true; - } - unset($_GET['a']); ----} ----# - ----# Updating obsolete alerts from before RC3 -UPDATE {$db_prefix}user_alerts -SET content_type = 'member', content_id = id_member_started -WHERE content_type = 'buddy'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'member' -WHERE content_type = 'profile'; - -UPDATE {$db_prefix}user_alerts -SET content_id = id_member_started -WHERE content_type = 'member' AND content_action LIKE 'register_%'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_topic' -WHERE content_type = 'unapproved' AND content_action = 'topic'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_reply' -WHERE content_type = 'unapproved' AND content_action = 'reply'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'topic', content_action = 'unapproved_post' -WHERE content_type = 'unapproved' AND content_action = 'post'; - -UPDATE {$db_prefix}user_alerts -SET content_type = 'msg', content_action = 'unapproved_attachment', content_id = f.id_msg -FROM {$db_prefix}attachments AS f -WHERE content_type = 'unapproved' AND content_action = 'attachment' AND f.id_attach = content_id; ----# - ----# Adding index on id_board to log_notify table -CREATE INDEX {$db_prefix}log_notify_id_board ON {$db_prefix}log_notify (id_board); ----# - -/******************************************************************************/ ---- Adding support for topic unwatch -/******************************************************************************/ ----# Adding new column to log_topics... -ALTER TABLE {$db_prefix}log_topics -ADD COLUMN IF NOT EXISTS unwatched int NOT NULL DEFAULT 0; ----# - ----# Fixing column name change... -ALTER TABLE {$db_prefix}log_topics -DROP COLUMN IF EXISTS disregarded; ----# - -/******************************************************************************/ ---- Name changes -/******************************************************************************/ ----# Altering the membergroup stars to icons -ALTER TABLE {$db_prefix}membergroups -RENAME stars TO icons; ----# - ----# set default membergroup icons -ALTER TABLE {$db_prefix}membergroups -ALTER icons SET DEFAULT ''; ----# - ----# Renaming default theme... -UPDATE {$db_prefix}themes -SET value = 'SMF Default Theme - Curve2' -WHERE value LIKE 'SMF Default Theme%'; ----# - ----# Fader time update -UPDATE {$db_prefix}themes -SET value = '3000' -WHERE variable = 'newsfader_time'; ----# - ----# Adding the enableThemes setting. -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('enableThemes', '1') ON CONFLICT DO NOTHING; ----# - ----# Setting "default" as the default... -UPDATE {$db_prefix}settings -SET value = '1' -WHERE variable = 'theme_guests'; - -UPDATE {$db_prefix}boards -SET id_theme = 0; - -UPDATE {$db_prefix}members -SET id_theme = 0; ----# - -/******************************************************************************/ ---- Membergroup icons changes -/******************************************************************************/ ----# Check the current saved names for icons and change them to the new name. ----{ -$request = Db::$db->query( - 'SELECT icons - FROM {db_prefix}membergroups - WHERE icons != {string:blank}', - array( - 'blank' => '', - ) -); -$toMove = array(); -$toChange = array(); -while ($row = Db::$db->fetch_assoc($request)) -{ - if (strpos($row['icons'], 'star.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('star.gif', 'icon.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'starmod.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('starmod.gif', 'iconmod.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'stargmod.gif') !== false) - $toChange[] = array( - 'old' => $row['icons'], - 'new' => str_replace('stargmod.gif', 'icongmod.png', $row['icons']), - ); - - elseif (strpos($row['icons'], 'staradmin.gif') !== false) - $toChange[] = array( - '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) - Db::$db->query( - 'UPDATE {db_prefix}membergroups - SET icons = {string:new} - WHERE icons = {string:old}', - array( - '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); -} ----} ----# - -/******************************************************************************/ ---- Cleaning up after old themes... -/******************************************************************************/ ----# Clean up settings for unused themes ----{ -// Fetch list of theme directories -$request = Db::$db->query( - 'SELECT id_theme, variable, value - FROM {db_prefix}themes - WHERE variable = {string:theme_dir} - AND id_theme != {int:default_theme};', - array( - '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 = array('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 -Db::$db->query( - 'DELETE FROM {db_prefix}themes - WHERE id_theme NOT IN ({array_int:known_themes});', - array( - 'known_themes' => $known_themes, - ) -); -// Set knownThemes -$known_themes = implode(',', $known_themes); -Db::$db->query( - 'UPDATE {db_prefix}settings - SET value = {string:known_themes} - WHERE variable = {string:known_theme_str};', - array( - 'known_theme_str' => 'knownThemes', - 'known_themes' => $known_themes, - ) -); ----} ----# - -/******************************************************************************/ ---- Messenger fields -/******************************************************************************/ ----# Adding new field_order column... -ALTER TABLE {$db_prefix}custom_fields -ADD COLUMN IF NOT EXISTS field_order smallint NOT NULL default '0'; ----# - ----# Adding new show_mlist column... -ALTER TABLE {$db_prefix}custom_fields -ADD COLUMN IF NOT EXISTS show_mlist smallint NOT NULL default '0'; ----# - ----# Insert fields -INSERT INTO {$db_prefix}custom_fields (col_name, field_name, field_desc, field_type, field_length, field_options, field_order, mask, show_reg, show_display, show_mlist, show_profile, private, active, bbc, can_search, default_value, enclose, placement) VALUES -('cust_skype', '{skype}', '{skype_desc}', 'text', 32, '', 2, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '{INPUT} ', 1) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}custom_fields (col_name, field_name, field_desc, field_type, field_length, field_options, field_order, mask, show_reg, show_display, show_mlist, show_profile, private, active, bbc, can_search, default_value, enclose, placement) VALUES -('cust_loca', '{location}', '{location_desc}', 'text', 50, '', 4, 'nohtml', 0, 1, 0, 'forumprofile', 0, 1, 0, 0, '', '', 0) ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}custom_fields (col_name, field_name, field_desc, field_type, field_length, field_options, field_order, mask, show_reg, show_display, show_mlist, show_profile, private, active, bbc, can_search, default_value, enclose, placement) VALUES -('cust_gender', '{gender}', '{gender_desc}', 'radio', 255, '{gender_0},{gender_1},{gender_2}', 5, 'nohtml', 1, 1, 0, 'forumprofile', 0, 1, 0, 0, '{gender_0}', '', 1) ON CONFLICT DO NOTHING; ----# - ----# Add an order value to each existing cust profile field. ----{ - $ocf = Db::$db->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; - - Db::$db->query( - 'UPDATE {db_prefix}custom_fields - SET field_order = {int:field_count} - WHERE id_field = {int:id_field}', - array( - 'field_count' => $fields_count, - 'id_field' => $row['id_field'], - ) - ); - } - Db::$db->free_result($ocf); ----} ----# - ----# Converting member values... ----{ -// We cannot do this twice -// See which columns we have -$results = Db::$db->list_columns('{db_prefix}members'); -$possible_columns = array('msn', 'location', 'gender'); - -// Find values that are in both arrays -$select_columns = array_intersect($possible_columns, $results); - -if (!empty($select_columns)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Converting member values'; - $step_progress['current'] = $_GET['a']; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - - $limit = 10000; - $is_done = false; - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - $request = Db::$db->query( - 'SELECT id_member, '. implode(',', $select_columns) .' - FROM {db_prefix}members - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'start' => $_GET['a'], - 'limit' => $limit, - )); - - while ($row = Db::$db->fetch_assoc($request)) - { - if (!empty($row['msn'])) - $inserts[] = array($row['id_member'], 1, 'cust_skype', $row['msn']); - - if (!empty($row['location'])) - $inserts[] = array($row['id_member'], 1, 'cust_loca', $row['location']); - - if (!empty($row['gender'])) - $inserts[] = array($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', - array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'), - $inserts, - array('id_theme', 'id_member', 'variable') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } -} -unset($_GET['a']); ----} ----# - ----# Dropping old fields -ALTER TABLE {$db_prefix}members - DROP IF EXISTS icq, - DROP IF EXISTS aim, - DROP IF EXISTS yim, - DROP IF EXISTS msn, - DROP IF EXISTS location, - DROP IF EXISTS gender; ----# - ----# Create the displayFields setting ----{ - if (empty(Config::$modSettings['displayFields'])) - { - $request = Db::$db->query( - 'SELECT col_name, field_name, field_type, field_order, bbc, enclose, placement, show_mlist - FROM {db_prefix}custom_fields', - array() - ); - - $fields = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $fields[] = array( - 'col_name' => strtr($row['col_name'], array('|' => '', ';' => '')), - 'title' => strtr($row['field_name'], array('|' => '', ';' => '')), - '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); - - Db::$db->insert('', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['displayFields', json_encode($fields)]), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Adding support for drafts -/******************************************************************************/ ----# Creating drafts table. -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}user_drafts_seq; - -CREATE TABLE IF NOT EXISTS {$db_prefix}user_drafts ( - id_draft bigint DEFAULT nextval('{$db_prefix}user_drafts_seq'), - id_topic int NOT NULL DEFAULT '0', - id_board smallint NOT NULL DEFAULT '0', - id_reply bigint NOT NULL DEFAULT '0', - type smallint NOT NULL DEFAULT '0', - poster_time int NOT NULL DEFAULT '0', - id_member int NOT NULL DEFAULT '0', - subject varchar(255) NOT NULL DEFAULT '', - smileys_enabled smallint NOT NULL DEFAULT '1', - body text NOT NULL, - icon varchar(16) NOT NULL DEFAULT 'xx', - locked smallint NOT NULL DEFAULT '0', - is_sticky smallint NOT NULL DEFAULT '0', - to_list varchar(255) NOT NULL DEFAULT '', - PRIMARY KEY (id_draft) -); -CREATE UNIQUE INDEX IF NOT EXISTS {$db_prefix}user_drafts_id_member ON {$db_prefix}user_drafts (id_member, id_draft, type); ----# - ----# Adding draft permissions... ----{ -// We cannot do this twice -if (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 = upgrade_query(" - SELECT id_group, id_board, add_deny, permission - FROM {$db_prefix}board_permissions - WHERE permission = 'post_unapproved_topics'"); - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_group'], $row['id_board'], 'post_draft', $row['add_deny']); - } - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('replace', - '{$db_prefix}board_permissions', - array('id_group' => 'int', 'id_board' => 'int', 'permission' => 'string', 'add_deny' => 'int'), - $inserts, - array('id_group', 'id_profile', 'permission') - ); - } - - // Next we find people who can send PMs, and assume they can save pm_drafts as well - $request = upgrade_query(" - SELECT id_group, add_deny, permission - FROM {$db_prefix}permissions - WHERE permission = 'pm_send'"); - $inserts = array(); - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_group'], 'pm_draft', $row['add_deny']); - } - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}permissions', - array('id_group' => 'int', 'add_deny' => 'int', 'permission' => 'string'), - $inserts, - array('id_group', 'permission') - ); - } -} ----} -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('drafts_autosave_enabled', '1') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('drafts_show_saved_enabled', '1') ON CONFLICT DO NOTHING; -INSERT INTO {$db_prefix}settings (variable, value) VALUES ('drafts_keep_days', '7') ON CONFLICT DO NOTHING; - -INSERT INTO {$db_prefix}themes (id_member, id_theme, variable, value) VALUES (-1, '1', 'drafts_show_saved_enabled', '1') ON CONFLICT DO NOTHING; ----# - -/******************************************************************************/ ---- Adding support for likes -/******************************************************************************/ ----# Creating likes table. -CREATE TABLE IF NOT EXISTS {$db_prefix}user_likes ( - id_member int NOT NULL DEFAULT '0', - content_type char(6) DEFAULT '', - content_id int NOT NULL DEFAULT '0', - like_time int NOT NULL DEFAULT '0', - PRIMARY KEY (content_id, content_type, id_member) -); - -DROP INDEX IF EXISTS {$db_prefix}user_likes_content; -DROP INDEX IF EXISTS {$db_prefix}user_likes_liker; - -CREATE INDEX {$db_prefix}user_likes_content ON {$db_prefix}user_likes (content_id, content_type); -CREATE INDEX {$db_prefix}user_likes_liker ON {$db_prefix}user_likes (id_member); ----# - ----# Adding likes column to the messages table. (May take a while) -ALTER TABLE {$db_prefix}messages -ADD COLUMN IF NOT EXISTS likes smallint NOT NULL default '0'; ----# - -/******************************************************************************/ ---- Adding support for mentions -/******************************************************************************/ ----# Creating mentions table -CREATE TABLE IF NOT EXISTS {$db_prefix}mentions ( - content_id int DEFAULT '0', - content_type varchar(10) DEFAULT '', - id_mentioned int DEFAULT 0, - id_member int NOT NULL DEFAULT 0, - time int NOT NULL DEFAULT 0, - PRIMARY KEY (content_id, content_type, id_mentioned) -); - -DROP INDEX IF EXISTS {$db_prefix}mentions_content; -DROP INDEX IF EXISTS {$db_prefix}mentions_mentionee; - -CREATE INDEX {$db_prefix}mentions_content ON {$db_prefix}mentions (content_id, content_type); -CREATE INDEX {$db_prefix}mentions_mentionee ON {$db_prefix}mentions (id_member); ----# - -/******************************************************************************/ ---- Adding support for group-based board moderation -/******************************************************************************/ ----# Creating moderator_groups table -CREATE TABLE IF NOT EXISTS {$db_prefix}moderator_groups ( - id_board smallint NOT NULL DEFAULT '0', - id_group smallint NOT NULL DEFAULT '0', - PRIMARY KEY (id_board, id_group) -); ----# - -/******************************************************************************/ ---- Cleaning up integration hooks -/******************************************************************************/ ----# -DELETE FROM {$db_prefix}settings -WHERE variable LIKE 'integrate_%'; ----# - -/******************************************************************************/ ---- Cleaning up old settings -/******************************************************************************/ ----# Fixing a deprecated option. -UPDATE {$db_prefix}settings -SET value = 'option_css_resize' -WHERE variable = 'avatar_action_too_large' - AND (value = 'option_html_resize' OR value = 'option_js_resize'); ----# - ----# Cleaning up the old Core Features page. ----{ - // First get the original value - $request = Db::$db->query( - 'SELECT value - FROM {db_prefix}settings - WHERE variable = {literal:admin_features}' - ); - if (Db::$db->num_rows($request) > 0 && $row = Db::$db->fetch_assoc($request)) - { - // Some of these *should* already be set but you never know. - $new_settings = array(); - $admin_features = explode(',', $row['value']); - - // cd = calendar, should also have set cal_enabled already - // cp = custom profile fields, which already has several fields that cover tracking - // k = karma, should also have set karmaMode already - // 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)) - { - $new_settings[] = array('adminlog_enabled', '1'); - $new_settings[] = array('userlog_enabled', '1'); - } - - // Post moderation - if (in_array('pm', $admin_features)) - { - $new_settings[] = array('postmod_active', '1'); - } - - // And now actually apply it. - if (!empty($new_settings)) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - $new_settings, - array('variable') - ); - } - } - Db::$db->free_result($request); ----} ----# - ----# Cleaning up old settings. -DELETE FROM {$db_prefix}settings -WHERE variable IN ('enableStickyTopics', 'guest_hideContacts', 'notify_new_registration', 'attachmentEncryptFilenames', 'hotTopicPosts', 'hotTopicVeryPosts', 'fixLongWords', 'admin_features', 'log_ban_hits', 'topbottomEnable', 'simpleSearch', 'enableVBStyleLogin', 'admin_bbc', 'enable_unwatch', 'cache_memcached', 'cache_enable', 'cookie_no_auth_secret'); ----# - ----# Cleaning up old theme settings. -DELETE FROM {$db_prefix}themes -WHERE variable IN ('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'); ----# - ----# Update the SM Stat collection. ----{ - // First get the original value - $request = Db::$db->query( - 'SELECT value - FROM {db_prefix}settings - WHERE variable = {literal:allow_sm_stats}' - ); - if (Db::$db->num_rows($request) > 0 && $row = Db::$db->fetch_assoc($request)) - { - if (!empty($row['value'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array( - array('sm_stats_key', $row['value']), - array('enable_sm_stats', '1'), - ), - array('variable') - ); - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {literal:allow_sm_stats}'); - } - } - Db::$db->free_result($request); ----} ----# - ----# Adding new "httponlyCookies" setting ----{ - if (!isset(Config::$modSettings['httponlyCookies'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array('httponlyCookies', '1'), - array() - ); ----} ----# - ----# Adding new "samesiteCookies" setting ----{ - if (!isset(Config::$modSettings['samesiteCookies'])) - Db::$db->insert('insert', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['samesiteCookies', 'lax']), - array() - ); ----} ----# - ----# Calculate appropriate hash cost ----{ - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['bcrypt_hash_cost', Security::hashBenchmark()]), - array('variable') - ); ----} - -/******************************************************************************/ ---- Updating files that fetched from simplemachines.org -/******************************************************************************/ ----# We no longer call on several files. -DELETE FROM {$db_prefix}admin_info_files -WHERE filename IN ('latest-packages.js', 'latest-smileys.js', 'latest-support.js', 'latest-themes.js') - AND path = '/smf/'; ----# - ----# But we do need new files. ----{ -// Don't insert the info if it's already there... -$file_check = Db::$db->query( - 'SELECT id_file - FROM {db_prefix}admin_info_files - WHERE filename = {string:latest-versions}', - array( - 'latest-versions' => 'latest-versions.txt', - ) -); - -if (Db::$db->num_rows($file_check) == 0) -{ - Db::$db->insert('', - '{db_prefix}admin_info_files', - array('filename' => 'string', 'path' => 'string', 'parameters' => 'string', 'data' => 'string', 'filetype' => 'string'), - array(['latest-versions.txt', '/smf/', 'version=%3$s', '', 'text/plain']), - array('id_file') - ); -} - -Db::$db->free_result($file_check); ----} ----# - -/******************************************************************************/ ---- Upgrading "verification questions" feature -/******************************************************************************/ ----# Creating qanda table -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}qanda_seq; - -CREATE TABLE IF NOT EXISTS {$db_prefix}qanda ( - id_question smallint DEFAULT nextval('{$db_prefix}qanda_seq'), - lngfile varchar(255) NOT NULL DEFAULT '', - question varchar(255) NOT NULL DEFAULT '', - answers text NOT NULL, - PRIMARY KEY (id_question) -); ----# - ----# Create index on qanda -DROP INDEX IF EXISTS {$db_prefix}qanda_lngfile; -CREATE INDEX {$db_prefix}qanda_lngfile ON {$db_prefix}qanda (lngfile varchar_pattern_ops); ----# - ----# Moving questions and answers to the new table ----{ - $questions = array(); - - $get_questions = upgrade_query(" - SELECT body AS question, recipient_name AS answer - FROM {$db_prefix}log_comments - WHERE comment_type = 'ver_test'"); - - while ($row = Db::$db->fetch_assoc($get_questions)) - $questions[] = array($upcontext['language'], $row['question'], serialize(array($row['answer']))); - - Db::$db->free_result($get_questions); - - if (!empty($questions)) - { - Db::$db->insert('', - '{db_prefix}qanda', - array('lngfile' => 'string', 'question' => 'string', 'answers' => 'string'), - $questions, - array('id_question') - ); - - // Delete the questions from log_comments now - upgrade_query(" - DELETE FROM {$db_prefix}log_comments - WHERE comment_type = 'ver_test' - "); - } ----} ----# - -/******************************************************************************/ ---- Marking packages as uninstalled... -/******************************************************************************/ ----# Updating log_packages -UPDATE {$db_prefix}log_packages -SET install_state = 0; ----# - -/******************************************************************************/ ---- Updating profile permissions... -/******************************************************************************/ ----# Removing the old "view your own profile" permission -DELETE FROM {$db_prefix}permissions -WHERE permission = 'profile_view_own'; ----# - ----# Updating the old "view any profile" permission -UPDATE {$db_prefix}permissions -SET permission = 'profile_view' -WHERE permission = 'profile_view_any'; ----# - ----# Removing the old notification permissions -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'mark_notify' OR permission = 'mark_any_notify'; ----# - ----# Removing the send-topic permission -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'send_topic'; ----# - ----# Removing the draft "autosave" permissions -DELETE FROM {$db_prefix}permissions -WHERE permission = 'post_autosave_draft' OR permission = 'pm_autosave_draft'; - -DELETE FROM {$db_prefix}board_permissions -WHERE permission = 'post_autosave_draft'; ----# - ----# Adding "profile_password_own" ----{ -$inserts = array(); - -$request = upgrade_query(" - SELECT id_group, add_deny - FROM {$db_prefix}permissions - WHERE permission = 'profile_identity_own'"); - - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_group'], 'profile_password_own', $row['add_deny']); - } - - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}permissions', - array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), - $inserts, - array('id_group', 'permission') - ); - } ----} ----# - ----# Adding "view_warning_own" and "view_warning_any" permissions ----{ -if (isset(Config::$modSettings['warning_show'])) -{ - $can_view_warning_own = array(); - $can_view_warning_any = array(); - - if (Config::$modSettings['warning_show'] >= 1) - { - $can_view_warning_own[] = 0; - - $request = Db::$db->query( - 'SELECT id_group - FROM {db_prefix}membergroups - WHERE min_posts = {int:not_post_based}', - array( - 'not_post_based' => -1, - ) - ); - while ($row = Db::$db->fetch_assoc($request)) - { - if (in_array($row['id_group'], array(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 = Db::$db->query( - 'SELECT id_group, add_deny - FROM {db_prefix}permissions - WHERE permission = {string:perm}', - array( - 'perm' => 'issue_warning', - ) - ); - while ($row = Db::$db->fetch_assoc($request)) - { - if (in_array($row['id_group'], array(-1, 1, 3)) || $row['add_deny'] != 1) - continue; - - $can_view_warning_any[] = $row['id_group']; - } - Db::$db->free_result($request); - } - - $inserts = array(); - - foreach ($can_view_warning_own as $id_group) - $inserts[] = array($id_group, 'view_warning_own', 1); - - foreach ($can_view_warning_any as $id_group) - $inserts[] = array($id_group, 'view_warning_any', 1); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}permissions', - array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), - $inserts, - array('id_group', 'permission') - ); - } - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {string:warning_show}', - array( - 'warning_show' => 'warning_show', - ) - ); -} ----} ----# - ----# Adding other profile permissions ----{ -$inserts = array(); - -$request = upgrade_query(" - SELECT id_group, add_deny - FROM {$db_prefix}permissions - WHERE permission = 'profile_extra_own'"); - - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_group'], 'profile_blurb_own', $row['add_deny']); - $inserts[] = array($row['id_group'], 'profile_displayed_name_own', $row['add_deny']); - $inserts[] = array($row['id_group'], 'profile_forum_own', $row['add_deny']); - $inserts[] = array($row['id_group'], 'profile_website_own', $row['add_deny']); - $inserts[] = array($row['id_group'], 'profile_signature_own', $row['add_deny']); - } - - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}permissions', - array('id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'), - $inserts, - array('id_group', 'permission') - ); - } ----} ----# - -/******************************************************************************/ ---- Upgrading PM labels... -/******************************************************************************/ ----# Creating pm_labels sequence... -CREATE SEQUENCE IF NOT EXISTS {$db_prefix}pm_labels_seq; ----# - ----# Adding pm_labels table... -CREATE TABLE IF NOT EXISTS {$db_prefix}pm_labels ( - id_label bigint NOT NULL DEFAULT nextval('{$db_prefix}pm_labels_seq'), - id_member int NOT NULL DEFAULT '0', - name varchar(30) NOT NULL DEFAULT '', - PRIMARY KEY (id_label) -); ----# - ----# Adding pm_labeled_messages table... -CREATE TABLE IF NOT EXISTS {$db_prefix}pm_labeled_messages ( - id_label bigint NOT NULL DEFAULT '0', - id_pm bigint NOT NULL DEFAULT '0', - PRIMARY KEY (id_label, id_pm) -); ----# - ----# Adding "in_inbox" column to pm_recipients -ALTER TABLE {$db_prefix}pm_recipients -ADD COLUMN IF NOT EXISTS in_inbox smallint NOT NULL default '1'; ----# - ----# Moving label info to new tables and updating rules (May be slow!!!) ----{ - // First see if we still have a message_labels column - $results = Db::$db->list_columns('{db_prefix}members'); - if (in_array('message_labels', $results)) - { - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Moving pm labels'; - $step_progress['current'] = $_GET['a']; - - $request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}members - WHERE message_labels != {string:blank}', - array( - 'blank' => '', - ) - ); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - if ($maxMembers > 0) - { - $limit = 5000; - $is_done = false; - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // 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:start}, {int:limit}', - array( - 'blank' => '', - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - - $label_info = array(); - $member_list = array(); - 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[] = array($id_member, $label); - } - } - - if (!empty($inserts)) - { - Db::$db->insert('', '{db_prefix}pm_labels', array('id_member' => 'int', 'name' => 'string-30'), $inserts, array()); - - // Clear this out for our next query below - $inserts = array(); - } - - // 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) > 0 - AND id_member IN ({array_int:member_list})', - array( - '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})', - array( - 'member_list' => $member_list, - ) - ); - - $label_info_2 = array(); - 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})', - array( - '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[] = array($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', array('id_pm' => 'int', 'id_label' => 'int'), $inserts, array()); - } - - // 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})', - array( - '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}', - array( - '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})', - array( - 'blank' => '', - 'member_list' => $member_list, - ) - ); - - Db::$db->free_result($get_pm_rules); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - - // Lastly, we drop the old columns - Db::$db->remove_column('{db_prefix}members', 'message_labels'); - Db::$db->remove_column('{db_prefix}pm_recipients', 'labels'); - } - } - unset($_GET['a']); ----} ----# - -/******************************************************************************/ ---- Adding support for edit reasons -/******************************************************************************/ ----# Adding "modified_reason" column to messages -ALTER TABLE {$db_prefix}messages -ADD COLUMN IF NOT EXISTS modified_reason varchar(255) NOT NULL default ''; ----# - -/******************************************************************************/ ---- Cleaning up guest permissions -/******************************************************************************/ ----# Removing permissions guests can no longer have... ----{ - $illegal_board_permissions = array( - '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' - ); - - $illegal_permissions = array('calendar_edit_any', 'moderate_board', 'moderate_forum', 'send_email_to_members'); - - Db::$db->query( - 'DELETE FROM {db_prefix}board_permissions - WHERE id_group = {int:guests} - AND permission IN ({array_string:illegal_board_perms})', - array( - 'guests' => -1, - 'illegal_board_perms' => $illegal_board_permissions, - ) - ); - - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE id_group = {int:guests} - AND permission IN ({array_string:illegal_perms})', - array( - 'guests' => -1, - 'illegal_perms' => $illegal_permissions, - ) - ); ----} ----# - -/******************************************************************************/ ---- Adding gravatar settings -/******************************************************************************/ ----# Adding default gravatar settings ----{ - if (empty(Config::$modSettings['gravatarEnabled'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('gravatarEnabled', '1'), - array('gravatarOverride', '0'), - array('gravatarAllowExtraEmail', '1'), - array('gravatarMaxRating', 'PG'), - ), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Adding timezone support -/******************************************************************************/ ----# Adding the "timezone" column to the members table -ALTER TABLE {$db_prefix}members ADD IF NOT EXISTS timezone VARCHAR(80) NOT NULL DEFAULT ''; ----# - ----# Converting time offset to timezone ----{ - 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, date_format($now, 'I'))) !== false) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('default_timezone', $new_tzid), - ), - array('variable') - ); - - Config::$modSettings['default_timezone'] = $new_tzid; - } - - Db::$db->query( - 'DELETE FROM {db_prefix}settings - WHERE variable = {literal:time_offset}', - array() - ); - } ----} ----# - -/******************************************************************************/ ---- Adding mail queue settings -/******************************************************************************/ ----# Adding default settings for the mail queue ----{ - if (empty(Config::$modSettings['mail_limit'])) - { - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string-255', 'value' => 'string'), - array( - array('mail_limit', '5'), - array('mail_quantity', '5'), - ), - array('variable') - ); - } ----} ----# - -/******************************************************************************/ ---- Cleaning up old email settings -/******************************************************************************/ ----# Removing the "send_email_to_members" permission ----{ - Db::$db->query( - 'DELETE FROM {db_prefix}permissions - WHERE permission = {literal:send_email_to_members}', - array() - ); ----} ----# - ----# Dropping the "hide_email" column from the members table -ALTER TABLE {$db_prefix}members -DROP IF EXISTS hide_email; ----# - ----# Dropping the "email_address" column from log_reported_comments -ALTER TABLE {$db_prefix}log_reported_comments -DROP IF EXISTS email_address; ----# - -/******************************************************************************/ ---- Deleting the "Auto Optimize" task -/******************************************************************************/ ----# Removing the task and associated data -DELETE FROM {$db_prefix}scheduled_tasks -WHERE id_task = '2'; - -DELETE FROM {$db_prefix}log_scheduled_tasks -WHERE id_task = '2'; - -DELETE FROM {$db_prefix}settings -WHERE variable = 'autoOptMaxOnline'; ----# - -/******************************************************************************/ ---- Removing OpenID-related things... -/******************************************************************************/ ----# Removing the openid_uri column in the members table -ALTER TABLE {$db_prefix}members -DROP IF EXISTS openid_uri; ----# - ----# Dropping the openid_assoc table -DROP TABLE IF EXISTS {$db_prefix}openid_assoc; ----# - ----# Removing related settings -DELETE FROM {$db_prefix}settings -WHERE variable='enableOpenID' OR variable='dh_keys'; ----# - -/******************************************************************************/ ---- Fixing the url column in the log_spider_hits and log_online tables -/******************************************************************************/ ----# Changing url column size in log_spider_hits from 255 to 1024 -ALTER TABLE {$db_prefix}log_spider_hits -ALTER url TYPE varchar(1024); - -ALTER TABLE {$db_prefix}log_spider_hits -ALTER url SET DEFAULT ''; ----# - ----# Changing url column in log_online from text to varchar(1024) -ALTER TABLE {$db_prefix}log_online -ALTER url TYPE varchar(2048); ----# - -/******************************************************************************/ ---- Adding support for 2FA -/******************************************************************************/ ----# Adding the secret column to members table -ALTER TABLE {$db_prefix}members -ADD COLUMN IF NOT EXISTS tfa_secret VARCHAR(24) NOT NULL DEFAULT ''; ----# - ----# Adding the backup column to members tab -ALTER TABLE {$db_prefix}members -ADD COLUMN IF NOT EXISTS tfa_backup VARCHAR(64) NOT NULL DEFAULT ''; ----# - ----# Force 2FA per membergroup -ALTER TABLE {$db_prefix}membergroups -ADD COLUMN IF NOT EXISTS tfa_required smallint NOT NULL default '0'; ----# - ----# Add tfa_mode setting ----{ - if (!isset(Config::$modSettings['tfa_mode'])) - Db::$db->insert('replace', - '{db_prefix}settings', - array('variable' => 'string', 'value' => 'string'), - array(['tfa_mode', '1']), - array('variable') - ); ----} ----# - -/******************************************************************************/ ---- optimization of members -/******************************************************************************/ ----# DROP INDEX to members -DROP INDEX IF EXISTS {$db_prefix}members_member_name_low; -DROP INDEX IF EXISTS {$db_prefix}members_real_name_low; -DROP INDEX IF EXISTS {$db_prefix}members_active_real_name; ----# - ----# ADD INDEX to members -CREATE INDEX {$db_prefix}members_member_name_low ON {$db_prefix}members (LOWER(member_name) varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_real_name_low ON {$db_prefix}members (LOWER(real_name) varchar_pattern_ops); -CREATE INDEX {$db_prefix}members_active_real_name ON {$db_prefix}members (is_activated, real_name); ----# - -/******************************************************************************/ ---- UNLOGGED Table PG 9.1+ -/******************************************************************************/ ----# update table ----{ -$result = Db::$db->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)) -{ - $tables = array('log_online','log_floodcontrol','sessions'); - foreach($tables as $tab) - { - if($pg_version >= 90500) - upgrade_query("ALTER TABLE {$db_prefix}".$tab." SET UNLOGGED;"); - ELSE - upgrade_query(" - alter table {$db_prefix}".$tab." rename to old_{$db_prefix}".$tab."; - - do - $$ - declare r record; - begin - for r in select * from pg_constraint where conrelid='old_{$db_prefix}".$tab."'::regclass loop - execute format('alter table old_{$db_prefix}".$tab." rename constraint %I to %I', r.conname, 'old_' || r.conname); - end loop; - for r in select * from pg_indexes where tablename='old_{$db_prefix}".$tab."' and indexname !~ '^old_' loop - execute format('alter index %I rename to %I', r.indexname, 'old_' || r.indexname); - end loop; - end; - $$; - - create unlogged table {$db_prefix}".$tab." (like old_{$db_prefix}".$tab." including all); - - insert into {$db_prefix}".$tab." select * from old_{$db_prefix}".$tab."; - - drop table old_{$db_prefix}".$tab.";" - ); - } - -} ----} ----# - -/******************************************************************************/ ---- remove redundant index -/******************************************************************************/ ----# duplicate to messages_current_topic -DROP INDEX IF EXISTS {$db_prefix}messages_id_topic; -DROP INDEX IF EXISTS {$db_prefix}messages_topic; ----# - ----# duplicate to topics_last_message_sticky and topics_board_news -DROP INDEX IF EXISTS {$db_prefix}topics_id_board; ----# - -/******************************************************************************/ ---- update ban ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$table_columns = Db::$db->list_columns('{db_prefix}ban_items'); -$upcontext['skip_db_substeps'] = in_array('ip_low', $table_columns); ----} ----# - ----# add columns -ALTER TABLE {$db_prefix}ban_items ADD COLUMN IF NOT EXISTS ip_low inet; -ALTER TABLE {$db_prefix}ban_items ADD COLUMN IF NOT EXISTS ip_high inet; ----# - ----# convert data -UPDATE {$db_prefix}ban_items -SET ip_low = (ip_low1||'.'||ip_low2||'.'||ip_low3||'.'||ip_low4)::inet, - ip_high = (ip_high1||'.'||ip_high2||'.'||ip_high3||'.'||ip_high4)::inet -WHERE ip_low1 > 0; ----# - ----# index -DROP INDEX IF EXISTS {$db_prefix}ban_items_id_ban_ip; -CREATE INDEX {$db_prefix}ban_items_id_ban_ip ON {$db_prefix}ban_items (ip_low,ip_high); ----# - ----# Dropping columns from ban_items -ALTER TABLE {$db_prefix}ban_items -DROP ip_low1, -DROP ip_low2, -DROP ip_low3, -DROP ip_low4, -DROP ip_high1, -DROP ip_high2, -DROP ip_high3, -DROP ip_high4; ----# - -/******************************************************************************/ ---- helper function for ip convert -/******************************************************************************/ ----# the function migrate_inet ----{ -upgrade_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;" -); ----} ----# - -/******************************************************************************/ ---- update log_action ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_actions', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert column -ALTER TABLE {$db_prefix}log_actions - ALTER ip DROP not null, - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); ----# - -/******************************************************************************/ ---- update log_banned ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_banned', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old column -ALTER TABLE {$db_prefix}log_banned - ALTER ip DROP not null, - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); ----# - -/******************************************************************************/ ---- update log_errors members ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_errors', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old columns -ALTER TABLE {$db_prefix}log_errors - ALTER ip DROP not null, - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); ----# - -/******************************************************************************/ ---- update log_errors members ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}members', 'member_ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# -ALTER TABLE {$db_prefix}members - ALTER member_ip DROP not null, - ALTER member_ip DROP default, - ALTER member_ip TYPE inet USING migrate_inet(member_ip); -ALTER TABLE {$db_prefix}members - ALTER member_ip2 DROP not null, - ALTER member_ip2 DROP default, - ALTER member_ip2 TYPE inet USING migrate_inet(member_ip2); ----# - -/******************************************************************************/ ---- update messages poster_ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}messages', 'poster_ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old column -ALTER TABLE {$db_prefix}messages - ALTER poster_ip DROP not null, - ALTER poster_ip DROP default, - ALTER poster_ip TYPE inet USING migrate_inet(poster_ip); ----# - -/******************************************************************************/ ---- update log_floodcontrol ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_floodcontrol', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# drop pk -TRUNCATE TABLE {$db_prefix}log_floodcontrol; -ALTER TABLE {$db_prefix}log_floodcontrol DROP CONSTRAINT {$db_prefix}log_floodcontrol_pkey; ----# - ----# convert old column -ALTER TABLE {$db_prefix}log_floodcontrol - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); ----# - ----# Modify log_type size -ALTER TABLE {$db_prefix}log_floodcontrol ALTER COLUMN log_type TYPE varchar(30); ----# - ----# add pk -ALTER TABLE {$db_prefix}log_floodcontrol - ADD CONSTRAINT {$db_prefix}log_floodcontrol_pkey PRIMARY KEY(ip, log_type); ----# - -/******************************************************************************/ ---- update log_online ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_online', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old columns -ALTER TABLE {$db_prefix}log_online - ALTER ip DROP not null, - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); ----# - -/******************************************************************************/ ---- update log_reported_comments member_ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}log_reported_comments', 'member_ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old columns -ALTER TABLE {$db_prefix}log_reported_comments - ALTER member_ip DROP not null, - ALTER member_ip DROP default, - ALTER member_ip TYPE inet USING migrate_inet(member_ip); ----# - -/******************************************************************************/ ---- update member_logins ip with ipv6 support -/******************************************************************************/ ----# upgrade check ----{ -$column_info = upgradeGetColumnInfo('{db_prefix}member_logins', 'ip'); -if (stripos($column_info['type'], 'inet') !== false) - $upcontext['skip_db_substeps'] = true; ----} ----# - ----# convert old columns -ALTER TABLE {$db_prefix}member_logins - ALTER ip DROP not null, - ALTER ip DROP default, - ALTER ip TYPE inet USING migrate_inet(ip); -ALTER TABLE {$db_prefix}member_logins - ALTER ip2 DROP not null, - ALTER ip2 DROP default, - ALTER ip2 TYPE inet USING migrate_inet(ip2); ----# - -/******************************************************************************/ ---- Renaming the "profile_other" permission... -/******************************************************************************/ ----# Changing the "profile_other" permission to "profile_website" -UPDATE {$db_prefix}permissions SET permission = 'profile_website_own' WHERE permission = 'profile_other_own'; -UPDATE {$db_prefix}permissions SET permission = 'profile_website_any' WHERE permission = 'profile_other_any'; ----# - -/******************************************************************************/ ---- Adding support for start and end times on calendar events -/******************************************************************************/ ----# Add start_time end_time, and timezone columns to calendar table -ALTER TABLE {$db_prefix}calendar -ADD COLUMN IF NOT EXISTS start_time time, -ADD COLUMN IF NOT EXISTS end_time time, -ADD COLUMN IF NOT EXISTS timezone VARCHAR(80); ----# - ----# Update cal_maxspan and drop obsolete cal_allowspan setting ----{ - if (!isset(Config::$modSettings['cal_allowspan'])) - $cal_maxspan = 0; - elseif (Config::$modSettings['cal_allowspan'] == false) - $cal_maxspan = 1; - else - $cal_maxspan = (Config::$modSettings['cal_maxspan'] > 1) ? Config::$modSettings['cal_maxspan'] : 0; - - upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '$cal_maxspan' - WHERE variable = 'cal_maxspan'"); - - if (isset(Config::$modSettings['cal_allowspan'])) - upgrade_query(" - DELETE FROM {$db_prefix}settings - WHERE variable = 'cal_allowspan'"); ----} ----# - -/******************************************************************************/ ---- Adding location support for calendar events -/******************************************************************************/ ----# Add location column to calendar table -ALTER TABLE {$db_prefix}calendar -ADD COLUMN IF NOT EXISTS location VARCHAR(255) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Updating various calendar settings -/******************************************************************************/ ----# Update the max year for the calendar -UPDATE {$db_prefix}settings -SET value = '2030' -WHERE variable = 'cal_maxyear'; ----# - ----# Adding various calendar settings -INSERT INTO {$db_prefix}settings - (variable, value) -VALUES - ('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') ON CONFLICT DO NOTHING; ----# - -/******************************************************************************/ ---- Update index for like search -/******************************************************************************/ ----# Change index for table log_packages -DROP INDEX IF EXISTS {$db_prefix}log_packages_filename; -CREATE INDEX {$db_prefix}log_packages_filename ON {$db_prefix}log_packages (filename varchar_pattern_ops); ----# - ----# Change index for table members -DROP INDEX IF EXISTS {$db_prefix}members_email_address; -CREATE INDEX {$db_prefix}members_email_address ON {$db_prefix}members (email_address varchar_pattern_ops); -DROP INDEX IF EXISTS {$db_prefix}members_lngfile; -CREATE INDEX {$db_prefix}members_lngfile ON {$db_prefix}members (lngfile varchar_pattern_ops); -DROP INDEX IF EXISTS {$db_prefix}members_member_name; -CREATE INDEX {$db_prefix}members_member_name ON {$db_prefix}members (member_name varchar_pattern_ops); -DROP INDEX IF EXISTS {$db_prefix}members_real_name; -CREATE INDEX {$db_prefix}members_real_name ON {$db_prefix}members (real_name varchar_pattern_ops); ----# - ----# Change index for table scheduled_tasks -DROP INDEX IF EXISTS {$db_prefix}scheduled_tasks_task; -CREATE UNIQUE INDEX {$db_prefix}scheduled_tasks_task ON {$db_prefix}scheduled_tasks (task varchar_pattern_ops); ----# - ----# Change index for table admin_info_files -DROP INDEX IF EXISTS {$db_prefix}admin_info_files_filename; -CREATE INDEX {$db_prefix}admin_info_files_filename ON {$db_prefix}admin_info_files (filename varchar_pattern_ops); ----# - ----# Change index for table boards -DROP INDEX IF EXISTS {$db_prefix}boards_member_groups; -CREATE INDEX {$db_prefix}boards_member_groups ON {$db_prefix}boards (member_groups varchar_pattern_ops); ----# - ----# Change index for table log_comments -DROP INDEX IF EXISTS {$db_prefix}log_comments_comment_type; -CREATE INDEX {$db_prefix}log_comments_comment_type ON {$db_prefix}log_comments (comment_type varchar_pattern_ops); ----# - -/******************************************************************************/ ---- Migrating pm notification settings -/******************************************************************************/ ----# Upgrading pm notification settings ----{ -// First see if we still have a pm_email_notify column -$results = Db::$db->list_columns('{db_prefix}members'); -if (in_array('pm_email_notify', $results)) -{ - $_GET['a'] = isset($_GET['a']) ? (int) $_GET['a'] : 0; - $step_progress['name'] = 'Upgrading pm notification settings'; - $step_progress['current'] = $_GET['a']; - - $limit = 10000; - $is_done = false; - - $request = Db::$db->query('SELECT COUNT(*) FROM {db_prefix}members'); - list($maxMembers) = Db::$db->fetch_row($request); - Db::$db->free_result($request); - - while (!$is_done) - { - nextSubStep($substep); - $inserts = array(); - - // Skip errors here so we don't croak if the columns don't exist... - $request = Db::$db->query( - 'SELECT id_member, pm_email_notify - FROM {db_prefix}members - ORDER BY id_member - LIMIT {int:start}, {int:limit}', - array( - 'db_error_skip' => true, - 'start' => $_GET['a'], - 'limit' => $limit, - ) - ); - if (Db::$db->num_rows($request) != 0) - { - while ($row = Db::$db->fetch_assoc($request)) - { - $inserts[] = array($row['id_member'], 'pm_new', !empty($row['pm_email_notify']) ? 2 : 0); - $inserts[] = array($row['id_member'], 'pm_notify', $row['pm_email_notify'] == 2 ? 2 : 1); - } - Db::$db->free_result($request); - } - - Db::$db->insert('ignore', - '{db_prefix}user_alerts_prefs', - array('id_member' => 'int', 'alert_pref' => 'string', 'alert_value' => 'string'), - $inserts, - array('id_member', 'alert_pref') - ); - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - - if ($step_progress['current'] >= $maxMembers) - $is_done = true; - } - unset($_GET['a']); -} ----} ----# - ----# drop column pm_email_notify on table members -ALTER TABLE {$db_prefix}members DROP COLUMN IF EXISTS pm_email_notify; ----# - -/******************************************************************************/ ---- Cleaning up after old UTF-8 languages -/******************************************************************************/ ----# Update the members' languages -UPDATE {$db_prefix}members -SET lngfile = REPLACE(lngfile, '-utf8', ''); ----# - -/******************************************************************************/ ---- Create index for birthday calendar query -/******************************************************************************/ ----# Create help function for index ----{ -upgrade_query(" - CREATE OR REPLACE FUNCTION indexable_month_day(date) RETURNS TEXT as ' - SELECT to_char($1, ''MM-DD'');' - LANGUAGE 'sql' IMMUTABLE STRICT;" -); ----} ----# - ----# Create index members_birthdate2 -DROP INDEX IF EXISTS {$db_prefix}members_birthdate2; -CREATE INDEX {$db_prefix}members_birthdate2 ON {$db_prefix}members (indexable_month_day(birthdate)); ----# - -/******************************************************************************/ ---- Create index for messages likes -/******************************************************************************/ ----# Add Index for messages likes -DROP INDEX IF EXISTS {$db_prefix}messages_likes; -CREATE INDEX {$db_prefix}messages_likes ON {$db_prefix}messages (likes); ----# - -/******************************************************************************/ ---- Create index for messages board, msg, approved -/******************************************************************************/ ----# Remove old approved index -DROP INDEX IF EXISTS {$db_prefix}messages_approved; ----# - ----# Add Index for messages board, msg, approved -DROP INDEX IF EXISTS {$db_prefix}messages_id_board; -CREATE UNIQUE INDEX {$db_prefix}messages_id_board ON {$db_prefix}messages (id_board, id_msg, approved); ----# - -/******************************************************************************/ ---- Update smileys -/******************************************************************************/ ----# Adding the new `smiley_files` table -CREATE TABLE IF NOT EXISTS {$db_prefix}smiley_files -( - id_smiley smallint NOT NULL DEFAULT '0', - smiley_set varchar(48) NOT NULL DEFAULT '', - filename varchar(48) NOT NULL DEFAULT '', - PRIMARY KEY (id_smiley, smiley_set) -); ----# - ----# Cleaning up unused smiley sets and adding the lovely new ones ----{ -// 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 = array(); -foreach ($dirs AS $ix => $dir) -{ - if (!empty($setnames[$ix])) - $combined[$dir] = array($setnames[$ix], ''); -} - -// Add our lovely new 2.1 smiley sets if not already there -$combined['fugue'] = array(Lang::$txt['default_fugue_smileyset_name'], 'png'); -$combined['alienine'] = array(Lang::$txt['default_alienine_smileyset_name'], 'png'); - -// Add/fix our 2.0 sets (to correct past problems where these got corrupted) -$combined['default'] = array(Lang::$txt['default_legacy_smileyset_name'], 'gif'); -$combined['aaron'] = array(Lang::$txt['default_aaron_smileyset_name'], 'gif'); -$combined['akyhne'] = array(Lang::$txt['default_akyhne_smileyset_name'], 'gif'); - -// Confirm they exist in the filesystem -$filtered = array(); -foreach ($combined as $dir => $attrs) -{ - if (is_dir(Config::$modSettings['smileys_dir'] . '/' . $dir . '/')) - $filtered[$dir] = $attrs[0]; -} - -// Update the Settings Table... -upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . Db::$db->escape_string(implode(',', array_keys($filtered))) . "' - WHERE variable = 'smiley_sets_known'"); - -upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . Db::$db->escape_string(implode("\n", $filtered)) . "' - WHERE variable = 'smiley_sets_names'"); - -// Populate the smiley_files table -$smileys_columns = Db::$db->list_columns('{db_prefix}smileys'); -if (in_array('filename', $smileys_columns)) -{ - $inserts = array(); - - $request = upgrade_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[] = array($row['id_smiley'], $set, $pathinfo['filename'] . '.' . $ext); - } - } - Db::$db->free_result($request); - - if (!empty($inserts)) - { - Db::$db->insert('ignore', - '{db_prefix}smiley_files', - array('id_smiley' => 'int', 'smiley_set' => 'string-48', 'filename' => 'string-48'), - $inserts, - array('id_smiley', 'smiley_set') - ); - - // Unless something went horrifically wrong, drop the defunct column - if (count($inserts) == Db::$db->affected_rows()) - upgrade_query(" - ALTER TABLE {$db_prefix}smileys - DROP COLUMN IF EXISTS filename;"); - } -} - -// 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)) - $newdefault = reset(array_keys($filtered)); - else - $newdefault = ''; - upgrade_query(" - UPDATE {$db_prefix}settings - SET value = '" . $newdefault . "' - WHERE variable = 'smiley_sets_default'"); -} - ----} ----# - -/******************************************************************************/ ---- Add backtrace to log_error -/******************************************************************************/ ----# add backtrace column -ALTER TABLE {$db_prefix}log_errors -ADD COLUMN IF NOT EXISTS backtrace text NOT NULL default ''; ----# - -/******************************************************************************/ ---- Update permissions system board_permissions_view -/******************************************************************************/ ----# Create table board_permissions_view -CREATE TABLE IF NOT EXISTS {$db_prefix}board_permissions_view -( - id_group smallint NOT NULL DEFAULT '0', - id_board smallint NOT NULL, - deny smallint NOT NULL, - PRIMARY KEY (id_group, id_board, deny) -); - ----# upgrade check ----{ - // 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'); -$upcontext['skip_db_substeps'] = !in_array('id_group', $table_columns) || !in_array('member_groups', $table_columns2) || !in_array('deny_member_groups', $table_columns2); ----} ----# - ----# -TRUNCATE {$db_prefix}board_permissions_view; ----# - ----# Update board_permissions_view table with membergroups -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) 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); ----# - ----# Update board_permissions_view table with -1 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, -1, 0 -FROM {$db_prefix}boards b -where (FIND_IN_SET(-1, b.member_groups) != 0); ----# - ----# Update board_permissions_view table with 0 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, 0, 0 -FROM {$db_prefix}boards b -where (FIND_IN_SET(0, b.member_groups) != 0); ----# - ----# Update deny board_permissions_view table with membergroups -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) 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); ----# - ----# Update deny board_permissions_view table with -1 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, -1, 1 -FROM {$db_prefix}boards b -where (FIND_IN_SET(-1, b.deny_member_groups) != 0); ----# - ----# Update deny board_permissions_view table with 0 -INSERT INTO {$db_prefix}board_permissions_view (id_board, id_group, deny) SELECT id_board, 0, 1 -FROM {$db_prefix}boards b -where (FIND_IN_SET(0, b.deny_member_groups) != 0); ----# - -/******************************************************************************/ ---- Correct schema diff -/******************************************************************************/ ----# log_subscribed -ALTER TABLE {$db_prefix}log_subscribed -ALTER pending_details DROP DEFAULT; ----# - ----# mail_queue -ALTER TABLE {$db_prefix}mail_queue -ALTER recipient SET DEFAULT ''; - -ALTER TABLE {$db_prefix}mail_queue -ALTER subject SET DEFAULT ''; ----# - ----# members -ALTER TABLE {$db_prefix}members -ALTER lngfile SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER real_name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER pm_ignore_list SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER pm_ignore_list TYPE TEXT, -ALTER pm_ignore_list DROP NOT NULL, -ALTER pm_ignore_list DROP DEFAULT; - -ALTER TABLE {$db_prefix}members -ALTER email_address SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER personal_text SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER website_title SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER website_url SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER avatar SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER usertitle SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER secret_question SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER additional_groups SET DEFAULT ''; - -ALTER TABLE {$db_prefix}members -ALTER COLUMN password_salt TYPE varchar(255); ----# - ----# messages -ALTER TABLE {$db_prefix}messages -ALTER subject SET DEFAULT ''; - -ALTER TABLE {$db_prefix}messages -ALTER poster_name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}messages -ALTER poster_email SET DEFAULT ''; ----# - ----# package_servers -ALTER TABLE {$db_prefix}package_servers -ALTER name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}package_servers -ALTER url SET DEFAULT ''; ----# - ----# permission_profiles -ALTER TABLE {$db_prefix}permission_profiles -ALTER profile_name SET DEFAULT ''; ----# - ----# personal_messages -ALTER TABLE {$db_prefix}personal_messages -ALTER subject SET DEFAULT ''; ----# - ----# polls -ALTER TABLE {$db_prefix}polls -ALTER question SET DEFAULT ''; ----# - ----# poll_choices -ALTER TABLE {$db_prefix}poll_choices -ALTER label SET DEFAULT ''; ----# - ----# settings -ALTER TABLE {$db_prefix}settings -ALTER variable SET DEFAULT ''; ----# - ----# sessions -ALTER TABLE {$db_prefix}sessions -ALTER session_id SET DEFAULT ''; - -ALTER TABLE {$db_prefix}sessions -ALTER last_update SET DEFAULT 0; ----# - ----# spiders -ALTER TABLE {$db_prefix}spiders -ALTER spider_name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}spiders -ALTER user_agent SET DEFAULT ''; - -ALTER TABLE {$db_prefix}spiders -ALTER ip_info SET DEFAULT ''; ----# - ----# subscriptions -ALTER TABLE {$db_prefix}subscriptions -ALTER id_subscribe TYPE int; - -ALTER TABLE {$db_prefix}subscriptions -ALTER name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}subscriptions -ALTER description SET DEFAULT ''; - -ALTER TABLE {$db_prefix}subscriptions -ALTER length SET DEFAULT ''; - -ALTER TABLE {$db_prefix}subscriptions -ALTER add_groups SET DEFAULT ''; ----# - ----# themes -ALTER TABLE {$db_prefix}themes -ALTER variable SET DEFAULT ''; ----# - ----# admin_info_files -ALTER TABLE {$db_prefix}admin_info_files -ALTER filename SET DEFAULT ''; - -ALTER TABLE {$db_prefix}admin_info_files -ALTER path SET DEFAULT ''; - -ALTER TABLE {$db_prefix}admin_info_files -ALTER parameters SET DEFAULT ''; - -ALTER TABLE {$db_prefix}admin_info_files -ALTER filetype SET DEFAULT ''; ----# - ----# attachments -ALTER TABLE {$db_prefix}attachments -ALTER filename SET DEFAULT ''; ----# - ----# ban_items -ALTER TABLE {$db_prefix}ban_items -ALTER hostname SET DEFAULT ''; - -ALTER TABLE {$db_prefix}ban_items -ALTER email_address SET DEFAULT ''; ----# - ----# boards -ALTER TABLE {$db_prefix}boards -ALTER name SET DEFAULT ''; ----# - ----# categories -ALTER TABLE {$db_prefix}categories -ALTER name SET DEFAULT ''; ----# - ----# custom_fields -ALTER TABLE {$db_prefix}custom_fields -ALTER field_desc SET DEFAULT ''; - -ALTER TABLE {$db_prefix}custom_fields -ALTER mask SET DEFAULT ''; - -ALTER TABLE {$db_prefix}custom_fields -ALTER default_value SET DEFAULT ''; ----# - ----# log_banned -ALTER TABLE {$db_prefix}log_banned -ALTER email SET DEFAULT ''; ----# - ----# log_comments -ALTER TABLE {$db_prefix}log_comments -ALTER recipient_name SET DEFAULT ''; ----# - ----# log_digest -ALTER TABLE {$db_prefix}log_digest -ALTER id_topic SET DEFAULT 0; - -ALTER TABLE {$db_prefix}log_digest -ALTER id_msg SET DEFAULT 0; ----# - ----# log_errors -ALTER TABLE {$db_prefix}log_errors -ALTER file SET DEFAULT ''; ----# - ----# log_member_notices -ALTER TABLE {$db_prefix}log_member_notices -ALTER subject SET DEFAULT ''; ----# - ----# log_online -ALTER TABLE {$db_prefix}log_online -ALTER url SET DEFAULT ''; ----# - ----# log_packages -ALTER TABLE {$db_prefix}log_packages -ALTER filename SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_packages -ALTER package_id SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_packages -ALTER name SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_packages -ALTER version SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_packages -ALTER themes_installed SET DEFAULT ''; ----# - ----# log_reported -ALTER TABLE {$db_prefix}log_reported -ALTER membername SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_reported -ALTER subject SET DEFAULT ''; ----# - ----# log_reported_comments -ALTER TABLE {$db_prefix}log_reported_comments -ALTER membername SET DEFAULT ''; - -ALTER TABLE {$db_prefix}log_reported_comments -ALTER comment SET DEFAULT ''; ----# - ----# log_actions -DROP INDEX IF EXISTS {$db_prefix}log_actions_id_topic_id_log; -CREATE INDEX {$db_prefix}log_actions_id_topic_id_log ON {$db_prefix}log_actions (id_topic, id_log); ----# - -/******************************************************************************/ ---- FROM_UNIXTIME fix -/******************************************************************************/ ----# Drop the old int version -DROP FUNCTION IF EXISTS FROM_UNIXTIME(int); ----# - ----# Add FROM_UNIXTIME for bigint -CREATE OR REPLACE FUNCTION FROM_UNIXTIME(bigint) RETURNS timestamp AS - 'SELECT timestamp ''epoch'' + $1 * interval ''1 second'' AS result' -LANGUAGE 'sql'; ----# - -/******************************************************************************/ ---- bigint versions of date functions -/******************************************************************************/ ----# MONTH(bigint) -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) -CREATE OR REPLACE FUNCTION DAYOFMONTH (bigint) RETURNS integer AS - 'SELECT CAST (EXTRACT(DAY FROM TO_TIMESTAMP($1)) AS integer) AS result' -LANGUAGE 'sql'; ----# - -/******************************************************************************/ ---- Update holidays -/******************************************************************************/ ----# Delete all the dates -DELETE FROM {$db_prefix}calendar_holidays WHERE title in -('Mother''s Day','Father''s Day', 'Summer Solstice', 'Vernal Equinox', 'Winter Solstice', 'Autumnal Equinox', - 'Thanksgiving', 'Memorial Day', 'Labor Day', 'New Year''s', 'Christmas', 'Valentine''s Day', 'St. Patrick''s Day', - 'April Fools', 'Earth Day', 'United Nations Day', 'Halloween', 'Independence Day', 'Cinco de Mayo', 'Flag Day', - 'Veterans Day', 'Groundhog Day', 'D-Day'); ----# - ----# Insert the updated dates -INSERT INTO {$db_prefix}calendar_holidays - (title, event_date) -VALUES ('New Year''s', '1004-01-01'), - ('Christmas', '1004-12-25'), - ('Valentine''s Day', '1004-02-14'), - ('St. Patrick''s Day', '1004-03-17'), - ('April Fools', '1004-04-01'), - ('Earth Day', '1004-04-22'), - ('United Nations Day', '1004-10-24'), - ('Halloween', '1004-10-31'), - ('Mother''s Day', '2010-05-09'), - ('Mother''s Day', '2011-05-08'), - ('Mother''s Day', '2012-05-13'), - ('Mother''s Day', '2013-05-12'), - ('Mother''s Day', '2014-05-11'), - ('Mother''s Day', '2015-05-10'), - ('Mother''s Day', '2016-05-08'), - ('Mother''s Day', '2017-05-14'), - ('Mother''s Day', '2018-05-13'), - ('Mother''s Day', '2019-05-12'), - ('Mother''s Day', '2020-05-10'), - ('Mother''s Day', '2021-05-09'), - ('Mother''s Day', '2022-05-08'), - ('Mother''s Day', '2023-05-14'), - ('Mother''s Day', '2024-05-12'), - ('Mother''s Day', '2025-05-11'), - ('Mother''s Day', '2026-05-10'), - ('Mother''s Day', '2027-05-09'), - ('Mother''s Day', '2028-05-14'), - ('Mother''s Day', '2029-05-13'), - ('Mother''s Day', '2030-05-12'), - ('Father''s Day', '2010-06-20'), - ('Father''s Day', '2011-06-19'), - ('Father''s Day', '2012-06-17'), - ('Father''s Day', '2013-06-16'), - ('Father''s Day', '2014-06-15'), - ('Father''s Day', '2015-06-21'), - ('Father''s Day', '2016-06-19'), - ('Father''s Day', '2017-06-18'), - ('Father''s Day', '2018-06-17'), - ('Father''s Day', '2019-06-16'), - ('Father''s Day', '2020-06-21'), - ('Father''s Day', '2021-06-20'), - ('Father''s Day', '2022-06-19'), - ('Father''s Day', '2023-06-18'), - ('Father''s Day', '2024-06-16'), - ('Father''s Day', '2025-06-15'), - ('Father''s Day', '2026-06-21'), - ('Father''s Day', '2027-06-20'), - ('Father''s Day', '2028-06-18'), - ('Father''s Day', '2029-06-17'), - ('Father''s Day', '2030-06-16'), - ('Summer Solstice', '2010-06-21'), - ('Summer Solstice', '2011-06-21'), - ('Summer Solstice', '2012-06-20'), - ('Summer Solstice', '2013-06-21'), - ('Summer Solstice', '2014-06-21'), - ('Summer Solstice', '2015-06-21'), - ('Summer Solstice', '2016-06-20'), - ('Summer Solstice', '2017-06-20'), - ('Summer Solstice', '2018-06-21'), - ('Summer Solstice', '2019-06-21'), - ('Summer Solstice', '2020-06-20'), - ('Summer Solstice', '2021-06-21'), - ('Summer Solstice', '2022-06-21'), - ('Summer Solstice', '2023-06-21'), - ('Summer Solstice', '2024-06-20'), - ('Summer Solstice', '2025-06-21'), - ('Summer Solstice', '2026-06-21'), - ('Summer Solstice', '2027-06-21'), - ('Summer Solstice', '2028-06-20'), - ('Summer Solstice', '2029-06-21'), - ('Summer Solstice', '2030-06-21'), - ('Vernal Equinox', '2010-03-20'), - ('Vernal Equinox', '2011-03-20'), - ('Vernal Equinox', '2012-03-20'), - ('Vernal Equinox', '2013-03-20'), - ('Vernal Equinox', '2014-03-20'), - ('Vernal Equinox', '2015-03-20'), - ('Vernal Equinox', '2016-03-20'), - ('Vernal Equinox', '2017-03-20'), - ('Vernal Equinox', '2018-03-20'), - ('Vernal Equinox', '2019-03-20'), - ('Vernal Equinox', '2020-03-20'), - ('Vernal Equinox', '2021-03-20'), - ('Vernal Equinox', '2022-03-20'), - ('Vernal Equinox', '2023-03-20'), - ('Vernal Equinox', '2024-03-20'), - ('Vernal Equinox', '2025-03-20'), - ('Vernal Equinox', '2026-03-20'), - ('Vernal Equinox', '2027-03-20'), - ('Vernal Equinox', '2028-03-20'), - ('Vernal Equinox', '2029-03-20'), - ('Vernal Equinox', '2030-03-20'), - ('Winter Solstice', '2010-12-21'), - ('Winter Solstice', '2011-12-22'), - ('Winter Solstice', '2012-12-21'), - ('Winter Solstice', '2013-12-21'), - ('Winter Solstice', '2014-12-21'), - ('Winter Solstice', '2015-12-22'), - ('Winter Solstice', '2016-12-21'), - ('Winter Solstice', '2017-12-21'), - ('Winter Solstice', '2018-12-21'), - ('Winter Solstice', '2019-12-22'), - ('Winter Solstice', '2020-12-21'), - ('Winter Solstice', '2021-12-21'), - ('Winter Solstice', '2022-12-21'), - ('Winter Solstice', '2023-12-22'), - ('Winter Solstice', '2024-12-21'), - ('Winter Solstice', '2025-12-21'), - ('Winter Solstice', '2026-12-21'), - ('Winter Solstice', '2027-12-22'), - ('Winter Solstice', '2028-12-21'), - ('Winter Solstice', '2029-12-21'), - ('Winter Solstice', '2030-12-21'), - ('Autumnal Equinox', '2010-09-23'), - ('Autumnal Equinox', '2011-09-23'), - ('Autumnal Equinox', '2012-09-22'), - ('Autumnal Equinox', '2013-09-22'), - ('Autumnal Equinox', '2014-09-23'), - ('Autumnal Equinox', '2015-09-23'), - ('Autumnal Equinox', '2016-09-22'), - ('Autumnal Equinox', '2017-09-22'), - ('Autumnal Equinox', '2018-09-23'), - ('Autumnal Equinox', '2019-09-23'), - ('Autumnal Equinox', '2020-09-22'), - ('Autumnal Equinox', '2021-09-22'), - ('Autumnal Equinox', '2022-09-23'), - ('Autumnal Equinox', '2023-09-23'), - ('Autumnal Equinox', '2024-09-22'), - ('Autumnal Equinox', '2025-09-22'), - ('Autumnal Equinox', '2026-09-23'), - ('Autumnal Equinox', '2027-09-23'), - ('Autumnal Equinox', '2028-09-22'), - ('Autumnal Equinox', '2029-09-22'), - ('Autumnal Equinox', '2030-09-22'); - -INSERT INTO {$db_prefix}calendar_holidays - (title, event_date) -VALUES ('Independence Day', '1004-07-04'), - ('Cinco de Mayo', '1004-05-05'), - ('Flag Day', '1004-06-14'), - ('Veterans Day', '1004-11-11'), - ('Groundhog Day', '1004-02-02'), - ('Thanksgiving', '2010-11-25'), - ('Thanksgiving', '2011-11-24'), - ('Thanksgiving', '2012-11-22'), - ('Thanksgiving', '2013-11-28'), - ('Thanksgiving', '2014-11-27'), - ('Thanksgiving', '2015-11-26'), - ('Thanksgiving', '2016-11-24'), - ('Thanksgiving', '2017-11-23'), - ('Thanksgiving', '2018-11-22'), - ('Thanksgiving', '2019-11-28'), - ('Thanksgiving', '2020-11-26'), - ('Thanksgiving', '2021-11-25'), - ('Thanksgiving', '2022-11-24'), - ('Thanksgiving', '2023-11-23'), - ('Thanksgiving', '2024-11-28'), - ('Thanksgiving', '2025-11-27'), - ('Thanksgiving', '2026-11-26'), - ('Thanksgiving', '2027-11-25'), - ('Thanksgiving', '2028-11-23'), - ('Thanksgiving', '2029-11-22'), - ('Thanksgiving', '2030-11-28'), - ('Memorial Day', '2010-05-31'), - ('Memorial Day', '2011-05-30'), - ('Memorial Day', '2012-05-28'), - ('Memorial Day', '2013-05-27'), - ('Memorial Day', '2014-05-26'), - ('Memorial Day', '2015-05-25'), - ('Memorial Day', '2016-05-30'), - ('Memorial Day', '2017-05-29'), - ('Memorial Day', '2018-05-28'), - ('Memorial Day', '2019-05-27'), - ('Memorial Day', '2020-05-25'), - ('Memorial Day', '2021-05-31'), - ('Memorial Day', '2022-05-30'), - ('Memorial Day', '2023-05-29'), - ('Memorial Day', '2024-05-27'), - ('Memorial Day', '2025-05-26'), - ('Memorial Day', '2026-05-25'), - ('Memorial Day', '2027-05-31'), - ('Memorial Day', '2028-05-29'), - ('Memorial Day', '2029-05-28'), - ('Memorial Day', '2030-05-27'), - ('Labor Day', '2010-09-06'), - ('Labor Day', '2011-09-05'), - ('Labor Day', '2012-09-03'), - ('Labor Day', '2013-09-02'), - ('Labor Day', '2014-09-01'), - ('Labor Day', '2015-09-07'), - ('Labor Day', '2016-09-05'), - ('Labor Day', '2017-09-04'), - ('Labor Day', '2018-09-03'), - ('Labor Day', '2019-09-02'), - ('Labor Day', '2020-09-07'), - ('Labor Day', '2021-09-06'), - ('Labor Day', '2022-09-05'), - ('Labor Day', '2023-09-04'), - ('Labor Day', '2024-09-02'), - ('Labor Day', '2025-09-01'), - ('Labor Day', '2026-09-07'), - ('Labor Day', '2027-09-06'), - ('Labor Day', '2028-09-04'), - ('Labor Day', '2029-09-03'), - ('Labor Day', '2030-09-02'), - ('D-Day', '1004-06-06'); ----# - -/******************************************************************************/ ---- Add Attachments index -/******************************************************************************/ ----# Create new index on Attachments -DROP INDEX IF EXISTS {$db_prefix}attachments_id_thumb; -CREATE INDEX {$db_prefix}attachments_id_thumb ON {$db_prefix}attachments (id_thumb); ----# - -/******************************************************************************/ ---- Update log_spider_stats -/******************************************************************************/ ----# Allow for hyper aggressive crawlers -ALTER TABLE {$db_prefix}log_spider_stats ALTER COLUMN page_hits TYPE INT; ----# - -/******************************************************************************/ ---- Update policy & agreement settings -/******************************************************************************/ ----# Strip -utf8 from policy settings ----{ -$utf8_policy_settings = array(); -foreach(Config::$modSettings AS $k => $v) -{ - if ((substr($k, 0, 7) === 'policy_') && (substr($k, -5) === '-utf8')) - $utf8_policy_settings[$k] = $v; -} -$adds = array(); -$deletes = array(); -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)) - { - $adds[] = '(\'policy_' . $language . '\', \'' . Db::$db->escape_string($val) . '\')'; - $deletes[] = '\'' . $var . '\''; - } -} -if (!empty($adds)) -{ - upgrade_query(" - INSERT INTO {$db_prefix}settings (variable, value) - VALUES " . implode(', ', $adds) - ); -} -if (!empty($deletes)) -{ - upgrade_query(" - DELETE FROM {$db_prefix}settings - WHERE variable IN (" . implode(', ', $deletes) . ") - "); -} - ----} ----# - ----# 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); -} - ----} ----# - ----# Fix missing values in log_actions ----{ - $current_substep = !isset($_GET['substep']) ? 0 : (int) $_GET['substep']; - - // Setup progress bar - if (!isset($_GET['total_fixes']) || !isset($_GET['a']) || !isset($_GET['last_action_id'])) - { - $request = Db::$db->query( - 'SELECT COUNT(*) - FROM {db_prefix}log_actions - WHERE id_member = {int:blank_id} - AND action IN ({array_string:target_actions})', - array( - 'blank_id' => 0, - 'target_actions' => array('policy_accepted', 'agreement_accepted'), - ) - ); - list ($step_progress['total']) = Db::$db->fetch_row($request); - $_GET['total_fixes'] = $step_progress['total']; - Db::$db->free_result($request); - - $_GET['a'] = 0; - $_GET['last_action_id'] = 0; - } - - $step_progress['name'] = 'Fixing missing IDs in log_actions'; - $step_progress['current'] = $_GET['a']; - $step_progress['total'] = $_GET['total_fixes']; - - // Main process loop - $limit = 10000; - $is_done = false; - while (!$is_done) - { - // Keep looping at the current step. - nextSubstep($current_substep); - - $extras = array(); - $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}', - array( - 'blank_id' => 0, - 'target_actions' => array('policy_accepted', 'agreement_accepted'), - 'last' => $_GET['last_action_id'], - 'limit' => $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 - $_GET['last_action_id'] = max(array_keys($extras)); - - foreach ($extras AS $id => $extra_ser) - { - $extra = 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}', - array( - 'id_member' => $extra['applicator'], - 'id_action' => $id, - ) - ); - } - } - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - } - - $step_progress = array(); - unset($_GET['a']); - unset($_GET['last_action_id']); - unset($_GET['total_fixes']); ----} ----# diff --git a/other/upgrade_3-0_MySQL.sql b/other/upgrade_3-0_MySQL.sql deleted file mode 100644 index c085e6ce7c..0000000000 --- a/other/upgrade_3-0_MySQL.sql +++ /dev/null @@ -1,1046 +0,0 @@ -/* ATTENTION: You don't need to run or use this file! The upgrade.php script does everything for you! */ - -/******************************************************************************/ ---- Language Upgrade... -/******************************************************************************/ - ----# Upgrading language settings ----{ -$limit = 10000; -$statements = []; -$langs = []; -$args = ['defaultLang' => '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) -{ - nextSubStep($substep); - - // 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' => $limit, - 'possible_languages' => $langs - ] - ); - if (Db::$db->num_rows($request) == 0) { - $is_done = true; - break; - } else { - 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; - } else { - $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 - ); -} - -// Rename the privacy policy records. -foreach (Config::$modSettings as $variable => $value) { - if (!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 = 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); - } -} ----} ----# - -/******************************************************************************/ ---- Updating log_errors table -/******************************************************************************/ - ----# Fixing the default for the sessions column ----{ -Db::$db->change_column('{db_prefix}log_errors', 'session', ['default' => '']); ----} ----# - -/******************************************************************************/ ---- Adding version information to posts, polls, and personal messages -/******************************************************************************/ - ----# Adding a new column "version" to messages table ----{ -$cols = Db::$db->list_columns('{db_prefix}messages'); - -if (!in_array('version', $cols)) { - Db::$db->add_column( - '{db_prefix}messages', - [ - 'name' => 'version', - 'type' => 'varchar', - 'size' => 5, - 'null' => false, - 'default' => '', - ], - ); -} ----} ----# - ----# Adding a new column "version" to personal_messages table ----{ -$cols = Db::$db->list_columns('{db_prefix}personal_messages'); - -if (!in_array('version', $cols)) { - Db::$db->add_column( - '{db_prefix}personal_messages', - [ - 'name' => 'version', - 'type' => 'varchar', - 'size' => 5, - 'null' => false, - 'default' => '', - ], - ); -} ----} ----# - -/******************************************************************************/ ---- Adding support for recurring events... -/******************************************************************************/ - ----# Adding support for recurring events... ----{ -$cols = Db::$db->list_columns('{db_prefix}calendar'); - -if (in_array('end_time', $cols)) { - Db::$db->query('ALTER TABLE {db_prefix}calendar - MODIFY COLUMN start_date DATE AFTER id_member', - [], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'duration', - 'type' => 'varchar', - 'size' => 32, - 'null' => false, - 'default' => '', - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'rrule', - 'type' => 'varchar', - 'size' => 1024, - 'null' => false, - 'default' => 'FREQ=YEARLY;COUNT=1', - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'rdates', - 'type' => 'text', - 'null' => false, - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'exdates', - 'type' => 'text', - 'null' => false, - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'adjustments', - 'type' => 'json', - 'null' => true, - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'sequence', - 'type' => 'smallint', - 'unsigned' => true, - 'null' => false, - 'default' => 0, - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'uid', - 'type' => 'varchar', - 'size' => 255, - 'null' => false, - 'default' => '', - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'type', - 'type' => 'tinyint', - 'unsigned' => true, - 'null' => false, - 'default' => 0, - ], - ); - Db::$db->add_column( - '{db_prefix}calendar', - [ - 'name' => 'enabled', - 'type' => 'tinyint', - 'unsigned' => true, - 'null' => false, - 'default' => 1, - ], - ); - - $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'); - - $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'); -} ----} ----# - ----# Migrate holidays to events ----{ -$exists = count(Db::$db->list_tables(false, '%calendar_holidays')) > 0; - -if ($exists) { - if (!isset(\SMF\User::$me)) { - \SMF\User::load(); - } - - if (empty(\SMF\User::$me->id) && !empty($upcontext['user']['id'])) { - \SMF\User::setMe($upcontext['user']['id']); - } - - $known_holidays = [ - 'April Fools' => [ - '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', - ], - ], - ]; - - $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($known_holidays[$row['title']])) { - $holiday = &$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 \SMF\Time($holiday['start_date'] . (!$holiday['allday'] ? ' ' . $holiday['start_time'] . ' ' . $holiday['timezone'] : '')); - $holiday['duration'] = new \DateInterval($holiday['duration'] ?? 'P1D'); - $holiday['recurrence_end'] = new \SMF\Time($holiday['recurrence_end']); - unset($holiday['start_date'], $holiday['start_time'], $holiday['timezone']); - - $event = new \SMF\Calendar\Event(0, $known_holidays[$row['title']]); - } else { - $row['type'] = 1; - $row['allday'] = true; - $row['recurrence_end'] = new \SMF\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 \SMF\Time(preg_replace('/^100\d-/', '2000-', $row['start'])); - $row['rrule'] = 'FREQ=YEARLY'; - } else { - $row['start'] = new \SMF\Time($row['start']); - $row['rrule'] = 'FREQ=DAILY;COUNT=1'; - } - - $event = new \SMF\Calendar\Event(0, $row); - } - - $event->save(); - } - - Db::$db->free_result($request); -} ----} ----# - ----# Setting 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, - ); -} ----} ----# - ----# Dropping "calendar_holidays" ----{ -Db::$db->drop_table('{db_prefix}calendar_holidays'); ----} ----# - -/******************************************************************************/ ---- Adding SpoofDetector support -/******************************************************************************/ - ----# Adding a new column "spoofdetector_name" to members table ----{ -Db::$db->add_column( - '{db_prefix}members', - [ - 'name' => 'spoofdetector_name', - 'type' => 'varchar', - 'size' => 255, - 'null' => false, - 'default' => '', - ], - [], - 'ignore', -); -Db::$db->add_index( - '{db_prefix}members', - [ - 'name' => 'idx_spoofdetector_name', - 'columns' => ['spoofdetector_name'], - ], - [], - 'ignore', -); -Db::$db->add_index( - '{db_prefix}members', - [ - 'name' => 'idx_spoofdetector_name_id', - 'columns' => ['spoofdetector_name', 'id_member'], - ], - [], - 'ignore', -); ----} ----# - ----# Adding new "spoofdetector_censor" setting -INSERT IGNORE INTO {$db_prefix}settings (variable, value) VALUES ('spoofdetector_censor', '1'); ----# - -/******************************************************************************/ ---- Adding SMF version information to log_packages -/******************************************************************************/ - ----# Adding a new column "smf_version" to log_packages table -ALTER TABLE {$db_prefix}log_packages -ADD COLUMN smf_version VARCHAR(5) NOT NULL DEFAULT ''; ----# - -/******************************************************************************/ ---- Improving search results storage -/******************************************************************************/ - ----# Updating primary key for log_search_results table -ALTER TABLE {$db_prefix}log_search_results DROP PRIMARY KEY; -ALTER TABLE {$db_prefix}log_search_results ADD PRIMARY KEY (id_search, id_topic, id_msg); ----# - -/******************************************************************************/ ---- Updating Settings -/******************************************************************************/ - ----# Update mail_type -UPDATE {$db_prefix}settings -SET value = - CASE - WHEN value = 0 - THEN 'SendMail' - WHEN value = 1 - THEN 'SMTP' - WHEN value = 2 - THEN 'SMTPTLS' - ELSE - value - END -WHERE variable = 'mail_type' - AND value IN (0,1,2); ----# - ----# Remove cookieTime setting -DELETE FROM {$db_prefix}settings -WHERE variable = 'cookieTime'; ----#