Skip to content

Add legacy WordPress support on PHP 5.6#3469

Draft
JanJakes wants to merge 9 commits intotrunkfrom
legacy-wordpress-support-v2
Draft

Add legacy WordPress support on PHP 5.6#3469
JanJakes wants to merge 9 commits intotrunkfrom
legacy-wordpress-support-v2

Conversation

@JanJakes
Copy link
Copy Markdown
Member

@JanJakes JanJakes commented Apr 6, 2026

Summary

Run old WordPress versions (1.5 through 4.9 tested, any existing release should work) in the browser using PHP 5.6 WASM with SQLite as the database backend.

  • PHP 5.6 WASM compilation — asyncify (Node) and JSPI (browser) builds, with OpenSSL 1.1 compat patch and SQLite 3.51 upgrade
  • PHP 5.6 compatibility transpiler — transforms PHP 7+ syntax (type declarations, ??, Throwable, attributes, Closure::call()) to PHP 5.6, used to patch the SQLite Database Integration plugin. Guards nullsafe operators (?->) from corruption.
  • Pre-patched SQLite v2.2.22 — offline patcher adds polyfills (reinitialize_sqlite(), placeholder_escape(), etc.) and guards for old WP functions
  • Legacy WordPress boot processwp-load.php shim for WP < 2.0, extension_loaded('mysql') bypass, lazy $wpdb loader with reinitialize_sqlite(), MySQL/MySQLi function stubs, WP 1.5 seed data insertion (SQLite NOT NULL constraint fix)
  • Web worker and CLI integration — SQLite driver version selection per PHP/WP combination, mu-plugin guards for old WP functions
  • Bare version string support?wp=4.9 resolves to a wordpress.org download automatically (versions < 2.0 are normalized: 1.0→1.0.2, 1.2→1.2.2, 1.5→1.5.2)

Tested with WP 1.2 through 4.9 (31 versions).

Test plan

Automated (Playwright)

Requires Chrome/Chromium with JSPI support (Chrome 131+).

npm run dev
# Wait for "Local: http://127.0.0.1:5400/website-server/" in output

# In another terminal, run the full legacy boot test suite (31 WP versions):
node packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs

Manual (browser)

  1. Start the dev server: npm run dev
  2. Open each URL in Chrome/Chromium (JSPI required):
  3. Verify each shows a WordPress front page with "Hello world!" post

Unit tests

npx nx test playground-wordpress --testFile=legacy-php-compat.spec.ts

Runs 28 tests covering the PHP 5.6 transpiler functions (stripPhp7TypeDeclarations, replaceNullCoalescing, replacePhp7ErrorClasses).

Known issues

  • WP 1.0.2 excluded — SQLite AST driver can't parse its enum() column types
  • WP 1.5 shows PHP warnings: "Creating default object from empty value" and deprecated preg_replace /e modifier (WP 1.5 code predates PHP 5.4/5.5)
  • prefetchUpdateChecks fails on all legacy WP versions (non-fatal)
  • assertValidDatabaseConnection always fails for legacy PHP (treated as non-fatal)
  • CLI production build only ships trunk SQLite ZIP — vite.config.ts needs updating to copy version-specific ZIPs
  • CLI handler missing v2.1.16 SQLite selection for WP < 6.x on modern PHP

@JanJakes JanJakes force-pushed the legacy-wordpress-support-v2 branch from bda9d53 to 90815ba Compare April 6, 2026 09:32
@JanJakes JanJakes requested a review from adamziel April 6, 2026 09:37
@JanJakes JanJakes force-pushed the legacy-wordpress-support-v2 branch 6 times, most recently from 18d442f to 9c61190 Compare April 7, 2026 12:00
@JanJakes JanJakes changed the title Add legacy WordPress support (PHP 5.6 + WP 4.9/3.9/2.9/1.5) Add legacy WordPress support on PHP 5.6 Apr 7, 2026
@JanJakes JanJakes force-pushed the legacy-wordpress-support-v2 branch 5 times, most recently from 79c3c39 to 373d90a Compare April 8, 2026 11:44
JanJakes added 9 commits April 8, 2026 13:54
Introduce LegacyPHPVersions and AllPHPVersions types alongside
the existing SupportedPHPVersions. PHP 5.6 is classified as a
"legacy" version — available but not in the standard supported
list. Update Blueprint types to accept legacy PHP versions and
regenerate the Blueprint JSON schema.
Add PHP 5.6.40 compilation support for both asyncify and JSPI
build modes. Key changes to the build pipeline:

- Add OpenSSL 1.1 compatibility patch (1100 lines) since PHP 5.6
  directly accesses opaque OpenSSL structs removed in later versions
- Replace PHP 5.6's bundled SQLite 3.8.10.2 with SQLite 3.51.0
  (the v2.2.22 SQLite driver requires >= 3.37.0)
- Add EMULATE_FUNCTION_POINTER_CASTS for JSPI build to fix a sort
  crash in PHP 5.6's zend_qsort
- Patch C source files for compatibility with older PHP internal APIs
- Add node-builds/5-6 and web-builds/5-6 packages with pre-compiled
  WASM binaries
- Add PHP 5.6 loader entries in node and web runtime packages
Add functions that transform PHP 7+ code to PHP 5.6 compatible
code for the SQLite integration plugin:

- stripPhp7TypeDeclarations: removes parameter and return type
  declarations, including from interface/abstract methods (`;`
  terminators). Handles `mixed`, `never`, and nullable types.
- replaceNullCoalescing: transforms ?? operators to isset()
  ternaries, with special handling for class constants and
  method calls to avoid double evaluation. Includes a safety
  limit to prevent infinite loops.
- replacePhp7ErrorClasses: replaces Throwable with Exception,
  removes PHP 8 attributes and declare(strict_types), transforms
  Closure::call(), replaces callable property invocations with
  call_user_func(), and handles isset() on class constant arrays.

These are used both at runtime (when extracting the SQLite
plugin ZIP) and offline (in the pre-patching script).
Add an offline patcher script that transforms the SQLite Database
Integration v2.2.22 plugin into a PHP 5.6-compatible version:

- Removes all PHP 7+ syntax using the transpiler functions
- Renames 'throw' method (reserved word in PHP 5.6)
- Adds polyfills: placeholder_escape(), reinitialize_sqlite(),
  init_charset(), get_caller(), log_query(), and wpdb properties
- Guards WordPress function calls that don't exist in old versions
- Makes schema parse failures non-fatal for old WP schemas
- Skips SQLite's wp_install() override for WP < 3.0

The pre-patched ZIP is committed as a build artifact. Add version
entries for v2.2.22 and v2.2.22-php56 in the SQLite driver module
details.

Regenerate with:
  node --experimental-strip-types scripts/patch-sqlite-for-php56.mjs
Make WordPress 1.2 through 4.9 bootable on PHP 5.6 with SQLite by
patching WP source files at boot time. All legacy-specific code lives
in dedicated modules (legacy-wp-fixes.ts, mysql-shims.ts) to keep
boot.ts and index.ts focused on the core boot flow.

WP source file patching (legacy-wp-fixes.ts):
- wp-settings.php: disable extension_loaded('mysql') check, suppress
  E_DEPRECATED/E_STRICT, remove set_magic_quotes_runtime, guard
  get_magic_quotes_gpc, fix =& new syntax, replace $HTTP_SERVER_VARS
- wp-db.php: guard $wpdb creation, patch mysql_connect to call
  db_connect(), inject wpdb method polyfills (set_prefix, timer_start,
  timer_stop, init_charset, bail, check_connection)
- install.php: fix relative paths, replace $HTTP_GET_VARS/$HTTP_POST_VARS,
  combine WP 1.x multi-step installer into single request
- functions.php: fix $all_options stdClass, eval db.php for parser bug
- schema.php: add wp_get_db_schema() polyfill for WP < 3.3
- Create wp-load.php shim for WP < 2.0, version.php stub for WP < 1.5
- Pre-create WP 1.x tables via PDO, seed admin user and default content
- Strip doc comments and skip large includes for WP 2.8 (parser bug)

SQLite integration (index.ts, legacy-wp-fixes.ts):
- Patch SQLite plugin for PHP 5.6 (strip type declarations, null
  coalescing, rename reserved words, replace __DIR__ and require_once)
- Write db.php with MySQL/MySQLi stubs and PHP polyfills
- Lazy $wpdb loader calls reinitialize_sqlite() for old WP
- Functional mysql_* stubs (mysql-shims.ts) delegate to $wpdb

Boot flow adjustments (boot.ts):
- Skip ensureWpConfig for legacy PHP, copy wp-config-sample.php instead
- Non-fatal install errors and database validation for legacy PHP
- Skip isWordPressInstalled() for legacy PHP (crashes WASM on old WP)

Also add JavaScript unzip fallback for PHP builds without ZipArchive.
Web worker (playground-worker-endpoint-blueprints-v1.ts):
- Select SQLite driver version based on PHP and WP version:
  PHP 5.6 uses pre-patched v2.2.22-php56, WP < 6.x on modern
  PHP uses v2.1.16, modern WP uses trunk
- Pass phpVersion to bootWordPress for legacy detection
- Disable WP_DEBUG for legacy PHP

CLI (blueprints-v1-handler.ts, download.ts, worker-thread-v1.ts):
- Add getPrebuiltWordPressPath() to use bundled WP ZIPs
- Pass SQLite version parameter to fetchSqliteIntegration()
- Pass phpVersion through to boot process

Mu-plugins (0-playground.php, wp_http_fetch.php, wp_http_dummy.php):
- Guard wp_doing_ajax() and wp_doing_cron() with function_exists()
- Guard get_current_user_id() for WP < 3.0 compatibility
- Guard Requests class and Requests_Transport interface checks
- Replace [] syntax with array() for PHP 5.6 compatibility
Test that WordPress versions 1.2 through 4.9 boot on PHP 5.6 with
SQLite and display "Hello world!". Starts the dev server, then runs
each version through the browser via Playwright.

WP 1.0.2 is excluded — the SQLite AST driver can't parse its enum()
column types, causing WHERE clause failures.
When a non-minified WP version like "4.9" or "1.5" is passed via
?wp= parameter, construct a wordpress.org download URL and route
it through the CORS proxy instead of silently falling back to the
latest minified WP build.

Versions >= 2.0 work as wordpress-<major>.<minor>.zip (wordpress.org
redirects to latest patch). Versions < 2.0 need normalization since
wordpress.org only hosts explicit patch versions (1.0.2, 1.2.2, 1.5.2).

Also fix SQLite version selection to use the actual requested WP
version instead of the minified fallback, ensuring old WP on modern
PHP correctly gets the v2.1.16 SQLite driver.
Three issues prevented wp-admin from working on PHP 5.6:

1. template.php and media.php were replaced with empty stubs as a
   workaround for the WASM parser size limit. The real fix is to
   strip doc comments (which stripDocCommentsFromWpIncludes already
   does). Remove the stub replacement. Also preserve ?> tags during
   comment stripping — PHP's ?> closes PHP mode even inside //
   comments.

2. Old WordPress (< 3.7) uses relative paths in wp-admin scripts.
   Patch these to use dirname(__FILE__) for absolute resolution.
   Covers index.php, index-extra.php, and admin.php across all
   old WP versions.

3. Auth cookies don't persist correctly for old WordPress admin
   sessions. Fix by re-generating auth cookies on every admin
   request: mu-plugin for WP 2.8+, direct admin.php patch for
   WP < 2.8 (no mu-plugin support). Handles three auth eras:
   - WP 2.5+: wp_generate_auth_cookie + $_COOKIE population
   - WP 2.0-2.4: USER_COOKIE/PASS_COOKIE with md5(md5(password))
   - WP 1.5: COOKIEHASH-based hardcoded cookie names

Also skip prefetchUpdateChecks for WP < 5.0 (the functions it
calls don't exist in old WP), skip 0-playground.php mu-plugin
for WP < 3.0 (closures in hooks cause fatal errors), and set up
wp_user_roles + usermeta when missing after install.
@JanJakes JanJakes force-pushed the legacy-wordpress-support-v2 branch from 373d90a to ad83845 Compare April 8, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants