File: /wordpress/plugins/wp-cloud-client/beta/src/Handler/ConfigureMultisiteHandler.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Handler;
use VPlugins\WPCloudClient\Debug\WpConfigEditor;
use VPlugins\WPCloudClient\Support\Logger;
/**
* Writes WordPress multisite constants to wp-config.php in two phases.
*
* Action name: configure_multisite
*
* This action must be called twice by wp-cloud-manager for each multisite site:
*
* Phase "pre" — before network_install:
* Writes WP_ALLOW_MULTISITE and SUBDOMAIN_INSTALL only. Writing MULTISITE=true
* before the network tables exist causes every subsequent WP request to fatal
* (ms-settings.php queries wp_blogs which does not exist yet).
*
* Phase "post" — after network_install succeeds:
* Writes MULTISITE=true and the *_CURRENT_SITE constants. At this point the
* network tables exist and WP can safely load ms-settings.php.
*
* Required params (both phases):
* - phase (string) — "pre" or "post".
*
* DOMAIN_CURRENT_SITE is always written as $_SERVER['HTTP_HOST'] so the domain
* resolves dynamically at runtime — domain changes on the atomic require no
* wp-config.php edits.
*
* Optional params (pre phase):
* - subdomain_install (bool) — true for subdomain network (default: false).
*
* Optional params (post phase):
* - path (string) — Network base path (default: "/").
* - site_id (int) — SITE_ID_CURRENT_SITE (default: 1).
* - blog_id (int) — BLOG_ID_CURRENT_SITE (default: 1).
*
* Pre-phase response example:
* {
* "phase": "pre",
* "subdomain_install": false,
* "message": "WP_ALLOW_MULTISITE enabled. Call network_install next, then configure_multisite with phase=post."
* }
*
* Post-phase response example:
* {
* "phase": "post",
* "domain": "$_SERVER['HTTP_HOST']",
* "path": "/",
* "site_id": 1,
* "blog_id": 1,
* "message": "Multisite constants configured successfully."
* }
*/
final class ConfigureMultisiteHandler extends AbstractHandler {
/**
* Class constructor.
*
* @param WpConfigEditor $editor wp-config.php editor service.
* @param Logger $logger Logger instance.
*/
public function __construct(
private readonly WpConfigEditor $editor,
private readonly Logger $logger,
) {}
/**
* Return the action name.
*
* @return string Action name.
*/
public function action(): string {
return 'configure_multisite';
}
/**
* Write multisite constants to wp-config.php for the requested phase.
*
* @param array<string, mixed> $params Action parameters.
* @return array<string, mixed> Configuration result.
*
* @throws \InvalidArgumentException When phase is missing or invalid.
*/
public function execute( array $params ): array {
$this->requireParams( $params, 'phase' );
$phase = strtolower( trim( (string) $params['phase'] ) );
if ( ! in_array( $phase, [ 'pre', 'post' ], true ) ) {
throw new \InvalidArgumentException( "Invalid phase '{$phase}': must be 'pre' or 'post'." );
}
return 'pre' === $phase
? $this->executePre( $params )
: $this->executePost( $params );
}
/**
* Write WP_ALLOW_MULTISITE and SUBDOMAIN_INSTALL only.
*
* Must be called before network_install. Writing MULTISITE=true here would
* cause wp-settings.php to load ms-settings.php and query the not-yet-created
* multisite DB tables on every subsequent request, fatalling before any plugin
* handler can run — including network_install itself.
*
* @param array<string, mixed> $params Action parameters.
* @return array<string, mixed>
*
* @throws \InvalidArgumentException When subdomain_install is not a boolean.
* @throws \RuntimeException When wp-config.php cannot be written.
*/
private function executePre( array $params ): array {
if ( isset( $params['subdomain_install'] ) && ! is_bool( $params['subdomain_install'] ) ) {
throw new \InvalidArgumentException( "'subdomain_install' must be a boolean." );
}
$subdomain_install = (bool) ( $params['subdomain_install'] ?? false );
$this->logger->info(
'ConfigureMultisite pre: enabling WP_ALLOW_MULTISITE',
[ 'subdomain_install' => $subdomain_install ]
);
try {
$this->editor->applyConstants(
[
'WP_ALLOW_MULTISITE' => true,
'SUBDOMAIN_INSTALL' => $subdomain_install,
// Explicitly set MULTISITE=false so that a previous failed run which
// already wrote MULTISITE=true does not cause WordPress to load
// ms-settings.php (and query the not-yet-created wp_site table) on
// every subsequent request until network_install has been re-run.
'MULTISITE' => false,
]
);
} catch ( \RuntimeException $e ) {
$this->logger->error( 'ConfigureMultisite pre: failed to write wp-config.php', $e );
throw $e;
}
$this->logger->info( 'ConfigureMultisite pre: complete' );
return [
'phase' => 'pre',
'subdomain_install' => $subdomain_install,
'message' => 'WP_ALLOW_MULTISITE enabled. Call network_install next, then configure_multisite with phase=post.',
];
}
/**
* Write MULTISITE and the *_CURRENT_SITE constants.
*
* Must be called after network_install succeeds. At that point the multisite
* DB tables exist and subsequent requests can safely load ms-settings.php.
*
* @param array<string, mixed> $params Action parameters.
* @return array<string, mixed>
*
* @throws \InvalidArgumentException When path/id values are invalid.
* @throws \RuntimeException When wp-config.php cannot be written.
*/
private function executePost( array $params ): array {
$path = isset( $params['path'] ) ? (string) $params['path'] : '/';
$site_id = isset( $params['site_id'] ) ? (int) $params['site_id'] : 1;
$blog_id = isset( $params['blog_id'] ) ? (int) $params['blog_id'] : 1;
// Always write DOMAIN_CURRENT_SITE as a raw PHP expression so it resolves
// dynamically at runtime — domain changes require no wp-config.php edits.
$domain = '$_SERVER[\'HTTP_HOST\']';
if ( '' === $path || '/' !== $path[0] ) {
throw new \InvalidArgumentException( "'path' must begin with a forward slash." );
}
if ( $site_id < 1 ) {
throw new \InvalidArgumentException( "'site_id' must be a positive integer." );
}
if ( $blog_id < 1 ) {
throw new \InvalidArgumentException( "'blog_id' must be a positive integer." );
}
$this->logger->info(
'ConfigureMultisite post: writing multisite constants',
[
'domain' => $domain,
'path' => $path,
'site_id' => $site_id,
'blog_id' => $blog_id,
]
);
try {
$this->editor->applyConstants(
[
'MULTISITE' => true,
'DOMAIN_CURRENT_SITE' => $domain,
'PATH_CURRENT_SITE' => $path,
'SITE_ID_CURRENT_SITE' => $site_id,
'BLOG_ID_CURRENT_SITE' => $blog_id,
]
);
} catch ( \RuntimeException $e ) {
$this->logger->error( 'ConfigureMultisite post: failed to write wp-config.php', $e );
throw $e;
}
$this->logger->info( 'ConfigureMultisite post: complete', [ 'domain' => $domain ] );
return [
'phase' => 'post',
'domain' => $domain,
'path' => $path,
'site_id' => $site_id,
'blog_id' => $blog_id,
'message' => 'Multisite constants configured successfully.',
];
}
}