Skip to content

[3.0] Adds migration and cleanup steps for upgrading from old versions#9120

Draft
Sesquipedalian wants to merge 7 commits intoSimpleMachines:release-3.0from
Sesquipedalian:3.0/upgrade_old_versions
Draft

[3.0] Adds migration and cleanup steps for upgrading from old versions#9120
Sesquipedalian wants to merge 7 commits intoSimpleMachines:release-3.0from
Sesquipedalian:3.0/upgrade_old_versions

Conversation

@Sesquipedalian
Copy link
Member

I've had this sitting around nearly completed for months now, so I thought I'd take a few hours to finish it off and submit it.

This PR adds all the necessary table schemas, migration steps, and cleanup steps required to allow the upgrader to upgrade any version of SMF or even YaBB SE.

@jdarwood007, I know that we had previously discussed writing converters for old SMF versions rather than upgrader steps. However, when I sat down to do it, writing upgrader steps turned out to be much easier, because I could base them on the old upgrade scripts rather than trying to come up with entirely new logic. And since everything in the new SMF\Maintenance paradigm is so clearly defined and reliable, I don't think these new upgrader steps are going to be a burden to maintain the way the old upgrade scripts were.

@sbulen, is there any chance you could fire up that marvellous test suite of yours to make sure that these all work? Overall, I expect that it go well, but there are doubtless some copy-paste errors and typos hiding in there someplace.

@Sesquipedalian Sesquipedalian added this to the 3.0 Alpha 5 milestone Feb 22, 2026
@Sesquipedalian Sesquipedalian changed the title Adds migration and cleanup steps for upgrading from old versions [3.0] Adds migration and cleanup steps for upgrading from old versions Feb 22, 2026
@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from a2e1ec0 to ff9e7b5 Compare February 22, 2026 05:48
@sbulen
Copy link
Contributor

sbulen commented Feb 26, 2026

First attempt at running an upgrade with this PR tonight. First test was a 2.1 to 3.0 upgrade.

I had to do a composer update to get this to work. I actually wonder if the /vendor contents & composer lock should have been in the commits??? Not sure what the proper protocol should be. I was able to get past that by committing the update to composer.lock locally in my test branch for this PR.

Next problem is that the 'Continue' buttons are all associated with 3 second countdowns. I don't think there should be any countdown at all... That wasn't even enough time to type in my credentials. And it shouldn't continue until I say so...

I got past that by updating the upgrade template countdown to 30. And reading and typing quickly.

Next problem is that it appears to have automatically started trying to do a postgresql upgrade on my mysql database. Screenshot below. I wasn't able to get past this.

Not sure why it's attempting pg, I didn't even have pg started. And the 2.1 style Settings.php file specifies $db_type = 'mysql';

Note that with this screen, it's still doing an auto-continue every 3 seconds:
image

Mysql 8.4.4
PHP 8.4.5

@Sesquipedalian
Copy link
Member Author

Well, that's a bunch of weird surprises. I didn't touch any of those things in this PR. But I guess we'll need to fix them so you can test.

@Sesquipedalian
Copy link
Member Author

I had to do a composer update to get this to work. I actually wonder if the /vendor contents & composer lock should have been in the commits??? Not sure what the proper protocol should be. I was able to get past that by committing the update to composer.lock locally in my test branch for this PR.

I'm not sure why you had to do that. There are no differences between this branch and the release-3.0 branch for composer.lock, nor any for composer.json.

Next problem is that the 'Continue' buttons are all associated with 3 second countdowns. I don't think there should be any countdown at all... That wasn't even enough time to type in my credentials. And it shouldn't continue until I say so...

I got past that by updating the upgrade template countdown to 30. And reading and typing quickly.

I could have sworn that I removed that in an earlier PR.

Next problem is that it appears to have automatically started trying to do a postgresql upgrade on my mysql database. Screenshot below. I wasn't able to get past this.

Not sure why it's attempting pg, I didn't even have pg started. And the 2.1 style Settings.php file specifies $db_type = 'mysql';

That's extremely weird. More investigation is needed before I can even begin to guess why you saw that.

@Sesquipedalian
Copy link
Member Author

Next problem is that the 'Continue' buttons are all associated with 3 second countdowns. I don't think there should be any countdown at all... That wasn't even enough time to type in my credentials. And it shouldn't continue until I say so...

I got past that by updating the upgrade template countdown to 30. And reading and typing quickly.

I believe the latest commit has fixed that one. It appears to have been introduced in #9090.

@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Feb 26, 2026

Next problem is that it appears to have automatically started trying to do a postgresql upgrade on my mysql database. Screenshot below. I wasn't able to get past this.

Not sure why it's attempting pg, I didn't even have pg started. And the 2.1 style Settings.php file specifies $db_type = 'mysql';

Note that with this screen, it's still doing an auto-continue every 3 seconds:

Oh, now I see. The screenshot doesn't indicate that the upgrader thought your database was PostgreSQL. It is just showing that the upgrader was walking through some of the early migration substeps that apply only to PostgreSQL (and skipping them because they weren't applicable) when it encountered an error with the message "TypeError: NetworkError when attempting to fetch resource."

The question is, why did it encounter that error? Based on a quick web search, it indicates a CORS problem while making an HTTP request, which is rather odd in this context.

To help us figure where the heck that error is coming from, please check:

  1. Your browser's console log to see what it might reveal about this error.
  2. The contents of the ./logs/install.log file that SMF 3.0 creates to log installation progress.
  3. Your server's logs.

@sbulen
Copy link
Contributor

sbulen commented Feb 26, 2026

Good news: I refreshed the code from the latest this morning, & the 2.1 => 3.0 upgrade went well!

I found that the composer.lock was simply because I'd run the composer update & there is a new version of fixer available.

Side note: I haven't had a successful 3.0 installer run in a very long time (I'd logged several issues early 2025). I still cannot get it to run, with or without this PR. Logged #9131

I just attempted a 2.0 (latin1) => 3.0 upgrade & got the following error. All files are there, I believe, & I double-checked the files used in the file check, and the versions look good. Haven't dug much deeper; I see the code looks for some classes also, it might be failing there.

A 2.0 (utf8) => 3.0 upgrade attempt has the same result.

image

@sbulen
Copy link
Contributor

sbulen commented Feb 26, 2026

I attempted a 1.1 => 3.0 upgrade & got a WSOD with the following:

Warning: Undefined array key "custom_avatar_url" in D:\wamp64\www\van1121\Sources\QueryString.php on line 778

Fatal error: Uncaught TypeError: strtr(): Argument # 1 ($string) must be of type string, null given in D:\wamp64\www\van1121\Sources\QueryString.php:778 Stack trace: #0 D:\wamp64\www\van1121\Sources\QueryString.php(778): strtr(NULL, Array) # 1 D:\wamp64\www\van1121\Sources\QueryString.php(295): SMF\QueryString::fixUrl() # 2 D:\wamp64\www\van1121\Sources\Maintenance\Tools\Upgrade.php(549): SMF\QueryString::cleanRequest() # 3 D:\wamp64\www\van1121\Sources\Maintenance\Maintenance.php(255): SMF\Maintenance\Tools\Upgrade->__construct() # 4 D:\wamp64\www\van1121\upgrade.php(24): SMF\Maintenance\Maintenance->execute(2) # 5 {main} thrown in D:\wamp64\www\van1121\Sources\QueryString.php on line 778

Pretty sure custom avatars didn't exist before 2.0?

@sbulen
Copy link
Contributor

sbulen commented Feb 26, 2026

I attempted a 1.0 => 3.0 upgrade & got a different WSOD:

Fatal error: Cannot redeclare class SMF\Maintenance\Migration\v1_0\BoardsAndCategories (previously declared in D:\wamp64\www\van1023\Sources\Maintenance\Migration\v1_0\BoardsAndCategories.php:22) in D:\wamp64\www\van1023\Sources\Maintenance\Migration\v1_0\AnnouncementPermissions.php on line 22

@sbulen
Copy link
Contributor

sbulen commented Feb 26, 2026

yabbse:

Fatal error: Uncaught TypeError: is_dir(): Argument # 1 ($filename) must be of type string, false given in D:\wamp64\www\yabbse\Sources\Config.php:1009 Stack trace: # 0 D:\wamp64\www\yabbse\Sources\Config.php(1009): is_dir(false) # 1 D:\wamp64\www\yabbse\index.php(127): SMF\Config::set(Array) # 2 D:\wamp64\www\yabbse\index.php(140): {closure:D:\wamp64\www\yabbse\index.php:102}() # 3 D:\wamp64\www\yabbse\upgrade.php(20): require_once('D:\wamp64\www\y...') # 4 {main} thrown in D:\wamp64\www\yabbse\Sources\Config.php on line 1009

It's been a while since I've run these, always possible I'm grappling with environment issues.

@sbulen
Copy link
Contributor

sbulen commented Feb 27, 2026

I took a peek at the 2.0 issue. First time seeing this code, so take this with a grain of salt...

It finds all the files OK, Upgrade.php line 704.

When it starts looking for the 2.0 classes is where there is an issue, Upgrade.php, line 718. It finds & loads the first one fine (SMF\Maintenance\Migration\v2_0\PostgreSQLFunctions), and loadClass() is properly returning true. So far, so good...

But for some reason it still throws the exception on line 719. I believe it's falling back thru a closure & losing the "true" there.

So, well hidden under the covers, it is throwing the error: Exception->__construct($message = 'SMF\Maintenance\Migration\v2_0\PostgreSQLFunctions does not exist') D:\wamp64\www\van2021\Sources\Maintenance\Tools\Upgrade.php:719

When in fact it was loaded OK.

@sbulen
Copy link
Contributor

sbulen commented Feb 27, 2026

Note that loadClass actually does an include, which in turn, recursively includes MigrationBase & SubStepInterface immediately after PostgreSQLFunctions. It's possible there's an issue with one of those? Topic me, all appear to load ok.

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from 356c8c9 to ddf782e Compare February 28, 2026 19:16
@Sesquipedalian
Copy link
Member Author

I took a peek at the 2.0 issue. First time seeing this code, so take this with a grain of salt...

It finds all the files OK, Upgrade.php line 704.

When it starts looking for the 2.0 classes is where there is an issue, Upgrade.php, line 718. It finds & loads the first one fine (SMF\Maintenance\Migration\v2_0\PostgreSQLFunctions), and loadClass() is properly returning true. So far, so good...

But for some reason it still throws the exception on line 719. I believe it's falling back thru a closure & losing the "true" there.

So, well hidden under the covers, it is throwing the error: Exception->__construct($message = 'SMF\Maintenance\Migration\v2_0\PostgreSQLFunctions does not exist') D:\wamp64\www\van2021\Sources\Maintenance\Tools\Upgrade.php:719

When in fact it was loaded OK.

Note that loadClass actually does an include, which in turn, recursively includes MigrationBase & SubStepInterface immediately after PostgreSQLFunctions. It's possible there's an issue with one of those? Topic me, all appear to load ok.

Just trying to clarify here. When you write "loadClass", do you mean "class_exists"?

@sbulen
Copy link
Contributor

sbulen commented Feb 28, 2026

class_exists by default forces an autoload, which calls Composer's loadClass as well as many layers of other stuff... A fractal recursive maze of OOPsie.

check_exists loads everything. If that's not desired, I think you can pass false as 2nd param.

I'm not sure why that's failing though. Seems to be loading fine - including the layers of base classes & interfaces. Makes no sense.

@sbulen
Copy link
Contributor

sbulen commented Feb 28, 2026

I think I found my new forum signature: "A fractal recursive maze of OOPsie."

@sbulen
Copy link
Contributor

sbulen commented Feb 28, 2026

The last class loaded was SubStepInterface.

This might make sense if there were an issue with SubStepInterface, but I don't see one.

@Sesquipedalian
Copy link
Member Author

Gotcha. Hm, that's still odd. I don't have any ideas about it yet. I'll have to try again to see if I can reproduce the error locally.

@sbulen
Copy link
Contributor

sbulen commented Mar 1, 2026

OK, some progress... My version of Composer was a few months old, 2.8.8. Latest version is 2.9.5, and the error was clearly deep in the heart of Composer, so...

I did a composer self-update to 2.9.5, and now I'm getting further... But the error now is identical to the 1.1 error. And yes, I refreshed to get the most recent commits:

Warning: Undefined array key "custom_avatar_url" in D:\wamp64\www\van2021\Sources\QueryString.php on line 778

Fatal error: Uncaught TypeError: strtr(): Argument # 1 ($string) must be of type string, null given in D:\wamp64\www\van2021\Sources\QueryString.php:778 Stack trace: # 0 D:\wamp64\www\van2021\Sources\QueryString.php(778): strtr(NULL, Array) # 1 D:\wamp64\www\van2021\Sources\QueryString.php(295): SMF\QueryString::fixUrl() # 2 D:\wamp64\www\van2021\Sources\Maintenance\Tools\Upgrade.php(549): SMF\QueryString::cleanRequest() # 3 D:\wamp64\www\van2021\Sources\Maintenance\Maintenance.php(255): SMF\Maintenance\Tools\Upgrade->__construct() # 4 D:\wamp64\www\van2021\upgrade.php(24): SMF\Maintenance\Maintenance->execute(2) # 5 {main} thrown in D:\wamp64\www\van2021\Sources\QueryString.php on line 778

@sbulen
Copy link
Contributor

sbulen commented Mar 1, 2026

Odd the 2.1 => 3.0 upgrade was not impacted, but the 2.0 => 3.0 upgrade was...

@Sesquipedalian
Copy link
Member Author

custom_avatar_url

That should be an easy fix. I'll push a commit for that later tonight.

@Sesquipedalian
Copy link
Member Author

Does that latest commit allow installation to proceed, @sbulen?

@sbulen
Copy link
Contributor

sbulen commented Mar 4, 2026

Sorry, wasn't sure you wanted a retest yet.

Reran 2.1 => 3.0 - OK
Reran 2.0 (latin1) =>3.0 - Issue below.
Reran 2.0 (utf8) =>3.0 - Issue below.

Latin1:

Fatal error: Uncaught TypeError: SMF\Config::safeFileWrite(): Argument # 4 ($mtime) must be of type ?int, float given, called in D:\wamp64\www\van2021\Sources\Maintenance\Tools\ToolsBase.php on line 590 and defined in D:\wamp64\www\van2021\Sources\Config.php:2397 Stack trace: # 0 D:\wamp64\www\van2021\Sources\Maintenance\Tools\ToolsBase.php(590): SMF\Config::safeFileWrite('D:\wamp64\www\v...', '<?php\n\n\n\n\n$main...', NULL, 1772654716.5724) # 1 D:\wamp64\www\van2021\Sources\Maintenance\Tools\Upgrade.php(1141): SMF\Maintenance\Tools\ToolsBase->updateSettingsFile(Array, false, true) # 2 D:\wamp64\www\van2021\Sources\Maintenance\Maintenance.php(294): SMF\Maintenance\Tools\Upgrade->upgradeOptions() # 3 D:\wamp64\www\van2021\upgrade.php(24): SMF\Maintenance\Maintenance->execute(2) # 4 {main} thrown in D:\wamp64\www\van2021\Sources\Config.php on line 2397

utf8:

Fatal error: Uncaught TypeError: Cannot assign string to property SMF\Config::$db_port of type int in D:\wamp64\www\van20u8\Sources\Maintenance\Tools\Upgrade.php:1122 Stack trace: # 0 D:\wamp64\www\van20u8\Sources\Maintenance\Maintenance.php(294): SMF\Maintenance\Tools\Upgrade->upgradeOptions() # 1 D:\wamp64\www\van20u8\upgrade.php(24): SMF\Maintenance\Maintenance->execute(2) # 2 {main} thrown in D:\wamp64\www\van20u8\Sources\Maintenance\Tools\Upgrade.php on line 1122

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch 2 times, most recently from 69a55d1 to d92af01 Compare March 6, 2026 18:26
@Sesquipedalian
Copy link
Member Author

I've now pushed up fixes for all issues reported so far. When you are able, @sbulen, please check out a new copy of this brach and try again.

@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Mar 13, 2026

Timer looks accurate, but formatting lost.

Should be fixed in latest commits.

2.0 latin1 => 3.0 - critical error, no specific message (I suspect due to debug off on this run), screenshot below

Can you try again with debug enabled so that we can see where the problem lies?

2.0 utf8 => 3.0 - critical error, cannot use [] for reading, screenshot below

Should be fixed in latest commits.

1.1 => 3.0 - same weird issue, screenshot & log below

Should be fixed in latest commits.

@sbulen
Copy link
Contributor

sbulen commented Mar 13, 2026

Note I see this in the PHP log... Should have looked at this earlier...

This includes errors from all yesterday's tests...

[12-Mar-2026 23:16:22 UTC] PHP Warning: Undefined array key 72 in D:\wamp64\www\van2130\Sources\Maintenance\Utf8ConverterStep.php on line 746
[12-Mar-2026 23:16:22 UTC] PHP Warning: Attempt to read property "name" on null in D:\wamp64\www\van2130\Sources\Maintenance\Utf8ConverterStep.php on line 746
[12-Mar-2026 23:22:17 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[12-Mar-2026 23:22:17 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[12-Mar-2026 23:24:34 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[12-Mar-2026 23:24:34 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[12-Mar-2026 23:29:19 UTC] PHP Warning: Undefined variable $to_drop in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68
[12-Mar-2026 23:29:19 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68
[12-Mar-2026 23:32:00 UTC] PHP Warning: Undefined variable $to_drop in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68
[12-Mar-2026 23:32:00 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68

@sbulen
Copy link
Contributor

sbulen commented Mar 13, 2026

OK today's tests!

Another timer nit... Timer looks better, but only increments after major steps are completed. This in the past was an indicator it was actually running, even with debug off. In fact, it said so on the screen... "The time elapsed increments from the server to show progress is being made!" That was helpful on large forum upgrades.

I'm asking for debug on on all these, since that seems to provide better info.

2.1:

image

*** Nothing in the php/mysql/apache logs...

This is the bottom of the upgrader log:

...
+++ Add any missing functions (PostgreSQL)... skipped.
+++ Converting MySQL tables to the InnoDB engine and dynamic rows... done.
+++ Language Upgrade... done.
+++ Setting default value for log_errors.session column... done.
+++ Adding support for edit history... done.
+++ Adding version information to posts and personal messages... done.
+++ Adding SMF version information to log_packages... done.
+++ Adding support for recurring events...

2.0 latin1:

image

PHP log:
[13-Mar-2026 17:12:58 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[13-Mar-2026 17:12:58 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387

2.0 utf8:

image

PHP log:
[13-Mar-2026 17:15:07 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387
[13-Mar-2026 17:15:07 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387

1.1:

image

PHP log:
[13-Mar-2026 17:16:35 UTC] PHP Warning: Undefined variable $to_drop in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68
[13-Mar-2026 17:16:35 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from b1f4238 to d9dceb2 Compare March 17, 2026 19:45
@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Mar 17, 2026

2.0 latin1:

image PHP log: [13-Mar-2026 17:12:58 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387 [13-Mar-2026 17:12:58 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van2021\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387

2.0 utf8:

image PHP log: [13-Mar-2026 17:15:07 UTC] PHP Warning: Undefined variable $name_changes in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387 [13-Mar-2026 17:15:07 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van20u8\Sources\Maintenance\Migration\v2_0\RenameColumns.php on line 387

1.1:

image PHP log: [13-Mar-2026 17:16:35 UTC] PHP Warning: Undefined variable $to_drop in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68 [13-Mar-2026 17:16:35 UTC] PHP Warning: foreach() argument must be of type array|object, null given in D:\wamp64\www\van1121\Sources\Maintenance\Migration\v1_1\RemoveIndexes.php on line 68

These three should be fixed in the latest commits.

@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Mar 17, 2026

2.1:

image *** Nothing in the php/mysql/apache logs...

This is the bottom of the upgrader log:

...
+++ Add any missing functions (PostgreSQL)... skipped.
+++ Converting MySQL tables to the InnoDB engine and dynamic rows... done.
+++ Language Upgrade... done.
+++ Setting default value for log_errors.session column... done.
+++ Adding support for edit history... done.
+++ Adding version information to posts and personal messages... done.
+++ Adding SMF version information to log_packages... done.
+++ Adding support for recurring events...

Please temporarily replace the contents of ./Sources/Maintenance/Migrations/v3_0/RecurringEvents.php with the following. Like before, it will dump a mess into your install.log that should help me figure out the problem.

<?php

/**
 * Simple Machines Forum (SMF)
 *
 * @package SMF
 * @author Simple Machines https://www.simplemachines.org
 * @copyright 2026 Simple Machines and individual contributors
 * @license https://www.simplemachines.org/about/smf/license.php BSD
 *
 * @version 3.0 Alpha 4
 */

declare(strict_types=1);

namespace SMF\Maintenance\Migration\v3_0;

use SMF\Db\DatabaseApi as Db;
use SMF\Db\Schema;
use SMF\Maintenance\Migration\MigrationBase;

class RecurringEvents extends MigrationBase
{
	/*******************
	 * Public properties
	 *******************/

	/**
	 *
	 */
	public string $name = 'Adding support for recurring events';

	/****************
	 * Public methods
	 ****************/

	/**
	 *
	 */
	public function execute(): bool
	{
		Maintenance::$tool->logProgress('Getting calendar table definition', true);
		$table = new Schema\v3_0\Calendar();
		Maintenance::$tool->logProgress('Getting current calendar table structure', true);
		$existing_structure = $table->getCurrentStructure();

		Maintenance::$tool->logProgress('Adding missing columns', true);
		foreach ($table->columns as $column) {
			if (!isset($existing_structure['columns'][$column->name])) {
				Maintenance::$tool->logProgress($column->name, true);
				$table->addColumn($column);
			}

			$this->handleTimeout();
		}

		if (Db::$db->title === MYSQL_TITLE) {
			Maintenance::$tool->logProgress('Rearranging columns', true);
			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',
				[],
			);

		}

		Maintenance::$tool->logProgress('Building updates', true);
		$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)) {
			try {
			$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',
			];
			} catch (\Throwable $e) {
				Maintenance::$tool->logProgress($e->getMessage(), true);
				throw $e;
			}
		}
		Db::$db->free_result($request);

		foreach ($updates as $id_event => $changes) {
			Maintenance::$tool->logProgress('Submitting update: ' . \SMF\Utils::normalizeSpaces(\SMF\Config::varExport($changes), true, true, ['nobreaks' => true]), true);
			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,
			);
		}

		Maintenance::$tool->logProgress('Removing end_time column', true);
		Db::$db->remove_column('{db_prefix}calendar', 'end_time');

		Maintenance::$tool->logProgress('returning', true);
		return true;
	}
}

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch 4 times, most recently from a1f0fab to ac9f699 Compare March 19, 2026 06:38
@sbulen
Copy link
Contributor

sbulen commented Mar 19, 2026

Today's results. The 2.1 execution included the changes requested above.

I think the error reporting in the 2.1 execution should be permanent & universal. Errors are visible in the browser, and also logged in the upgrader log. That's what's needed. The output from the other executions is useless.

2.1:

image

system logs:
-- Nothing

Upgrader log, bottom:

+++ Add any missing functions (PostgreSQL)... skipped.
+++ Converting MySQL tables to the InnoDB engine and dynamic rows... done.
+++ Language Upgrade... done.
+++ Setting default value for log_errors.session column... done.
+++ Adding support for edit history... done.
+++ Adding version information to posts and personal messages... done.
+++ Adding SMF version information to log_packages... done.
+++ Adding support for recurring events... failed with error: "Class "SMF\Maintenance\Migration\v3_0\Maintenance" not found"

2.0 latin1:

image

system logs:
-- Nothing

Upgrader log, bottom:

2026-03-19T15:14:12+00:00
Step 1: Login
Step 2: Upgrade Options
Saving the following settings in the settings table: enable_sm_stats and force_ssl... done.
Step 4: Migrations
+++ Updating PostgreSQL functions... skipped.
+++ Changing column names...

2.1 utf8:

image

system logs:
-- Nothing

Upgrader log, complete:

2026-03-19T15:17:37+00:00
Step 1: Login
Step 2: Upgrade Options
Saving the following settings in the settings table: enable_sm_stats and force_ssl... done.
Saving the following settings in Settings.php: db_server and db_port... done.
Step 4: Migrations
+++ Updating PostgreSQL functions... skipped.
+++ Changing column names... done.
+++ Adding search engine tracking... done.
+++ Adding new forum settings...

1.1:

image

system logs:
-- Nothing

Upgrader log, complete:

2026-03-19T15:21:26+00:00
Step 1: Login
Step 2: Upgrade Options
Saving the following settings in the settings table: enable_sm_stats... done.
Saving the following settings in Settings.php: image_proxy_secret... done.
Step 4: Migrations
+++ Removing obsolete table indexes... done.
+++ Reordering categories... done.
+++ Reordering boards... done.
+++ Updating data in smileys table... done.
+++ Reorganizing configuration settings... done.
+++ Cleaning up after old themes... skipped.
+++ Updating personal message functionality...

@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Mar 19, 2026

Today's results. The 2.1 execution included the changes requested above.

I think the error reporting in the 2.1 execution should be permanent & universal. Errors are visible in the browser, and also logged in the upgrader log. That's what's needed. The output from the other executions is useless.

2.1:

image system logs: -- Nothing

Upgrader log, bottom:

+++ Add any missing functions (PostgreSQL)... skipped.
+++ Converting MySQL tables to the InnoDB engine and dynamic rows... done.
+++ Language Upgrade... done.
+++ Setting default value for log_errors.session column... done.
+++ Adding support for edit history... done.
+++ Adding version information to posts and personal messages... done.
+++ Adding SMF version information to log_packages... done.
+++ Adding support for recurring events... failed with error: "Class "SMF\Maintenance\Migration\v3_0\Maintenance" not found"

Derp. That's because of a silly mistake on my part. Please try again with this version:

<?php

/**
 * Simple Machines Forum (SMF)
 *
 * @package SMF
 * @author Simple Machines https://www.simplemachines.org
 * @copyright 2026 Simple Machines and individual contributors
 * @license https://www.simplemachines.org/about/smf/license.php BSD
 *
 * @version 3.0 Alpha 4
 */

declare(strict_types=1);

namespace SMF\Maintenance\Migration\v3_0;

use SMF\Db\DatabaseApi as Db;
use SMF\Db\Schema;
use SMF\Maintenance\Migration\MigrationBase;

class RecurringEvents extends MigrationBase
{
	/*******************
	 * Public properties
	 *******************/

	/**
	 *
	 */
	public string $name = 'Adding support for recurring events';

	/****************
	 * Public methods
	 ****************/

	/**
	 *
	 */
	public function execute(): bool
	{
		\SMF\Maintenance\Maintenance::$tool->logProgress('Getting calendar table definition', true);
		$table = new Schema\v3_0\Calendar();
		\SMF\Maintenance\Maintenance::$tool->logProgress('Getting current calendar table structure', true);
		$existing_structure = $table->getCurrentStructure();

		\SMF\Maintenance\Maintenance::$tool->logProgress('Adding missing columns', true);
		foreach ($table->columns as $column) {
			if (!isset($existing_structure['columns'][$column->name])) {
				\SMF\Maintenance\Maintenance::$tool->logProgress($column->name, true);
				$table->addColumn($column);
			}

			$this->handleTimeout();
		}

		if (Db::$db->title === MYSQL_TITLE) {
			\SMF\Maintenance\Maintenance::$tool->logProgress('Rearranging columns', true);
			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',
				[],
			);

		}

		\SMF\Maintenance\Maintenance::$tool->logProgress('Building updates', true);
		$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)) {
			try {
			$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',
			];
			} catch (\Throwable $e) {
				\SMF\Maintenance\Maintenance::$tool->logProgress($e->getMessage(), true);
				throw $e;
			}
		}
		Db::$db->free_result($request);

		foreach ($updates as $id_event => $changes) {
			\SMF\Maintenance\Maintenance::$tool->logProgress('Submitting update: ' . \SMF\Utils::normalizeSpaces(\SMF\Config::varExport($changes), true, true, ['nobreaks' => true]), true);
			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,
			);
		}

		\SMF\Maintenance\Maintenance::$tool->logProgress('Removing end_time column', true);
		Db::$db->remove_column('{db_prefix}calendar', 'end_time');

		\SMF\Maintenance\Maintenance::$tool->logProgress('returning', true);
		return true;
	}
}

@Sesquipedalian
Copy link
Member Author

Also, let's focus for now on getting the 2.1 → 3.0 upgrade all tested and working. Once that has all been fixed, we'll finally be able to move on to testing the original purpose of this PR, which was to support upgrading from older versions.

@sbulen
Copy link
Contributor

sbulen commented Mar 19, 2026

Also, let's focus for now on getting the 2.1 → 3.0 upgrade all tested and working. Once that has all been fixed, we'll finally be able to move on to testing the original purpose of this PR, which was to support upgrading from older versions.

Ok. Note there are layers of dependencies.

It's kind of hard to test anything atm with #9146 & #9147 open, IMO. After so many huge changes, it feels like we need a tweakfest, to get some ironing done... I dont think it's a lot of work, but its important to get the basics working again. Without doing so, we dont know if issues encountered in testing were introduced by the upgrade or not.

Then we can spend some time on 2.1=> 3.0 without this PR. Not too much, because those 2.1 => 3.0 tests need to be repeated with this PR.

Then return to this PR.

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from ac9f699 to 9e67b84 Compare March 20, 2026 00:03
@Sesquipedalian
Copy link
Member Author

Sesquipedalian commented Mar 20, 2026

It's kind of hard to test anything atm with #9146 & #9147 open, IMO.

Latest commits include the fixes for those issues.

@Sesquipedalian
Copy link
Member Author

After so many huge changes, it feels like we need a tweakfest, to get some ironing done... I dont think it's a lot of work, but its important to get the basics working again. Without doing so, we dont know if issues encountered in testing were introduced by the upgrade or not.

Then we can spend some time on 2.1=> 3.0 without this PR. Not too much, because those 2.1 => 3.0 tests need to be repeated with this PR.

Then return to this PR.

Hm. Yeah, I'm inclined to agree. I will move all the various bug fixes that have been added to this PR into their own, separate PR that we can test on their own.

@jdarwood007
Copy link
Member

Since #9162 needs to be resolved first, I won't review anything. But getting progress on 2,0, 1.x and yabbse is great.

@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from abb9135 to 54ff979 Compare March 21, 2026 01:26
@Sesquipedalian Sesquipedalian marked this pull request as draft March 21, 2026 01:27
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
Signed-off-by: Jon Stovell <jonstovell@gmail.com>
@Sesquipedalian Sesquipedalian force-pushed the 3.0/upgrade_old_versions branch from 54ff979 to b17bdca Compare March 21, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants