Skip to content

Commit 6e95f70

Browse files
committed
fix: preserve multisite with external MySQL
1 parent 42f4489 commit 6e95f70

3 files changed

Lines changed: 95 additions & 1 deletion

File tree

packages/runtime-playground/src/playground-cli-runner.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ function externalDatabaseWpConfig(spec: RuntimeCreateSpec): string | undefined {
460460
const host = spec.runtimeEnv?.DB_HOST
461461
if (!host) return undefined
462462
const port = spec.runtimeEnv?.DB_PORT
463+
const multisite = blueprintEnablesMultisite(spec.environment.blueprint)
464+
const multisiteSite = multisiteSiteIdentity(spec.preview?.siteUrl)
463465
const values = {
464466
DB_NAME: spec.runtimeEnv?.DB_NAME ?? "runtime",
465467
DB_USER: spec.runtimeEnv?.DB_USER ?? "root",
@@ -474,11 +476,33 @@ define('DB_HOST', ${phpLiteral(values.DB_HOST)});
474476
define('DB_CHARSET', 'utf8mb4');
475477
define('DB_COLLATE', '');
476478
$table_prefix = 'wp_';
477-
if (!defined('ABSPATH')) define('ABSPATH', __DIR__ . '/');
479+
${multisite ? `define('MULTISITE', true);
480+
define('SUBDOMAIN_INSTALL', false);
481+
define('DOMAIN_CURRENT_SITE', ${phpLiteral(multisiteSite.domain)});
482+
define('PATH_CURRENT_SITE', ${phpLiteral(multisiteSite.path)});
483+
define('SITE_ID_CURRENT_SITE', 1);
484+
define('BLOG_ID_CURRENT_SITE', 1);
485+
` : ""}if (!defined('ABSPATH')) define('ABSPATH', __DIR__ . '/');
478486
require_once ABSPATH . 'wp-settings.php';
479487
`
480488
}
481489

490+
function blueprintEnablesMultisite(blueprint: unknown): boolean {
491+
if (!blueprint || typeof blueprint !== "object" || Array.isArray(blueprint)) return false
492+
const steps = Array.isArray((blueprint as { steps?: unknown }).steps) ? (blueprint as { steps: unknown[] }).steps : []
493+
return steps.some((step) => Boolean(step && typeof step === "object" && !Array.isArray(step) && (step as { step?: unknown }).step === "enableMultisite"))
494+
}
495+
496+
function multisiteSiteIdentity(siteUrl?: string): { domain: string; path: string } {
497+
try {
498+
const url = new URL(siteUrl || "http://localhost")
499+
const path = `/${url.pathname.replace(/^\/+|\/+$/g, "")}`
500+
return { domain: url.hostname || "localhost", path: path === "/" ? path : `${path}/` }
501+
} catch {
502+
return { domain: "localhost", path: "/" }
503+
}
504+
}
505+
482506
function distributionBootstrapPhp(spec: RuntimeCreateSpec): string {
483507
const distribution = recipeDistribution(spec)
484508
if (!distribution) {

tests/disposable-mysql-mysqli.integration.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,61 @@ require_once ABSPATH . 'wp-settings.php';
146146
assert.deepEqual(aggregate.counts, { total: 2, succeeded: 2, failed: 0, timedOut: 0, cancelled: 0 })
147147
assert.deepEqual(aggregate.entries.map((entry: { artifactNamespace: string }) => entry.artifactNamespace), ["phpunit/one", "phpunit/two"])
148148
assert.deepEqual(aggregate.entries.map((entry: { inputIndex: number }) => entry.inputIndex), [0, 1])
149+
150+
const multisitePlugin = join(directory, "managed-multisite-fixture")
151+
const multisiteRecipePath = join(directory, "managed-multisite-recipe.json")
152+
await mkdir(join(multisitePlugin, "tests"), { recursive: true })
153+
await writeFile(join(multisitePlugin, "managed-multisite-fixture.php"), `<?php
154+
/** Plugin Name: Managed Multisite Fixture */
155+
add_action('init', static function (): void {
156+
update_option('wp_codebox_parent_init_ran', 1);
157+
add_action('init', static function (): void {
158+
update_option('wp_codebox_nested_init_ran', 1);
159+
do_action('wp_codebox_nested_init_replayed');
160+
}, 15);
161+
}, 0);
162+
`)
163+
await writeFile(join(multisitePlugin, "phpunit.xml.dist"), "<?xml version=\"1.0\"?><phpunit><testsuites><testsuite name=\"managed-multisite\"><directory>tests</directory></testsuite></testsuites></phpunit>\n")
164+
await writeFile(join(multisitePlugin, "tests", "ManagedMultisiteTest.php"), `<?php
165+
final class ManagedMultisiteTest extends WP_UnitTestCase {
166+
public function test_external_mysql_preserves_multisite_blog_switching(): void {
167+
$this->assertTrue(is_multisite());
168+
$original = get_current_blog_id();
169+
$blog_id = self::factory()->blog->create();
170+
switch_to_blog($blog_id);
171+
$this->assertSame($blog_id, get_current_blog_id());
172+
$this->assertTrue(ms_is_switched());
173+
restore_current_blog();
174+
$this->assertSame($original, get_current_blog_id());
175+
$this->assertFalse(ms_is_switched());
176+
}
177+
178+
public function test_external_mysql_replays_wordpress_lifecycle_in_order(): void {
179+
$this->assertGreaterThan(0, did_action('init'));
180+
$this->assertSame(1, (int) get_option('wp_codebox_parent_init_ran'));
181+
$this->assertSame(1, (int) get_option('wp_codebox_nested_init_ran'));
182+
$this->assertSame(1, did_action('wp_codebox_nested_init_replayed'));
183+
}
184+
185+
public function test_runtime_uses_real_mysql(): void {
186+
global $wpdb;
187+
$this->assertInstanceOf(mysqli::class, $wpdb->dbh);
188+
$this->assertSame('1', (string) $wpdb->get_var("SELECT JSON_VALID('{\\"valid\\":true}')"));
189+
}
190+
}
191+
`)
192+
const multisiteRecipe = buildWordPressPhpunitRecipe({
193+
pluginSlug: "managed-multisite-fixture",
194+
phpVersion: "8.3",
195+
databaseType: "mysql",
196+
multisite: true,
197+
pluginSource: multisitePlugin,
198+
dependencyMounts: ["/wordpress/wp-content/plugins/managed-multisite-fixture"],
199+
mounts: [{ source: join(harness, "vendor"), target: "/wp-codebox-vendor", mode: "readonly" }],
200+
})
201+
await writeFile(multisiteRecipePath, `${JSON.stringify(multisiteRecipe)}\n`)
202+
const multisiteResult = await runRecipe({ recipePath: multisiteRecipePath, previewHoldBlocking: false, previewLeaseRequested: false, previewLeaseChild: false, timeoutMs: 300_000, json: true, summary: false, dryRun: false })
203+
assert.equal(multisiteResult.success, true, JSON.stringify(multisiteResult))
149204
console.log("disposable MySQL and MariaDB mysqli E2E passed")
150205
} finally {
151206
await rm(directory, { recursive: true, force: true })

tests/playground-cli-runner-bootstrap-ini.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ try {
103103
const externalWpConfig = await readFile(externalWpConfigPath as string, "utf8")
104104
assert.match(externalWpConfig, /define\('DB_HOST', "127\.0\.0\.1:33061"\)/)
105105
assert.match(externalWpConfig, /define\('DB_PASSWORD', "secret"\)/)
106+
assert.doesNotMatch(externalWpConfig, /define\('MULTISITE'/)
107+
108+
calls.length = 0
109+
const externalMultisiteSpec: RuntimeCreateSpec = {
110+
...spec,
111+
environment: { ...spec.environment, blueprint: { steps: [{ step: "enableMultisite" }] } },
112+
}
113+
const externalMultisiteServer = await startPlaygroundCliServer(externalMultisiteSpec, [], { cliModule })
114+
await externalMultisiteServer[Symbol.asyncDispose]()
115+
const externalMultisiteConfigPath = calls[0]["mount-before-install"]?.find((mount) => mount.vfsPath === "/wordpress/wp-config.php")?.hostPath
116+
assert.equal(typeof externalMultisiteConfigPath, "string")
117+
const externalMultisiteConfig = await readFile(externalMultisiteConfigPath as string, "utf8")
118+
assert.match(externalMultisiteConfig, /define\('MULTISITE', true\)/)
119+
assert.match(externalMultisiteConfig, /define\('SUBDOMAIN_INSTALL', false\)/)
120+
assert.match(externalMultisiteConfig, /define\('DOMAIN_CURRENT_SITE', "localhost"\)/)
106121

107122
calls.length = 0
108123
const defaultRuntimeIniSpec: RuntimeCreateSpec = {

0 commit comments

Comments
 (0)