Skip to content

[Blueprints v2] Add support for "enableMultisite" step #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 6, 2025
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
3 changes: 3 additions & 0 deletions components/Blueprints/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
100 changes: 82 additions & 18 deletions components/Blueprints/Steps/DefineConstantsStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"<?php \n",
);
foreach ( $constants as $name => $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." );
}

// 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 );
}
$prepend[] = '?>';
$output = array_merge(
$prepend,
$output

// 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 )
);
}

Expand Down Expand Up @@ -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 ) ) {
Expand Down
114 changes: 114 additions & 0 deletions components/Blueprints/Steps/EnableMultisiteStep.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace WordPress\Blueprints\Steps;

use Symfony\Component\Process\Exception\ProcessFailedException;
use WordPress\Blueprints\Exception\BlueprintExecutionException;
use WordPress\Blueprints\Progress\Tracker;
use WordPress\Blueprints\Runtime;

/**
* Represents the 'enableMultisite' step.
*/
class EnableMultisiteStep implements StepInterface {
public function run( Runtime $runtime, Tracker $tracker ) {
$tracker->setCaption( 'Enabling multisite' );

$code =
<<<'PHP'
<?php
/*
* This code is mirroring the "wp core multisite-convert" command behavior.
* See: https://github.com/wp-cli/core-command/blob/f157fb37dae1d13fe7318452f932917161e83e53/src/Core_Command.php#L505
*/

require_once getenv( 'DOCROOT' ) . '/wp-load.php';
require_once getenv( 'DOCROOT' ) . '/wp-admin/includes/upgrade.php';

// need to register the multisite tables manually for some reason
foreach ( $wpdb->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 );
}
}
Loading
Loading