diff --git a/Sources/Maintenance/Migration/v2_1/Migration1000.php b/Sources/Maintenance/Migration/v2_1/Migration1000.php new file mode 100644 index 0000000000..77a3c64f06 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1000.php @@ -0,0 +1,142 @@ + 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, + ]; + + /** + * {@inheritDoc} + */ + public function isCandidate(): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + 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'] + ]); + + // 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 ($newSettings as $key => $default) { + if (!isset(Config::$modSettings[$key])) { + $newSettings[$key] = $default; + } + } + + // 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)) { + 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'; + } + + Config::updateModSettings($newSettings); + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1003.php b/Sources/Maintenance/Migration/v2_1/Migration1003.php new file mode 100644 index 0000000000..61a00e2a91 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1003.php @@ -0,0 +1,56 @@ +list_tables(); + + if (!in_array(Config::$db_prefix . 'member_logins', $tables)) { + $member_logins = new MemberLogins(); + $member_logins->create(); + } + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1004.php b/Sources/Maintenance/Migration/v2_1/Migration1004.php new file mode 100644 index 0000000000..c29581d2ab --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1004.php @@ -0,0 +1,75 @@ +list_tables(); + + return in_array(Config::$db_prefix . 'collapsed_categories', $tables); + } + + /** + * {@inheritDoc} + */ + public function execute(): bool + { + $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); + + $result = false; + + if (!empty($inserts)) { + $result = 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') + ); + } + + if ($result !== false) { + Db::$db->drop_table('{db_prefix}collapsed_categories'); + } + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1005.php b/Sources/Maintenance/Migration/v2_1/Migration1005.php new file mode 100644 index 0000000000..4a1d5b5da3 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1005.php @@ -0,0 +1,78 @@ +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)) + { + Db::$db->query('', ' + UPDATE {db_prefix}boards + SET name = {string:name}, description = {string:description} + WHERE id = {int:id}', + [ + 'id' => $row['id'], + 'name' => Utils::htmlspecialchars(strip_tags(BBCodeParser::load()->unparse($row['name']))), + 'description' => Utils::htmlspecialchars(strip_tags(BBCodeParser::load()->unparse($row['description']))), + ]); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } + Db::$db->free_result($request); + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1006.php b/Sources/Maintenance/Migration/v2_1/Migration1006.php new file mode 100644 index 0000000000..84bd644f1a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1006.php @@ -0,0 +1,261 @@ +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'], 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'])) + { + $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'])) + 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); + + $start += 100; + Maintenance::setCurrentStart($start); + } + + Config::updateModSettings(['attachments_21_done' => 1]); + return true; + } + + 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 = 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'); + } + + 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; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1007.php b/Sources/Maintenance/Migration/v2_1/Migration1007.php new file mode 100644 index 0000000000..1a8596060f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1007.php @@ -0,0 +1,89 @@ + 0 OR height > 0) + AND POSITION({literal:image} IN mime_type) IS NULL + */ + + $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, + ) + ); + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1008.php b/Sources/Maintenance/Migration/v2_1/Migration1008.php new file mode 100644 index 0000000000..96b8d9e896 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1008.php @@ -0,0 +1,67 @@ + 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; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/Migration/v2_1/Migration1009.php b/Sources/Maintenance/Migration/v2_1/Migration1009.php new file mode 100644 index 0000000000..e815143c7a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Migration1009.php @@ -0,0 +1,77 @@ +list_columns('{db_prefix}log_group_requests'); + $existing_indexes = Db::$db->list_indexes('{db_prefix}log_group_requests'); + + $logGroupRequestsTable = new LogGroupRequests(); + + foreach ($logGroupRequestsTable->columns as $column) { + // Column exists, don't need to do this. + if (in_array($column->name, $this->newColumns) && in_array($column->name, $existing_columns)) { + continue; + } + + $column->add('{db_prefix}log_group_requests'); + } + + Db::$db->remove_index('{db_prefix}log_group_requests', 'id_member'); + + foreach ($logGroupRequestsTable->indexes as $idx) { + // Column exists, don't need to do this. + if ($idx->name =='idx_id_member' && in_array($idx->name, $existing_indexes)) { + continue; + } + + $idx->add('{db_prefix}log_group_requests'); + } + + return true; + } +} + +?> \ No newline at end of file diff --git a/Sources/Maintenance/MigrationBase.php b/Sources/Maintenance/MigrationBase.php index dac8e39316..faab8520e0 100644 --- a/Sources/Maintenance/MigrationBase.php +++ b/Sources/Maintenance/MigrationBase.php @@ -60,7 +60,9 @@ public function execute(): bool */ protected function handleTimeout(?int $start = 0): void { - Maintenance::setCurrentStart($start); + if ($start !== null) { + Maintenance::setCurrentStart($start); + } Maintenance::$tool->checkAndHandleTimeout(); }