Skip to content

Add PHP 5.2 WebAssembly builds and runtime support#3501

Merged
adamziel merged 17 commits intotrunkfrom
php-5.2-wasm
Apr 16, 2026
Merged

Add PHP 5.2 WebAssembly builds and runtime support#3501
adamziel merged 17 commits intotrunkfrom
php-5.2-wasm

Conversation

@JanJakes
Copy link
Copy Markdown
Member

@JanJakes JanJakes commented Apr 16, 2026

Summary

  • Introduce LegacyPHPVersions / AllPHPVersions type system and register PHP 5.2.17 in the version registry
  • Add Dockerfile and C-source changes for PHP 5.2 compilation: autoconf 2.13, pre-generated parser/scanner downloads, flex wrapper, polyfill extension compat (ZEND_FE_END/PHP_FE_END), yytext duplicate-definition handling, mysqlnd gating, CLI getopt linking, php_cli_process_title version guard
  • Commit pre-built PHP 5.2 WASM binaries for all four targets (web/node × JSPI/Asyncify)
  • Wire PHP 5.2 into the web worker and CLI: legacy PHP ini pre-creation (disables ini_get_all, OPcache), single-instance mode for PROXYFS, correct SQLite plugin selection, extension gating, PHP 5.2-compatible mu-plugin stub
  • Support downloading non-minified legacy WordPress versions from wordpress.org (1.0–6.2)

Test plan

  • npx nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=5.2 succeeds
  • npx nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=5.2 succeeds
  • npx nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=5.2 succeeds
  • npx nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=5.2 succeeds
  • npx nx typecheck playground-website passes
  • npx nx run-many -t lint --projects=php-wasm-web,php-wasm-node,php-wasm-universal,playground-website passes

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds PHP 5.2.17 as a “legacy” WebAssembly runtime across web + node, and wires it through Playground (worker + CLI) to support booting older WordPress versions.

Changes:

  • Introduces legacy PHP version typing/registry, loader-module wiring, and pre-boot php.ini handling for PHP 5.2.
  • Adds new @php-wasm/*-5-2 build packages (web/node) plus runtime guards for unsupported extensions / multi-instance behavior.
  • Updates Playground boot flow for legacy WP/PHP constraints (WP zip sourcing, SQLite plugin selection, mu-plugin stubs, WP_DEBUG gating).

Reviewed changes

Copilot reviewed 50 out of 60 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tsconfig.base.json Adds TS path aliases for new @php-wasm/*-5-2 packages.
packages/playground/remote/src/lib/playground-worker-endpoint.ts Limits instances for legacy PHP and swaps mu-plugin stub for PHP 5.2.
packages/playground/remote/src/lib/playground-worker-endpoint-blueprints-v1.ts Downloads non-minified WP from wordpress.org; selects legacy SQLite; gates WP_DEBUG.
packages/playground/remote/src/lib/playground-mu-plugin/playground-includes/wp_http_fetch.php Replaces null-coalescing for PHP 5.2 compatibility.
packages/playground/remote/src/lib/playground-mu-plugin/0-playground.php Adds PHP/WP version guards and PHP-5.2-safe fallbacks.
packages/playground/remote/src/lib/playground-mu-plugin/0-playground-php52.php Adds PHP 5.2-compatible mu-plugin stub (named callbacks + dummy transport).
packages/playground/client/src/blueprints-v1-handler.ts Skips update-check prefetch for legacy WordPress versions.
packages/playground/cli/tests/run-cli.spec.ts Adjusts tests to avoid legacy WP versions in “supported” CLI boot test.
packages/playground/cli/src/run-cli.ts Accepts legacy PHP versions; gates WP_DEBUG defaults/extensions for legacy; adds loader logic tweaks.
packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts Passes phpVersion through V1 worker boot and request-handler creation.
packages/playground/cli/src/blueprints-v1/download.ts Allows selecting SQLite integration zip version (including php52 variant).
packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts Selects legacy SQLite integration zip for legacy PHP; passes phpVersion to worker boot.
packages/playground/blueprints/src/lib/v1/types.ts Expands Blueprint PHP version typing to include legacy PHP.
packages/playground/blueprints/src/lib/v1/compile.ts Uses AllPHPVersions for compilation-time version selection.
packages/playground/blueprints/src/lib/types.ts Updates RuntimeConfiguration to AllPHPVersion.
packages/playground/blueprints/public/blueprint-schema.json Adds LegacyPHPVersion/AllPHPVersion to schema and references them.
packages/php-wasm/web/src/lib/load-runtime.ts Supports legacy php.ini preRun and throws on intl with legacy PHP.
packages/php-wasm/web/src/lib/get-php-loader-module.ts Adds dynamic import for @php-wasm/web-5-2.
packages/php-wasm/web-builds/5-2/tsconfig.lib.json New TS config for @php-wasm/web-5-2 build.
packages/php-wasm/web-builds/5-2/tsconfig.json New TS project references for web 5.2 package.
packages/php-wasm/web-builds/5-2/src/index.ts Implements JSPI/Asyncify loader selection for web PHP 5.2.
packages/php-wasm/web-builds/5-2/project.json Nx targets for building/publishing web PHP 5.2 binaries.
packages/php-wasm/web-builds/5-2/package.json Defines new @php-wasm/web-5-2 package metadata/exports.
packages/php-wasm/web-builds/5-2/build.js esbuild bundling script for web PHP 5.2 package.
packages/php-wasm/universal/src/lib/supported-php-versions.ts Adds legacy/all version lists + type guard isLegacyPHPVersion.
packages/php-wasm/universal/src/lib/proxy-file-system.ts Skips PROXYFS mmap patch for legacy PHP instances due to parser bug.
packages/php-wasm/universal/src/lib/legacy-php-ini.ts Adds legacy PHP pre-boot php.ini + preRun writer.
packages/php-wasm/universal/src/lib/index.ts Re-exports legacy version helpers and legacy ini helpers.
packages/php-wasm/supported-php-versions.mjs Registers PHP 5.2.17 in version metadata list.
packages/php-wasm/node/src/lib/load-runtime.ts Supports legacy php.ini preRun and rejects extensions on legacy PHP.
packages/php-wasm/node/src/lib/get-php-loader-module.ts Adds dynamic import for @php-wasm/node-5-2.
packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts Refactors imports formatting (no functional change).
packages/php-wasm/node-builds/5-2/tsconfig.lib.json New TS config for @php-wasm/node-5-2 build.
packages/php-wasm/node-builds/5-2/tsconfig.json New TS project references for node 5.2 package.
packages/php-wasm/node-builds/5-2/src/index.ts Implements JSPI/Asyncify loader selection for node PHP 5.2; stubs extension paths.
packages/php-wasm/node-builds/5-2/project.json Nx targets for building/publishing node PHP 5.2 binaries.
packages/php-wasm/node-builds/5-2/package.json Defines new @php-wasm/node-5-2 package metadata/exports.
packages/php-wasm/node-builds/5-2/build.js esbuild bundling script for node PHP 5.2 package.
packages/php-wasm/compile/php/proc_open.h Adds include guard and PHP 5.x pipe type compatibility.
packages/php-wasm/compile/php/proc_open.c Adds PHP 5.x stub implementation for proc_* APIs.
packages/php-wasm/compile/php/php_wasm.c Adds PHP-version guards for headers/APIs and PHP 5.x compatibility fixes.
packages/php-wasm/compile/php/php5.2.patch Adds PHP 5.2 fixes (empty copy() crash; libxml buffer accessors).
packages/php-wasm/compile/php/php5.2-openssl-compat.patch Adds OpenSSL 1.1+ compatibility shims for PHP 5.2 openssl extension.
packages/php-wasm/compile/php/apply-mysqlnd-patch.sh Tolerates missing mysqlnd in older PHP versions; improves messaging/failure behavior.
packages/php-wasm/compile/php/Dockerfile Adds PHP 5.2-specific build steps (autoconf 2.13, parser sources, flags, feature gating).
packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c Disables custom memory storage for PHP < 7 with no-op init/shutdown.
packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.h Moves arginfo definitions out of header (compat).
packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c Adds PHP 5.2 compat for macros/TSRMLS and restores arginfo definitions.
packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.h Moves arginfo definitions out of header (compat).
packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c Adds PHP 5.2 compat for macros/TSRMLS and return handling.
Comments suppressed due to low confidence (1)

packages/php-wasm/node/src/lib/load-runtime.ts:1

  • Setting preRun: ... : undefined after spreading options.emscriptenOptions overwrites any caller-supplied preRun hook (even for non-legacy PHP), and for legacy PHP it replaces rather than appends to existing preRun steps. Prefer preserving existing preRun(s) and only adding createLegacyPhpIniPreRunStep() when legacy (e.g., normalize the existing preRun into an array, append the legacy step, and avoid writing preRun: undefined for non-legacy).
import {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts
Comment thread packages/php-wasm/universal/src/lib/proxy-file-system.ts
Comment thread packages/php-wasm/node-builds/5-2/src/index.ts Outdated
Comment thread packages/playground/cli/src/run-cli.ts
@JanJakes JanJakes force-pushed the php-5.2-wasm branch 3 times, most recently from 56514a4 to ac01d55 Compare April 16, 2026 12:30
Introduce LegacyPHPVersions and AllPHPVersions constants alongside
AllPHPVersion type to distinguish legacy PHP builds from supported
ones. Update Blueprint schema and types to accept legacy PHP versions.
Update Node.js and web runtime loaders to detect legacy PHP versions
and pre-create php.ini with disable_functions=ini_get_all before SAPI
startup (prevents WASM crashes). Gate extension loading (xdebug, intl,
redis, memcached) behind legacy checks — these are unavailable for
PHP 5.x.

Update Dockerfile and C sources for PHP 5.x compilation: conditional
libzip, SQLite, cURL, imagick, PCRE JIT, fiber-asm, and inline asm
handling.
Add pre-compiled PHP 5.2.17 WASM binaries for both Node.js and web
(asyncify and JSPI variants). Register the new packages in loader
modules and tsconfig path aliases.
The web worker's blueprints v1 endpoint only knew two ways to fetch
WordPress: a pre-built minified bundle (for versions in
MinifiedWordPressVersionsList) or a direct HTTP URL. That left bare
dotted versions like "4.9" or "1.5" with no path to boot, even though
wordpress.org hosts them.

Add a third branch that resolves any non-minified, non-URL version to
`https://wordpress.org/wordpress-<version>.zip`, routed through the
CORS proxy when configured. A regex guard rejects anything that isn't
a plain dotted numeric version before it reaches URL interpolation.

The WP 1.x releases wordpress.org serves are only keyed under their
full patch version (there is no `wordpress-1.5.zip`), so a
normalizeWordPressVersion helper rewrites the three known bare
<2.0 versions to their canonical patch release.

This unblocks booting any historical WordPress release from the web
worker and is a prerequisite for the upcoming legacy WP boot test
(which iterates versions 1.0 through 4.9).
Web worker: select pre-patched SQLite v2.2.22-php52 for legacy PHP,
force single-instance mode for legacy PHP (PROXYFS parser state bug),
disable WP_DEBUG for legacy WP (missing WP_DEBUG_DISPLAY).

CLI: pass phpVersion through boot chain, select correct SQLite version,
disable extensions for legacy PHP, add pre-built WordPress lookup.

Mu-plugins: add PHP 5.2-compatible stub (named functions instead of
closures), guard function_exists checks for WP < 3.0, use array()
syntax for PHP 5.3 compatibility, handle Requests class variations.

Skip update checks prefetch for legacy WordPress.
@@ -0,0 +1,76 @@
<?php
/**
* PHP 5.2-compatible minimal version of 0-playground.php.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove closures from 0-playground.php and use the same file everywhere? Or would that just complicate things?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude's opinion:

The two files serve quite different environments, not just different PHP syntax:

0-playground.php (PHP 5.3+, modern WP) has login message, networking-disabled admin UI, link rewriting, URL reporting to parent frame, pattern picker disable, client-side media disable, Fetch or Dummy HTTP transport, uses \WpOrg\Requests\Requests (namespaced, PHP 5.3+), uses str_ends_with (PHP 8.0+), and 9 closures.

0-playground-php52.php (PHP 5.2, old WP) has Dummy HTTP transport only (the fetch transport files use PHP 5.3+ syntax), WP Cron disable, uses class_exists('Requests') (no namespace), and uses substr instead of str_ends_with.

Removing closures is the easy part — just convert to named functions. The hard part is that most features in the main file don't apply to PHP 5.2 + old WordPress:

  • get_current_user_id() may not exist in WP 2.x
  • The networking UI assumes modern admin screens
  • str_ends_with is PHP 8.0+
  • The Requests namespace class is PHP 5.3+
  • Fetch transport files themselves won't parse on PHP 5.2

Unifying would mean wrapping most of the file in version_compare guards, making it harder to read than having two separate files. Keeping them separate seems cleaner given how different the two environments are.

// polyfills added)
// - Everything else: whatever the caller requested
const isLegacyPhp = isLegacyPHPVersion(phpVersion);
const effectiveSqliteVersion = isLegacyPhp
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we support PHP 5.2 in that plugin? Or have a branch where Rector automatically downgrades the codebase to 5.2?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamziel This is now in #3490. It appears that the split into two PRs was imperfect. Rector has no rules to downgrade below PHP 7.2, so the agent wrote its own logic using PHP parsers and some more patches. We could move it to the SQLite repo later, I guess that would make sense.

}

const parsedSiteUrl = new URL(siteUrl);
// Legacy PHP has a parser state bug that corrupts large include
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it legacy PHP, or is it us? Those sites worked back then, perhaps proxyfs is flawed? I think we can merge this PR as is, but wdyt about rephrasing this comment and getting some long-running agent to identify the root cause?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 1add040.

Copy link
Copy Markdown
Member Author

@JanJakes JanJakes Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this was incorrectly distilled from the previous 5.6 attempts. The correct fix is already done. Improved in 5f425d4 + 99c1b66.

Comment thread packages/php-wasm/compile/php/Dockerfile Outdated
Comment thread packages/php-wasm/compile/php/Dockerfile Outdated
Comment thread packages/php-wasm/compile/php/Dockerfile Outdated
Comment thread packages/php-wasm/node/src/lib/load-runtime.ts Outdated
Move all PHP 5.2-specific build logic into a dedicated Dockerfile.php52,
restoring the main Dockerfile to its trunk state (zero diff). This avoids
cluttering the main Dockerfile with version-specific conditionals.

The new file mirrors the main Dockerfile structure but with PHP 5.2
adjustments hardcoded: autoconf 2.13, different configure flags, no
OPcache/mysqlnd/imagick/fibers/phar, PHP5_FLAGS for emcc, etc.

build.js now selects the appropriate Dockerfile based on the PHP version.
Split the if/else into two independent guards so the extension-loading
block doesn't nest inside an else.
The multi-instance corruption on legacy PHP may be a PROXYFS bug rather
than a PHP parser issue — these sites worked on real servers originally.
Add a TODO to investigate the actual root cause.
Copy link
Copy Markdown
Collaborator

@adamziel adamziel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! My only real concern was the pointer cast thing and it appears like EMULATE_POINTER_CASTS solved that. I'm still concerned about the overhead part of it:

Use EMULATE_FUNCTION_POINTER_CASTS. When you build with -sEMULATE_FUNCTION_POINTER_CASTS, Emscripten emits code to emulate function pointer casts at runtime, adding extra arguments/dropping them/changing their type/adding or dropping a return type/etc. This can add significant runtime overhead, so it is not recommended, but is worth trying.

It would be interesting to measure that somehow and explore alternatives if anything is significantly slower. That's not a blocker here at all. Thank you @JanJakes!

@mho22
Copy link
Copy Markdown
Collaborator

mho22 commented Apr 16, 2026

Looks great! My only real concern was the pointer cast thing and it appears like EMULATE_POINTER_CASTS solved that. I'm still concerned about the overhead part of it:

@adamziel EMULATE_FUNCTION_POINTER_CASTS was removed from the PHP Dockerfile. So there is no potential overhead anymore.


if (loaderOptions.withIntl) {
emscriptenOptions = withIntl(phpVersion, emscriptenOptions);
if (isLegacy) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the same approach as for node/load-runtime.ts with :

	if (
		isLegacy &&
		(loaderOptions?.withIntl)
	) {
		throw new Error(
			`Extensions (intl) are not ` +
				`available for legacy PHP ${phpVersion}.`
		);
	}
	
	if (!isLegacy) {
	....

maybe?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 04c7b03.

Comment thread packages/playground/cli/src/run-cli.ts
Replace the duck-typing loop that searched all symbols for a
phpVersion property with the same [0] access pattern used by
every other function in proxy-file-system.ts.
Match the web version's parameter type. SupportedPHPVersion | string
was effectively just string, losing type safety. AllPHPVersion
enumerates all valid versions including legacy ones.
Replace the hardcoded '5.2' check with the isLegacyPHPVersion()
type guard so any future legacy version also gets the compatible
mu-plugin stub.
proc_open always returns FALSE in the WASM build, so no process
resources are ever created. The destructor exists only because
zend_register_list_destructors_ex requires a callback.
Explain how WP_Http prepends "WP_Http_" to filter entries,
why the class name is Wp_Http_Dummy, and why it intentionally
does not implement the Requests_Transport interface.
Explain how parseFloat extracts the major version from dotted
version strings and how non-numeric values like "nightly" or
"trunk" are handled via Number.isFinite.
The PROXYFS stale-fstat issue that caused parse errors on secondary
PHP instances is already handled by skipping the PROXYFS mmap patch
for legacy PHP (isLegacyPhpInstance check in proxyFileSystem). Without
mmap, PHP falls back to a read-based path that handles size mismatches
gracefully. The maxPhpInstances: 1 workaround was redundant.

See 60a57ee on legacy-wordpress-support-v2 for the original fix.
Explain the actual root cause: pre-mount MEMFS nodes retain stale
fstat sizes after PROXYFS shadows them, and PHP 5.x's mmap-based
zend_stream_fixup trusts that size. Without the mmap patch,
Emscripten returns ENOSYS and PHP uses a read-based fallback that
handles size mismatches gracefully.
Match the node version's pattern: early throw for legacy + guard
the extension setup behind !isLegacy, instead of nesting the
legacy check inside the extension check.
@adamziel
Copy link
Copy Markdown
Collaborator

The failing test looks like a flake, let's ship!

@adamziel adamziel merged commit bfd7d89 into trunk Apr 16, 2026
46 of 47 checks passed
@adamziel adamziel deleted the php-5.2-wasm branch April 16, 2026 16:13
JanJakes added a commit that referenced this pull request Apr 17, 2026
…3506)

## Summary

- Bumps the newly-introduced `@php-wasm/web-5-2` and
`@php-wasm/node-5-2` packages from `3.1.19` → `3.1.20` to match the
current workspace version (`lerna.json`). Without this they would have
been published below their own `@php-wasm/universal@3.1.20` dependency
and skipped by the next `lerna publish patch`.
- Adds `README.md` for both packages, mirroring the 8-5 style. The blurb
notes that 5.2 bundles no extensions (intl/Xdebug/Redis/Memcached
getters all throw), so the npmjs.com page isn't blank and consumers
aren't surprised.

Follow-up to #3501. Both packages are already live on npm at `3.1.20` —
this PR brings the source tree in sync so the weekly `lerna publish` CI
picks up cleanly from here.

## Test plan

- [x] `npm view @php-wasm/web-5-2 version` → `3.1.20`
- [x] `npm view @php-wasm/node-5-2 version` → `3.1.20`
- [x] `npm access list collaborators @php-wasm/web-5-2` matches
`@php-wasm/web-8-5`
- [x] Trusted Publisher configured on npmjs.com for both packages (repo
`WordPress/wordpress-playground`, workflow `publish-npm-packages.yml`,
environment `npm`)
@mho22
Copy link
Copy Markdown
Collaborator

mho22 commented Apr 20, 2026

It looks like the implementation of TSRMLS_CC breaks the PHP.wasm Node 8.5 compilation. I am working on a fix.

mho22 added a commit that referenced this pull request Apr 22, 2026
## Motivation for the change, related issues

[Add PHP 5.2 WebAssembly builds and runtime
support](#3501)
added `TSRMLS_CC` macros to `dns_polyfill` and `post_message_to_js` for
PHP 5.x compatibility, but these macros don't exist in PHP 7+. This
breaks recompilation of PHP.wasm for all PHP versions 7.0 through 8.5.

## Implementation details

Added empty `TSRMLS_CC` fallback defines to `dns_polyfill.c` and
`post_message_to_js.c`

## Testing Instructions (or ideally a Blueprint)

- [x] `nx run php-wasm-node:recompile-php:jspi`
- [x] `nx run php-wasm-node:recompile-php:asyncify`
- [x] `nx run php-wasm-web:recompile-php:jspi`
- [x] `nx run php-wasm-web:recompile-php:asyncify`
adamziel pushed a commit that referenced this pull request Apr 29, 2026
> **Stacked on #3501** (Add PHP 5.2 WebAssembly builds and runtime
support). Review that PR first.

## Summary

Boot legacy WordPress (1.0 – 6.2) in the browser, with SQLite as the
database.

<img width="743" height="586" alt="Screenshot 2026-04-15 at 13 30 53"
src="https://github.com/user-attachments/assets/1c42ebab-cfb8-4968-88e8-b8a628a42515"
/>

The Playground settings panel gains an **"Include older versions"**
checkbox that exposes every WordPress release from 1.0 through 6.9 (with
jazz release code names) and auto-locks the PHP version to whatever runs
it.

Replaces and closes #3469 (the PHP 5.6 version of the same feature).

## What's in this PR

Builds on the PHP 5.2 WASM runtime from #3501 and adds:

- **Offline PHP 7+ → 5.2 downgrader** (`scripts/php52-downgrader/`) — a
nikic/PhpParser pipeline of ~20 visitors that strips modern syntax so
arbitrary PHP 7-era files parse on PHP 5.2.
- **SQLite plugin patcher** (`scripts/patch-sqlite-for-php52.mjs`) —
produces a PHP 5.2-compatible build of the upstream SQLite integration
plugin.
- **Legacy WordPress boot flow** (`wordpress/src/boot.ts`,
`legacy-wp-fixes.ts`, `mysql-shims.ts`) — legacy-aware installer path,
PDO table fallback for WP < 3.5, ~3400 lines of targeted source patches
for WP 1.0 – 2.8 parser/SQL quirks, auth/nonce compat, `wp-db.php`
SQLite compatibility, and more.
- **WP 5.0–6.2 on PHP 7.4** — extends the legacy boot to cover the
mid-modern range where PHP 8.x is unreliable.
- **Site settings UI** — "Include older versions" checkbox + grouped
dropdown + automatic PHP locking: WP 1.0 – 4.9 → PHP 5.2, WP 5.0 – 6.2 →
PHP 7.4, WP 6.3+ → default.
- **Release code names** — WP version picker shows jazz-musician
shorthand (e.g. "6.9 (Gene)", "4.9 (Tipton)").
- **TinyMCE content_css fix** — inlines editor CSS via `content_style`
to work around TinyMCE's `document.open()` creating a
service-worker-uncontrolled iframe.

## Test plan

### Automated (Playwright, all 32 legacy versions)

```bash
npm run dev
# Wait for http://localhost:5400/website-server/ to be ready
node packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs
```

Covers front page, single post, admin dashboard, new post page, and
plugin activation across every WP minor release from 1.0 to 6.2.

### Manual UI walk-through

1. `npm run dev` and open http://localhost:5400/.
2. Open the settings gear → tick **Include older versions**.
3. Verify the WP dropdown shows two groups and every release from 1.0 to
6.9.
4. Pick **WP 4.9** → PHP locks to 5.2 (disabled).
5. Pick **WP 5.5** → PHP locks to 7.4 (disabled).
6. Pick **WP 4.8**, go to Posts → Add New, verify TinyMCE loads without
CSS errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants