diff --git a/components/Blueprints/RunnerConfiguration.php b/components/Blueprints/RunnerConfiguration.php index f02a04ab3..4818834e0 100644 --- a/components/Blueprints/RunnerConfiguration.php +++ b/components/Blueprints/RunnerConfiguration.php @@ -32,6 +32,26 @@ class RunnerConfiguration { * @var string */ private $siteUrl = ''; + /** + * @var string + */ + private $installSiteTitle = 'WordPress Site'; + /** + * @var string + */ + private $installAdminUser = 'admin'; + /** + * @var string + */ + private $installAdminPassword = 'password'; + /** + * @var string + */ + private $installAdminEmail = 'admin@example.com'; + /** + * @var bool + */ + private $installSkipEmail = false; /** * @var string */ @@ -120,6 +140,56 @@ public function getTargetSiteUrl(): string { return $this->siteUrl; } + public function setInstallSiteTitle( string $t ): self { + $this->installSiteTitle = $t; + + return $this; + } + + public function getInstallSiteTitle(): string { + return $this->installSiteTitle; + } + + public function setInstallAdminUser( string $u ): self { + $this->installAdminUser = $u; + + return $this; + } + + public function getInstallAdminUser(): string { + return $this->installAdminUser; + } + + public function setInstallAdminPassword( string $p ): self { + $this->installAdminPassword = $p; + + return $this; + } + + public function getInstallAdminPassword(): string { + return $this->installAdminPassword; + } + + public function setInstallAdminEmail( string $e ): self { + $this->installAdminEmail = $e; + + return $this; + } + + public function getInstallAdminEmail(): string { + return $this->installAdminEmail; + } + + public function setInstallSkipEmail( bool $skip ): self { + $this->installSkipEmail = $skip; + + return $this; + } + + public function getInstallSkipEmail(): bool { + return $this->installSkipEmail; + } + /** * Sets the database engine. * diff --git a/components/Blueprints/Runtime.php b/components/Blueprints/Runtime.php index cdfa1419c..617b5ada0 100644 --- a/components/Blueprints/Runtime.php +++ b/components/Blueprints/Runtime.php @@ -253,7 +253,24 @@ public function createPhpSubProcess( // Still put the script in a temporary file as the path may be refering // to a file inside the currently executed .phar archive. $actual_script_path = wp_join_unix_paths( $tempDir, 'script.php' ); - $code = ''; + $code = ''; $code .= file_get_contents( $script_path ); file_put_contents( $actual_script_path, $code ); diff --git a/components/Blueprints/SiteResolver/NewSiteResolver.php b/components/Blueprints/SiteResolver/NewSiteResolver.php index d874ddcef..418bea8c2 100644 --- a/components/Blueprints/SiteResolver/NewSiteResolver.php +++ b/components/Blueprints/SiteResolver/NewSiteResolver.php @@ -11,6 +11,7 @@ use WordPress\HttpClient\Client; use WordPress\HttpClient\Request; use WordPress\Zip\ZipFilesystem; +use WordPress\Blueprints\SiteResolver\WordPressInstaller; use function WordPress\Filesystem\copy_between_filesystems; use function WordPress\Filesystem\wp_join_unix_paths; @@ -110,28 +111,16 @@ static public function resolve( Runtime $runtime, Tracker $progress, ?VersionCon } } - // Perform installation using WP-CLI - // @TODO (low priority): Remove the WP-CLI dependency to lower the download size for blueprints.phar. - $progress['install_wordpress']->set( 0.7, 'Installing WordPress' ); - $wp_cli_path = $runtime->getWpCliPath(); - $process = $runtime->startShellCommand( [ - 'php', - $wp_cli_path, - 'core', - 'install', - '--path=' . $runtime->getConfiguration()->getTargetSiteRoot(), - - // For Docker compatibility. If we got this far, Blueprint runner was already - // allowed to run as root. - '--allow-root', - '--url=' . $runtime->getConfiguration()->getTargetSiteUrl(), - '--title=WordPress Site', - '--admin_user=admin', - '--admin_password=password', - '--admin_email=admin@example.com', - '--skip-email', + // Perform core installation without WP-CLI by invoking WordPressInternals + $installer = new WordPressInstaller(); + $installer->install( $runtime, $progress['install_wordpress'], [ + 'site_url' => $runtime->getConfiguration()->getTargetSiteUrl(), + 'title' => $runtime->getConfiguration()->getInstallSiteTitle(), + 'admin_user' => $runtime->getConfiguration()->getInstallAdminUser(), + 'admin_password' => $runtime->getConfiguration()->getInstallAdminPassword(), + 'admin_email' => $runtime->getConfiguration()->getInstallAdminEmail(), + 'skip_email' => $runtime->getConfiguration()->getInstallSkipEmail(), ] ); - $process->mustRun(); if ( ! self::isWordPressInstalled( $runtime, $progress ) ) { // @TODO: This breaks in Playground CLI diff --git a/components/Blueprints/SiteResolver/WordPressInstaller.php b/components/Blueprints/SiteResolver/WordPressInstaller.php new file mode 100644 index 000000000..b490c049a --- /dev/null +++ b/components/Blueprints/SiteResolver/WordPressInstaller.php @@ -0,0 +1,181 @@ + $runtime->getConfiguration()->getTargetSiteUrl() + * - 'title' => 'WordPress Site' + * - 'admin_user' => 'admin' + * - 'admin_password'=> 'password' + * - 'admin_email' => 'admin@example.com' + * - 'skip_email' => true + */ + public function install( Runtime $runtime, Tracker $tracker, array $options = [] ): void { + $targetFs = $runtime->getTargetFilesystem(); + $tracker->set( 0.65, 'Preparing WordPress installation' ); + + // Ensure wp-config.php exists + if ( ! $targetFs->exists( '/wp-config.php' ) ) { + if ( $targetFs->exists( 'wp-config-sample.php' ) ) { + $targetFs->copy( 'wp-config-sample.php', 'wp-config.php' ); + } else { + throw new BlueprintExecutionException( 'Neither wp-config.php, nor wp-config-sample.php was found in the WordPress archive.' ); + } + } + + // Define DB constants according to configuration + $dbEngine = $runtime->getConfiguration()->getDatabaseEngine(); + $dbCreds = $runtime->getConfiguration()->getDatabaseCredentials(); + $constants = []; + if ( $dbEngine === 'mysql' ) { + $constants = [ + 'DB_NAME' => $dbCreds['databaseName'] ?? 'wordpress', + 'DB_USER' => $dbCreds['username'] ?? 'root', + 'DB_PASSWORD' => $dbCreds['password'] ?? '', + 'DB_HOST' => $dbCreds['host'] ?? '127.0.0.1', + ]; + } elseif ( $dbEngine === 'sqlite' ) { + // Prefer canonical default used elsewhere in the runner + $dbPath = $dbCreds['path'] ?? 'wp-content/.ht.sqlite'; + if ($dbPath === '' || $dbPath === null) { + $dbPath = 'wp-content/.ht.sqlite'; + } + $constants = [ 'DB_NAME' => $dbPath ]; + + // Pre-create the database directory to avoid cross‑platform path issues + $targetFs = $runtime->getTargetFilesystem(); + $relativeDbPath = '/' . ltrim( $dbPath, '/' ); + $dbDir = dirname( $relativeDbPath ); + if ( ! $targetFs->is_dir( $dbDir ) ) { + $targetFs->mkdir( $dbDir, 0755, true ); + } + // Best effort to ensure file exists and is writable (SQLite will create as needed) + if ( ! $targetFs->exists( $relativeDbPath ) ) { + try { $targetFs->put_contents( $relativeDbPath, '' ); } catch ( \Throwable $e ) { /* ignore */ } + } + + // Ensure SQLite extension availability for clearer errors on macOS/Windows + if ( ! extension_loaded('sqlite3') && ! extension_loaded('pdo_sqlite') ) { + throw new BlueprintExecutionException( + 'SQLite database engine selected, but neither sqlite3 nor pdo_sqlite PHP extension is loaded. ' + . 'Enable one of these extensions or switch --db-engine to mysql.' + ); + } + } + if ( ! empty( $constants ) ) { + (new DefineConstantsStep( $constants ))->run( $runtime, $tracker ); + } + + // Prepare installation options + $siteUrl = $options['site_url'] ?? $runtime->getConfiguration()->getTargetSiteUrl(); + $title = $options['title'] ?? 'WordPress Site'; + $adminUser = $options['admin_user'] ?? 'admin'; + $adminPass = $options['admin_password'] ?? 'password'; + $adminEmail = $options['admin_email'] ?? 'admin@example.com'; + $skipEmail = (bool) ( $options['skip_email'] ?? true ); + + $tracker->set( 0.7, 'Installing WordPress' ); + $runtime->evalPhpCodeInSubProcess( + <<<'PHP' + $runtime->getConfiguration()->getTargetSiteRoot(), + 'SITE_URL' => $siteUrl, + 'TITLE' => $title, + 'ADMIN_USER' => $adminUser, + 'ADMIN_PASS' => $adminPass, + 'ADMIN_EMAIL' => $adminEmail, + 'SKIP_EMAIL' => $skipEmail ? '1' : '0', + ] + ); + } +} + + diff --git a/components/Blueprints/bin/blueprint.php b/components/Blueprints/bin/blueprint.php index 98a0925a2..d03fe9885 100644 --- a/components/Blueprints/bin/blueprint.php +++ b/components/Blueprints/bin/blueprint.php @@ -263,6 +263,12 @@ function createProgressReporter(): ProgressReporter { 'db-pass' => [ null, true, '', 'MySQL password' ], 'db-name' => [ null, true, 'wordpress', 'MySQL database' ], 'db-path' => [ 'p', true, 'wp.db', 'SQLite file path' ], + // WordPress install options (used when creating a new site) + 'wp-title' => [ null, true, 'WordPress Site', 'Site title used during installation' ], + 'wp-admin-user' => [ null, true, 'admin', 'Administrator username used during installation' ], + 'wp-admin-pass' => [ null, true, 'password', 'Administrator password used during installation' ], + 'wp-admin-email' => [ null, true, 'admin@example.com', 'Administrator email used during installation' ], + 'wp-skip-email' => [ null, false, false, 'Skip sending emails during installation' ], 'truncate-new-site-directory' => [ 't', false, false, 'Delete target directory if it exists before execution' ], /** * @TODO: Reuse this error message removed from the Playground repo: @@ -451,6 +457,23 @@ function cliArgsToRunnerConfiguration( array $positionalArgs, array $options ): $config->setTargetSiteRoot( $absoluteTargetSiteRoot ); $config->setTargetSiteUrl( $options['site-url'] ); + // Install options + if ( ! empty( $options['wp-title'] ) ) { + $config->setInstallSiteTitle( $options['wp-title'] ); + } + if ( ! empty( $options['wp-admin-user'] ) ) { + $config->setInstallAdminUser( $options['wp-admin-user'] ); + } + if ( ! empty( $options['wp-admin-pass'] ) ) { + $config->setInstallAdminPassword( $options['wp-admin-pass'] ); + } + if ( ! empty( $options['wp-admin-email'] ) ) { + $config->setInstallAdminEmail( $options['wp-admin-email'] ); + } + if ( ! empty( $options['wp-skip-email'] ) ) { + $config->setInstallSkipEmail( true ); + } + // Set database engine if ( ! empty( $options['db-engine'] ) ) { $config->setDatabaseEngine( $options['db-engine'] );