From 1de041a819415ad47c8f05f9a280eec320b290ae Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 1 Aug 2025 15:38:44 +0200 Subject: [PATCH 1/5] [Blueprints v2] Add support for "enableMultisite" step --- components/Blueprints/Runner.php | 3 + .../Blueprints/Steps/EnableMultisiteStep.php | 114 +++++++++++++ .../Unit/Steps/EnableMultisiteStepTest.php | 156 ++++++++++++++++++ .../Version2/json-schema/schema-v2.json | 51 +++--- .../appendix-A-blueprint-v2-schema.ts | 5 + 5 files changed, 310 insertions(+), 19 deletions(-) create mode 100644 components/Blueprints/Steps/EnableMultisiteStep.php create mode 100644 components/Blueprints/Tests/Unit/Steps/EnableMultisiteStepTest.php diff --git a/components/Blueprints/Runner.php b/components/Blueprints/Runner.php index 08e3d55f..8885c4d3 100644 --- a/components/Blueprints/Runner.php +++ b/components/Blueprints/Runner.php @@ -24,6 +24,7 @@ use WordPress\Blueprints\Steps\ActivateThemeStep; use WordPress\Blueprints\Steps\CpStep; use WordPress\Blueprints\Steps\DefineConstantsStep; +use WordPress\Blueprints\Steps\EnableMultisiteStep; use WordPress\Blueprints\Steps\Exception; use WordPress\Blueprints\Steps\ImportContentStep; use WordPress\Blueprints\Steps\ImportMediaStep; @@ -673,6 +674,8 @@ private function createStepObject( string $stepType, array $data ) { return new CpStep( $data['fromPath'], $data['toPath'] ); case 'defineConstants': return new DefineConstantsStep( $data['constants'] ); + case 'enableMultisite': + return new EnableMultisiteStep(); case 'importContent': /** * Flatten the content declaration from diff --git a/components/Blueprints/Steps/EnableMultisiteStep.php b/components/Blueprints/Steps/EnableMultisiteStep.php new file mode 100644 index 00000000..e1f5ef93 --- /dev/null +++ b/components/Blueprints/Steps/EnableMultisiteStep.php @@ -0,0 +1,114 @@ +setCaption( 'Enabling multisite' ); + + $code = +<<<'PHP' +tables( 'ms_global' ) as $table => $prefixed_table ) { + $wpdb->$table = $prefixed_table; +} + +install_network(); + +// Get multisite arguments +$site_id = 1; +$base = '/'; +$title = sprintf( '%s Sites', get_option( 'blogname' ) ); +$admin_email = get_option( 'admin_email' ); +$subdomains = false; + +// Get the base domain +$siteurl = get_option( 'siteurl' ); +$domain = (string) preg_replace( '|https?://|', '', $siteurl ); +$slash = strpos( $domain, '/' ); +if ( false !== $slash ) { + $domain = substr( $domain, 0, $slash ); +} + +// Eagerly check for custom ports +if ( strpos( $domain, ':' ) !== false ) { + throw new Exception( + sprintf( + 'The current host is "%s", but WordPress multisites do not support custom ports.', + $domain + ) + ); +} + +$result = populate_network( + $site_id, + $domain, + $admin_email, + $title, + $base, + $subdomains +); + +$site_id = $wpdb->get_var( "SELECT id FROM $wpdb->site" ); +$site_id = ( null === $site_id ) ? 1 : (int) $site_id; + +if ( $result instanceof WP_Error ) { + throw new Exception( + sprintf( + 'Error: [%s] %s', + $result->get_error_code(), + $result->get_error_message() + ) + ); +} + +// delete_site_option() cleans the alloptions cache to prevent dupe option +delete_site_option( 'upload_space_check_disabled' ); +update_site_option( 'upload_space_check_disabled', 1 ); + +$wp_config_constants = array( + 'WP_ALLOW_MULTISITE' => true, + 'MULTISITE' => true, + 'SUBDOMAIN_INSTALL' => $subdomains, + 'DOMAIN_CURRENT_SITE' => $domain, + 'PATH_CURRENT_SITE' => $base, + 'SITE_ID_CURRENT_SITE' => $site_id, + 'BLOG_ID_CURRENT_SITE' => 1, +); + +append_output( json_encode( $wp_config_constants ) ); +PHP; + + try { + $result = $runtime->evalPhpCodeInSubProcess( $code ); + } catch ( ProcessFailedException $e ) { + throw new BlueprintExecutionException( $e->getMessage() ); + } + + if ( '' === $result->outputFileContent ) { + throw new BlueprintExecutionException( 'Failed to enable multisite' ); + } + + // Reuse DefineConstantsStep to set the multisite constants. + $wpConfigConstants = json_decode( $result->outputFileContent, true ); + $defineConstantsStep = new DefineConstantsStep( $wpConfigConstants ); + $defineConstantsStep->run( $runtime, $tracker ); + } +} diff --git a/components/Blueprints/Tests/Unit/Steps/EnableMultisiteStepTest.php b/components/Blueprints/Tests/Unit/Steps/EnableMultisiteStepTest.php new file mode 100644 index 00000000..c7439c9b --- /dev/null +++ b/components/Blueprints/Tests/Unit/Steps/EnableMultisiteStepTest.php @@ -0,0 +1,156 @@ +runtime->evalPhpCodeInSubProcess( + <<<'PHP' +run( $this->runtime, $tracker ); + + // Verify that multisite is set up and enabled. + $result = $this->runtime->evalPhpCodeInSubProcess( + <<<'PHP' + is_multisite(), + 'name' => get_bloginfo( 'name' ), + 'wpurl' => get_bloginfo( 'wpurl' ), + 'url' => get_bloginfo( 'url' ), + 'network' => get_network(), + 'constants' => [ + 'WP_ALLOW_MULTISITE' => defined( 'WP_ALLOW_MULTISITE' ) ? WP_ALLOW_MULTISITE : null, + 'MULTISITE' => defined( 'MULTISITE' ) ? MULTISITE : null, + 'SUBDOMAIN_INSTALL' => defined( 'SUBDOMAIN_INSTALL' ) ? SUBDOMAIN_INSTALL : null, + 'DOMAIN_CURRENT_SITE' => defined( 'DOMAIN_CURRENT_SITE' ) ? DOMAIN_CURRENT_SITE : null, + 'PATH_CURRENT_SITE' => defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : null, + 'SITE_ID_CURRENT_SITE' => defined( 'SITE_ID_CURRENT_SITE' ) ? SITE_ID_CURRENT_SITE : null, + 'BLOG_ID_CURRENT_SITE' => defined( 'BLOG_ID_CURRENT_SITE' ) ? BLOG_ID_CURRENT_SITE : null, + ], + ] ) +); + +PHP + ); + + $output = json_decode( $result->outputFileContent, true ); + $this->assertSame( true, $output['is_multisite'] ); + $this->assertSame( 'WordPress Site', $output['name'] ); + $this->assertSame( 'http://localhost', $output['wpurl'] ); + $this->assertSame( 'http://localhost', $output['url'] ); + + $network = $output['network']; + $this->assertSame( 'localhost', $network['domain'] ); + $this->assertSame( 'localhost', $network['cookie_domain'] ); + $this->assertSame( '/', $network['path'] ); + $this->assertSame( 'WordPress Site Sites', $network['site_name'] ); + + $constants = $output['constants']; + $this->assertSame( true, $constants['WP_ALLOW_MULTISITE'] ); + $this->assertSame( true, $constants['MULTISITE'] ); + $this->assertSame( false, $constants['SUBDOMAIN_INSTALL'] ); + $this->assertSame( 'localhost', $constants['DOMAIN_CURRENT_SITE'] ); + $this->assertSame( '/', $constants['PATH_CURRENT_SITE'] ); + $this->assertSame( 1, $constants['SITE_ID_CURRENT_SITE'] ); + $this->assertSame( 1, $constants['BLOG_ID_CURRENT_SITE'] ); + } + + + public function testEnableMultisiteRedirectsWhenSiteNotFound() { + $step = new EnableMultisiteStep(); + $tracker = new Tracker(); + $step->run( $this->runtime, $tracker ); + + // Verify that multisite is set up and enabled. + $result = $this->runtime->evalPhpCodeInSubProcess( + <<<'PHP' +assertSame( 'redirected', $result->outputFileContent ); + } + + public function testEnableMultisiteFailsOnNon80Port() { + $this->runtime->evalPhpCodeInSubProcess( + <<<'PHP' +expectException( BlueprintExecutionException::class ); + $this->expectExceptionMessage( 'The current host is "localhost:8080", but WordPress multisites do not support custom ports.' ); + $step = new EnableMultisiteStep(); + $tracker = new Tracker(); + $step->run( $this->runtime, $tracker ); + } + + public function testEnableMultisiteFailsWhenAlreadyEnabled() { + $step = new EnableMultisiteStep(); + $tracker = new Tracker(); + $step->run( $this->runtime, $tracker ); + + $this->expectException( BlueprintExecutionException::class ); + $this->expectExceptionMessage( '[siteid_exists] The network already exists.' ); + $step->run( $this->runtime, $tracker ); + } + + public function testEnableMultisiteFailsWhenConfigInvalid() { + $this->runtime->evalPhpCodeInSubProcess( + <<<'PHP' +expectException( BlueprintExecutionException::class ); + $this->expectExceptionMessage( 'Failed to enable multisite' ); + $step = new EnableMultisiteStep(); + $tracker = new Tracker(); + $step->run( $this->runtime, $tracker ); + } +} diff --git a/components/Blueprints/Versions/Version2/json-schema/schema-v2.json b/components/Blueprints/Versions/Version2/json-schema/schema-v2.json index df8ca12a..5ce214d3 100644 --- a/components/Blueprints/Versions/Version2/json-schema/schema-v2.json +++ b/components/Blueprints/Versions/Version2/json-schema/schema-v2.json @@ -314,13 +314,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -747,13 +747,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -1190,13 +1190,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -1333,13 +1333,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -1617,6 +1617,19 @@ ], "additionalProperties": false }, + { + "type": "object", + "properties": { + "step": { + "type": "string", + "const": "enableMultisite" + } + }, + "required": [ + "step" + ], + "additionalProperties": false + }, { "type": "object", "properties": { @@ -1822,13 +1835,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -1965,13 +1978,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -2196,13 +2209,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -2432,13 +2445,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] @@ -2666,7 +2679,7 @@ ], "description": "Plugin directory reference, e.g. \"jetpack\", \"jetpack@6.4\", or \"akismet@6.4.3\".\n\nThese refer to a specific plugin slugs in the WordPress.org plugin repository.\n\nFor example, a reference to \"wordpress-seo\" means the Yoast SEO plugin as seen on https://wordpress.org/plugins/wordpress-seo/.\n\nThe Plugin Directory Reference are only meaningful in:\n\n* The top-level `plugins` array\n* The `installPlugin` imperative step" }, - "alias-1068919986-42696-42793-1068919986-0-42794": { + "alias-1068919986-42778-42875-1068919986-0-42876": { "anyOf": [ { "type": "string" @@ -2680,13 +2693,13 @@ { "type": "array", "items": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } }, { "type": "object", "additionalProperties": { - "$ref": "#/definitions/alias-1068919986-42696-42793-1068919986-0-42794" + "$ref": "#/definitions/alias-1068919986-42778-42875-1068919986-0-42876" } } ] diff --git a/components/Blueprints/Versions/Version2/json-schema/wsp/wsp-1-blueprint-v2-schema/appendix-A-blueprint-v2-schema.ts b/components/Blueprints/Versions/Version2/json-schema/wsp/wsp-1-blueprint-v2-schema/appendix-A-blueprint-v2-schema.ts index 0136a9d9..54295bfb 100644 --- a/components/Blueprints/Versions/Version2/json-schema/wsp/wsp-1-blueprint-v2-schema/appendix-A-blueprint-v2-schema.ts +++ b/components/Blueprints/Versions/Version2/json-schema/wsp/wsp-1-blueprint-v2-schema/appendix-A-blueprint-v2-schema.ts @@ -1449,6 +1449,10 @@ type DefineConstantsStep = { constants: WordPressConstants; }; +type EnableMultisiteStep = { + step: 'enableMultisite'; +}; + type ImportContentStep = { step: 'importContent'; content: ContentDefinition[]; @@ -1572,6 +1576,7 @@ type Step = | ActivateThemeStep | CpStep | DefineConstantsStep + | EnableMultisiteStep | ImportContentStep | ImportMediaStep | ImportThemeStarterContentStep From b3d2dc2841190f6a5030c1ae524c69a8bd3f2c83 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 5 Aug 2025 11:45:18 +0200 Subject: [PATCH 2/5] Add "enableMultisite" step to v1-to-v2 blueprint transpiler --- .../Blueprints/Versions/Version1/V1ToV2Transpiler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/Blueprints/Versions/Version1/V1ToV2Transpiler.php b/components/Blueprints/Versions/Version1/V1ToV2Transpiler.php index ba2bd9f6..170dadf4 100644 --- a/components/Blueprints/Versions/Version1/V1ToV2Transpiler.php +++ b/components/Blueprints/Versions/Version1/V1ToV2Transpiler.php @@ -210,9 +210,9 @@ public function upgrade( array $validated_v1_blueprint ): array { $this->logger->warning( 'The `defineSiteUrl` step is not supported by the Blueprint v2 schema. Use the runner configuration to set the site URL instead.' ); break; case 'enableMultisite': - // @TODO: Support this step in v3. Multisites will require a separate schema - // that defines what's related to which site. - $this->logger->warning( 'The `enableMultisite` step is not supported by the Blueprint v2 schema and will be ignored.' ); + $v2steps[] = [ + 'step' => 'enableMultisite', + ]; break; case 'importWordPressFiles': if ( isset( $v1step['progress'] ) ) { From 0a3f76fdfe71c7ead1f530a41c6d19bb596b45b5 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 5 Aug 2025 10:35:09 +0200 Subject: [PATCH 3/5] Inject new constants to a better location within "wp-config.php" --- .../Blueprints/Steps/DefineConstantsStep.php | 100 ++++++++-- .../Unit/Steps/DefineConstantsStepTest.php | 181 ++++++++++++++---- 2 files changed, 228 insertions(+), 53 deletions(-) diff --git a/components/Blueprints/Steps/DefineConstantsStep.php b/components/Blueprints/Steps/DefineConstantsStep.php index d924f216..3bf8e5a3 100644 --- a/components/Blueprints/Steps/DefineConstantsStep.php +++ b/components/Blueprints/Steps/DefineConstantsStep.php @@ -314,25 +314,72 @@ function rewrite_wp_config_to_define_constants( $content, $constants = array() ) // Add any constants that weren't found in the file if ( count( $constants ) ) { - $prepend = array( - " $value ) { - $prepend = array_merge( - $prepend, - array( - 'define(', - var_export( $name, true ), - ',', - var_export( $value, true ), - ");\n", - ) - ); + // First try to find the "That's all, stop editing!" comment. + $anchor = find_first_token_index( $output, T_COMMENT, "That's all, stop editing!" ); + + // If not found, try the "Absolute path to the WordPress directory." doc comment. + if ( null === $anchor ) { + $anchor = find_first_token_index( $output, T_DOC_COMMENT, "Absolute path to the WordPress directory." ); } - $prepend[] = '?>'; - $output = array_merge( - $prepend, - $output + + // If not found, try the "Sets up WordPress vars and included files." doc comment. + if ( null === $anchor ) { + $anchor = find_first_token_index( $output, T_DOC_COMMENT, "Sets up WordPress vars and included files." ); + } + + // If not found, try "require_once ABSPATH . 'wp-settings.php';". + if ( null === $anchor ) { + $require_anchor = find_first_token_index( $output, T_REQUIRE_ONCE ); + if ( null !== $require_anchor ) { + $abspath = $output[$require_anchor + 2] ?? null; + $path = $output[$require_anchor + 6] ?? null; + if ( + ( is_array( $abspath ) && $abspath[1] === 'ABSPATH' ) + && ( is_array( $path ) && $path[1] === "'wp-settings.php'" ) + ) { + $anchor = $require_anchor; + } + } + } + + // If not found, fall back to the PHP opening tag. + if ( null === $anchor ) { + $open_tag_anchor = find_first_token_index( $output, T_OPEN_TAG ); + if ( null !== $open_tag_anchor ) { + $anchor = $open_tag_anchor + 1; + } + } + + // If we still don't have an anchor, the file is not a valid PHP file. + if ( null === $anchor ) { + error_log( "Blueprint Error: wp-config.php file is not a valid PHP file." ); + exit( 1 ); + } + + // Ensure surrounding newlines when not already present. + $prev = $output[ $anchor - 1 ] ?? null; + $prev = is_array( $prev ) ? $prev[1] : $prev; + $next = $output[ $anchor ] ?? null; + $next = is_array( $next ) ? $next[1] : $next; + + $no_prefix = $prev && "\n\n" === substr( $prev, -2 ); + $no_suffix = $next && "\n\n" === substr( $next, 0, 2 ); + + // Add the new constants. + $new_constants = array( "\n" ); + foreach ( $constants as $name => $path ) { + $new_constants[] = 'define( '; + $new_constants[] = var_export( $name, true ); + $new_constants[] = ', '; + $new_constants[] = var_export( $path, true ); + $new_constants[] = " );\n"; + } + $new_constants[] = "\n"; + + $output = array_merge( + array_slice( $output, 0, $anchor ), + $new_constants, + array_slice( $output, $anchor ) ); } @@ -365,6 +412,23 @@ function skip_whitespace( $tokens ) { return $output; } +function find_first_token_index( $tokens, $type, $search = null ) { + foreach ( $tokens as $i => $token ) { + if ( ! is_array( $token ) ) { + continue; + } + + if ( $type !== $token[0] ) { + continue; + } + + if ( null === $search || false !== strpos( $token[1], $search ) ) { + return $i; + } + } + return null; +} + $wp_config_path = getenv( "DOCROOT" ) . "/wp-config.php"; if ( ! file_exists( $wp_config_path ) ) { diff --git a/components/Blueprints/Tests/Unit/Steps/DefineConstantsStepTest.php b/components/Blueprints/Tests/Unit/Steps/DefineConstantsStepTest.php index 52101fbd..d40bfdf6 100644 --- a/components/Blueprints/Tests/Unit/Steps/DefineConstantsStepTest.php +++ b/components/Blueprints/Tests/Unit/Steps/DefineConstantsStepTest.php @@ -8,61 +8,143 @@ class DefineConstantsStepTest extends StepTestCase { /** - * Sample wp-config.php content for testing + * Test updating existing constants */ - const SAMPLE_WP_CONFIG = <<<'PHP' - true, + 'DB_NAME' => 'updated_db', + ]; + $step = new DefineConstantsStep( $constants ); + $step->run( $this->runtime, new Tracker() ); + $this->assertWordPressConstants( $constants ); + } + + /** + * Test adding new constants + */ + public function testAddNewConstants() { + $constants = [ + 'WP_MEMORY_LIMIT' => '256M', + 'AUTOMATIC_UPDATER_DISABLED' => true, + ]; + $step = new DefineConstantsStep( $constants ); + $step->run( $this->runtime, new Tracker() ); + $this->assertWordPressConstants( $constants ); + } + + public function testAddNewConstantsToEmptyWpConfig() { + $this->runtime->getTargetFilesystem()->put_contents( 'wp-config.php', ' '256M', + 'AUTOMATIC_UPDATER_DISABLED' => true, + ]; + $step = new DefineConstantsStep( $constants ); + $step->run( $this->runtime, new Tracker() ); + $this->assertWordPressConstants( $constants ); -// ** Database settings - You can get this info from your web host ** // -/** The name of the database for WordPress */ -define('DB_NAME', 'test_db'); + $this->assertSame( + "runtime->getTargetFilesystem()->get_contents( 'wp-config.php' ) + ); -/** Database username */ -define('DB_USER', 'root'); + $this->runtime->getTargetFilesystem()->rm( 'wp-config.php' ); + } -/** Database password */ -define('DB_PASSWORD', 'password'); + public function testAddNewConstantsToWpConfigWithEditingComment() { + $this->runtime->getTargetFilesystem()->put_contents( + 'wp-config.php', + <<<'PHP' + '256M', + 'AUTOMATIC_UPDATER_DISABLED' => true, + ]; + $step = new DefineConstantsStep( $constants ); + $step->run( $this->runtime, new Tracker() ); + $this->assertWordPressConstants( $constants ); -define('WP_DEBUG', false); + $this->assertSame( + <<<'PHP' +runtime->getTargetFilesystem()->get_contents( 'wp-config.php' ) + ); + } - /** - * Test updating existing constants - */ - public function testUpdateExistingConstants() { + public function testAddNewConstantsToWpConfigWithSetupWpSettingsComment() { + $this->runtime->getTargetFilesystem()->put_contents( + 'wp-config.php', + <<<'PHP' + true, - 'DB_NAME' => 'updated_db', + 'WP_MEMORY_LIMIT' => '256M', + 'AUTOMATIC_UPDATER_DISABLED' => true, ]; $step = new DefineConstantsStep( $constants ); $step->run( $this->runtime, new Tracker() ); $this->assertWordPressConstants( $constants ); + + $this->assertSame( + <<<'PHP' +runtime->getTargetFilesystem()->get_contents( 'wp-config.php' ) + ); } - /** - * Test adding new constants - */ - public function testAddNewConstants() { + public function testAddNewConstantsToWpConfigWithRequireWpSettings() { + $this->runtime->getTargetFilesystem()->put_contents( + 'wp-config.php', + <<<'PHP' + '256M', 'AUTOMATIC_UPDATER_DISABLED' => true, @@ -70,6 +152,35 @@ public function testAddNewConstants() { $step = new DefineConstantsStep( $constants ); $step->run( $this->runtime, new Tracker() ); $this->assertWordPressConstants( $constants ); + + $this->assertSame( + <<<'PHP' +runtime->getTargetFilesystem()->get_contents( 'wp-config.php' ) + ); + } + + public function testAddNewConstantsToInvalidWpConfig() { + $this->runtime->getTargetFilesystem()->put_contents( 'wp-config.php', '' ); + $constants = [ + 'WP_MEMORY_LIMIT' => '256M', + 'AUTOMATIC_UPDATER_DISABLED' => true, + ]; + + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'Blueprint Error: wp-config.php file is not a valid PHP file.' ); + + $step = new DefineConstantsStep( $constants ); + $step->run( $this->runtime, new Tracker() ); } /** @@ -139,7 +250,7 @@ private function assertWordPressConstants( array $expected_constants ) { $constants = json_decode(getenv('CONSTANTS'), true); foreach ($constants as $name => $expected_value) { -$results[$name] = defined($name) ? constant($name) : null; + $results[$name] = defined($name) ? constant($name) : null; } append_output( json_encode($results) ); From c43930c400264d4d6251b1cdcc7dfe932512b135 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 6 Aug 2025 08:04:24 +0200 Subject: [PATCH 4/5] Fix indentation --- .../Blueprints/Steps/DefineConstantsStep.php | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/components/Blueprints/Steps/DefineConstantsStep.php b/components/Blueprints/Steps/DefineConstantsStep.php index 3bf8e5a3..01de3095 100644 --- a/components/Blueprints/Steps/DefineConstantsStep.php +++ b/components/Blueprints/Steps/DefineConstantsStep.php @@ -314,35 +314,35 @@ function rewrite_wp_config_to_define_constants( $content, $constants = array() ) // Add any constants that weren't found in the file if ( count( $constants ) ) { - // First try to find the "That's all, stop editing!" comment. + // First try to find the "That's all, stop editing!" comment. $anchor = find_first_token_index( $output, T_COMMENT, "That's all, stop editing!" ); - // If not found, try the "Absolute path to the WordPress directory." doc comment. + // If not found, try the "Absolute path to the WordPress directory." doc comment. if ( null === $anchor ) { $anchor = find_first_token_index( $output, T_DOC_COMMENT, "Absolute path to the WordPress directory." ); } - // If not found, try the "Sets up WordPress vars and included files." doc comment. + // If not found, try the "Sets up WordPress vars and included files." doc comment. if ( null === $anchor ) { $anchor = find_first_token_index( $output, T_DOC_COMMENT, "Sets up WordPress vars and included files." ); } - // If not found, try "require_once ABSPATH . 'wp-settings.php';". - if ( null === $anchor ) { - $require_anchor = find_first_token_index( $output, T_REQUIRE_ONCE ); - if ( null !== $require_anchor ) { - $abspath = $output[$require_anchor + 2] ?? null; - $path = $output[$require_anchor + 6] ?? null; - if ( - ( is_array( $abspath ) && $abspath[1] === 'ABSPATH' ) - && ( is_array( $path ) && $path[1] === "'wp-settings.php'" ) - ) { - $anchor = $require_anchor; - } - } - } - - // If not found, fall back to the PHP opening tag. + // If not found, try "require_once ABSPATH . 'wp-settings.php';". + if ( null === $anchor ) { + $require_anchor = find_first_token_index( $output, T_REQUIRE_ONCE ); + if ( null !== $require_anchor ) { + $abspath = $output[$require_anchor + 2] ?? null; + $path = $output[$require_anchor + 6] ?? null; + if ( + ( is_array( $abspath ) && $abspath[1] === 'ABSPATH' ) + && ( is_array( $path ) && $path[1] === "'wp-settings.php'" ) + ) { + $anchor = $require_anchor; + } + } + } + + // If not found, fall back to the PHP opening tag. if ( null === $anchor ) { $open_tag_anchor = find_first_token_index( $output, T_OPEN_TAG ); if ( null !== $open_tag_anchor ) { @@ -351,21 +351,21 @@ function rewrite_wp_config_to_define_constants( $content, $constants = array() ) } // If we still don't have an anchor, the file is not a valid PHP file. - if ( null === $anchor ) { - error_log( "Blueprint Error: wp-config.php file is not a valid PHP file." ); - exit( 1 ); - } + if ( null === $anchor ) { + error_log( "Blueprint Error: wp-config.php file is not a valid PHP file." ); + exit( 1 ); + } // Ensure surrounding newlines when not already present. - $prev = $output[ $anchor - 1 ] ?? null; - $prev = is_array( $prev ) ? $prev[1] : $prev; - $next = $output[ $anchor ] ?? null; - $next = is_array( $next ) ? $next[1] : $next; + $prev = $output[ $anchor - 1 ] ?? null; + $prev = is_array( $prev ) ? $prev[1] : $prev; + $next = $output[ $anchor ] ?? null; + $next = is_array( $next ) ? $next[1] : $next; - $no_prefix = $prev && "\n\n" === substr( $prev, -2 ); - $no_suffix = $next && "\n\n" === substr( $next, 0, 2 ); + $no_prefix = $prev && "\n\n" === substr( $prev, -2 ); + $no_suffix = $next && "\n\n" === substr( $next, 0, 2 ); - // Add the new constants. + // Add the new constants. $new_constants = array( "\n" ); foreach ( $constants as $name => $path ) { $new_constants[] = 'define( '; @@ -374,7 +374,7 @@ function rewrite_wp_config_to_define_constants( $content, $constants = array() ) $new_constants[] = var_export( $path, true ); $new_constants[] = " );\n"; } - $new_constants[] = "\n"; + $new_constants[] = "\n"; $output = array_merge( array_slice( $output, 0, $anchor ), From 141e1bd2f86cc00c9a1328a5511150ee3666def4 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 6 Aug 2025 13:08:15 +0200 Subject: [PATCH 5/5] Fix tests for PHP 7.2 and 7.3 PHP 7.2 and 7.3 tokenize "runtime->getTargetFilesystem()->put_contents( 'wp-config.php', 'runtime->getTargetFilesystem()->put_contents( 'wp-config.php', " '256M', 'AUTOMATIC_UPDATER_DISABLED' => true, @@ -44,7 +44,7 @@ public function testAddNewConstantsToEmptyWpConfig() { $this->assertWordPressConstants( $constants ); $this->assertSame( - "runtime->getTargetFilesystem()->get_contents( 'wp-config.php' ) );