Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const importWordPressFiles: StepHandler<
// Remove the directory where we unzipped the imported zip file.
await playground.rmdir(importPath);

// Ensure required constants are defined in wp-config.php.
// Ensure required constants are defined if wp-config.php doesn't define them.
await ensureWpConfig(playground, documentRoot);

const newSiteUrl = await playground.absoluteUrl;
Expand Down
6 changes: 3 additions & 3 deletions packages/playground/wordpress/src/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,9 @@ export async function bootWordPress(
php.defineConstant('WP_SITEURL', options.siteUrl);

/*
* Add required constants to "wp-config.php" if they are not already defined.
* This is needed, because some WordPress backups and exports may not include
* definitions for some of the necessary constants.
* Ensure required constants are defined if "wp-config.php" doesn't define
* them. This is needed because some WordPress backups and exports may not
* include definitions for some of the necessary constants.
*/
await ensureWpConfig(php, requestHandler.documentRoot);
// Run "before database" hooks to mount/copy more files in
Expand Down
113 changes: 55 additions & 58 deletions packages/playground/wordpress/src/test/wp-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,96 @@ import { joinPaths } from '@php-wasm/util';

const documentRoot = '/tmp';
const wpConfigPath = joinPaths(documentRoot, 'wp-config.php');
const constsJsonPath = '/internal/shared/consts.json';

function getDefinedConstants(php: PHP): Record<string, unknown> {
if (!php.fileExists(constsJsonPath)) {
return {};
}
return JSON.parse(php.readFileAsText(constsJsonPath));
}

/*
* Tests below execute the rewritten wp-config.php and assert on
* the JSON output, not just on define() substrings. This proves
* the file still parses and runs, constants have the expected
* runtime values, and no warnings or errors were introduced.
*/
describe('ensureWpConfig', () => {
let php: PHP;
beforeEach(async () => {
php = new PHP(await loadNodeRuntime(RecommendedPHPVersion));
});

it('should define required constants when they are missing', async () => {
php.writeFile(
wpConfigPath,
`<?php
echo json_encode([
'DB_NAME' => DB_NAME,
]);`
);
it('should define DB_NAME via defineConstant when wp-config.php does not define it', async () => {
php.writeFile(wpConfigPath, `<?php`);
await ensureWpConfig(php, documentRoot);

const rewritten = php.readFileAsText(wpConfigPath);
expect(rewritten).toContain(`define( 'DB_NAME', 'wordpress' );`);
// wp-config.php should not be modified.
expect(php.readFileAsText(wpConfigPath)).toBe(`<?php`);

const response = await php.run({ code: rewritten });
expect(response.json).toEqual({
DB_NAME: 'wordpress',
// DB_NAME should be defined via the auto-prepend mechanism.
expect(getDefinedConstants(php)).toHaveProperty('DB_NAME', 'wordpress');

// DB_NAME should be available at runtime via the auto-prepend script.
const response = await php.run({
code: `<?php echo json_encode(['DB_NAME' => DB_NAME]);`,
});
expect(response.errors).toHaveLength(0);
expect(response.json).toEqual({ DB_NAME: 'wordpress' });
});
Comment on lines +25 to +41
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This assertion verifies that defineConstant was invoked (via consts.json), but it doesn’t prove DB_NAME is actually available to WordPress execution (i.e., visible at runtime when WordPress loads). Add an assertion that runs PHP (via php.run) after ensureWpConfig and checks defined('DB_NAME') and DB_NAME value, to cover the end-to-end behavior this PR relies on.

Copilot uses AI. Check for mistakes.

it('should only define missing constants', async () => {
it('should not define DB_NAME when wp-config.php already defines it', async () => {
php.writeFile(
wpConfigPath,
`<?php
define( 'DB_USER', 'unchanged' );
define( 'AUTH_KEY', 'unchanged' );
define( 'WP_DEBUG', true );
echo json_encode([
'DB_NAME' => DB_NAME,
'DB_USER' => DB_USER,
'AUTH_KEY' => AUTH_KEY,
'WP_DEBUG' => WP_DEBUG,
]);`
define( 'DB_NAME', 'custom-db' );`
);
await ensureWpConfig(php, documentRoot);

const rewritten = php.readFileAsText(wpConfigPath);
expect(rewritten).toContain(`define( 'DB_NAME', 'wordpress' );`);
expect(rewritten).toContain(`define( 'DB_USER', 'unchanged' );`);
expect(rewritten).not.toContain(
`define( 'DB_USER', 'username_here' );`
);
expect(rewritten).toContain(`define( 'AUTH_KEY', 'unchanged' );`);
expect(rewritten).not.toContain(
`define( 'AUTH_KEY', 'put your unique phrase here' );`
// wp-config.php should not be modified.
expect(php.readFileAsText(wpConfigPath)).toContain(
`define( 'DB_NAME', 'custom-db' );`
);
expect(rewritten).toContain(`define( 'WP_DEBUG', true );`);
expect(rewritten).not.toContain(`define( 'WP_DEBUG', false );`);

const response = await php.run({ code: rewritten });
expect(response.json).toEqual({
DB_NAME: 'wordpress',
DB_USER: 'unchanged',
AUTH_KEY: 'unchanged',
WP_DEBUG: true,
});
// DB_NAME should not be in consts.json.
expect(getDefinedConstants(php)).not.toHaveProperty('DB_NAME');
});

it('should not define required constants when they are already defined conditionally', async () => {
it('should not define DB_NAME when wp-config.php defines it conditionally', async () => {
php.writeFile(
wpConfigPath,
`<?php
if(!defined('DB_NAME')) {
define('DB_NAME','defined-conditionally');
}
echo json_encode([
'DB_NAME' => DB_NAME,
]);`
}`
);
await ensureWpConfig(php, documentRoot);

const rewritten = php.readFileAsText(wpConfigPath);
expect(rewritten).not.toContain(`define( 'DB_NAME', 'wordpress' );`);
// wp-config.php should not be modified.
expect(php.readFileAsText(wpConfigPath)).toContain(
`define('DB_NAME','defined-conditionally');`
);

const response = await php.run({ code: rewritten });
expect(response.json).toEqual({
DB_NAME: 'defined-conditionally',
});
// DB_NAME should not be in consts.json.
expect(getDefinedConstants(php)).not.toHaveProperty('DB_NAME');
});

it('should only define missing constants and preserve pre-existing ones', async () => {
php.writeFile(
wpConfigPath,
`<?php
define( 'DB_NAME', 'custom-db' );`
);
php.defineConstant('WP_HOME', 'http://example.com');
await ensureWpConfig(php, documentRoot);

const consts = getDefinedConstants(php);
expect(consts).not.toHaveProperty('DB_NAME');
expect(consts).toHaveProperty('WP_HOME', 'http://example.com');
});
});

/*
* Tests below execute the rewritten wp-config.php and assert on
* the JSON output, not just on define() substrings. This proves
* the file still parses and runs, constants have the expected
* runtime values, and no warnings or errors were introduced.
*/
describe('defineWpConfigConstants', () => {
let php: PHP;
beforeEach(async () => {
Expand Down
76 changes: 55 additions & 21 deletions packages/playground/wordpress/src/wp-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import type { UniversalPHP } from '@php-wasm/universal';
import wpConfigTransformer from './wp-config-transformer.php?raw';

/**
* Ensures that the "wp-config.php" file exists and required constants are defined.
* Ensures that "wp-config.php" exists and required constants are defined.
*
* When a required constant is missing, it will be defined with a default value.
* - Copies "wp-config-sample.php" to "wp-config.php" if it doesn't exist.
* - Defines fallback values for missing constants without modifying "wp-config.php".
*
* @param php The PHP instance.
* @param documentRoot The path to the document root.
Expand All @@ -17,9 +18,6 @@ export async function ensureWpConfig(
documentRoot: string
): Promise<void> {
const wpConfigPath = joinPaths(documentRoot, 'wp-config.php');
const defaults = {
DB_NAME: 'wordpress',
};

/**
* WordPress requires a wp-config.php file to be present during
Expand Down Expand Up @@ -51,23 +49,10 @@ export async function ensureWpConfig(
return;
}

// Ensure required constants are defined.
const js = phpVars({ wpConfigPath, constants: defaults });
const result = await php.run({
code: `${wpConfigTransformer}
$wp_config_path = ${js.wpConfigPath};
$transformer = WP_Config_Transformer::from_file($wp_config_path);
foreach ( ${js.constants} as $name => $value ) {
if ( ! $transformer->constant_exists( $name ) ) {
$transformer->define_constant($name, $value);
}
}
$transformer->to_file($wp_config_path);
`,
// Ensure missing constants are defined without modifying "wp-config.php".
await defineWpConfigConstantFallbacks(php, wpConfigPath, {
DB_NAME: 'wordpress',
});
if (result.errors.length > 0) {
throw new Error('Failed to auto-configure wp-config.php.');
}
}

/**
Expand Down Expand Up @@ -101,3 +86,52 @@ export async function defineWpConfigConstants(
throw new Error('Failed to rewrite constants in wp-config.php.');
}
}

/**
* Defines fallback values for constants missing from "wp-config.php".
*
* This function does NOT modify "wp-config.php":
*
* 1. It checks "wp-config.php" to determine which constants are missing.
* 2. It defines the missing constants via the PHP auto-prepend script.
*
* @param php The PHP instance.
* @param wpConfigPath The path to the "wp-config.php" file.
* @param fallbacks The constants to define if missing.
*/
async function defineWpConfigConstantFallbacks(
php: UniversalPHP,
wpConfigPath: string,
fallbacks: Record<string, string | boolean | number | null>
): Promise<void> {
const constantNames = Object.keys(fallbacks);
const js = phpVars({ wpConfigPath, constantNames });
const result = await php.run({
code: `${wpConfigTransformer}
$transformer = WP_Config_Transformer::from_file(${js.wpConfigPath});
$missing = [];
foreach (${js.constantNames} as $name) {
if (!$transformer->constant_exists($name)) {
$missing[] = $name;
}
}
echo json_encode($missing);
`,
});
if (result.errors.length > 0) {
throw new Error('Failed to check wp-config.php for constants.');
}

// Define the missing constants via the PHP auto-prepend script.
let missing: string[];
try {
missing = JSON.parse(result.text);
} catch {
throw new Error(
`Failed to parse wp-config.php constant check output: ${result.text}`
);
}
for (const name of missing) {
await php.defineConstant(name, fallbacks[name]);
}
}
Loading