Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions apps/cli/lib/native-php/site-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ $constants = json_decode( $argv[3] ?? '', true );
require_once $transformer_path;

$transformer = WP_Config_Transformer::from_file( $wp_config_path );
if (
$transformer->constant_exists( 'DB_NAME' )
&& ! $transformer->constant_equals( 'DB_NAME', 'database_name_here' )
Comment thread
atirna marked this conversation as resolved.
Outdated
) {
unset( $constants['DB_NAME'] );
}
$transformer->define_constants( $constants );
$transformer->to_file( $wp_config_path );
`;
Expand Down
91 changes: 91 additions & 0 deletions apps/cli/lib/native-php/tests/site-setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ensureWpConfig } from '../site-setup';

const { ensurePhpBinaryAvailable, runPhpCommand } = vi.hoisted( () => ( {
ensurePhpBinaryAvailable: vi.fn( async () => undefined ),
runPhpCommand: vi.fn(),
} ) );

vi.mock( '../../dependency-management/php-binary', () => ( {
ensurePhpBinaryAvailable,
} ) );

vi.mock( '../php-process', () => ( {
runPhpCommand,
} ) );

const PHP_VERSION = '8.4' as Parameters< typeof ensureWpConfig >[ 1 ];
const TRANSFORMER_PATH = path.resolve(
import.meta.dirname,
'../../../php/wp-config-transformer.php'
);

function phpAvailable(): boolean {
try {
execFileSync( 'php', [ '--version' ], { stdio: 'ignore' } );
return true;
} catch {
return false;
}
}

describe.skipIf( ! phpAvailable() )( 'ensureWpConfig', () => {
let tmpDir: string;

beforeEach( () => {
tmpDir = fs.mkdtempSync( path.join( os.tmpdir(), 'studio-site-setup-' ) );
ensurePhpBinaryAvailable.mockClear();
runPhpCommand.mockImplementation( async ( args: string[] ) => {
const [ , script, , wpConfigPath, constants ] = args;
const runnerPath = path.join( tmpDir, 'run-transformer.php' );
fs.writeFileSync( runnerPath, `<?php\n${ script }` );
execFileSync( 'php', [ runnerPath, TRANSFORMER_PATH, wpConfigPath, constants ], {
stdio: 'pipe',
} );
} );
} );

afterEach( () => {
fs.rmSync( tmpDir, { recursive: true, force: true } );
} );

it( 'preserves an external database name while updating debug constants', async () => {
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync(
wpConfigPath,
"<?php\ndefine( 'DB_NAME', 'database_demo' );\ndefine( 'WP_DEBUG', true );\n"
);

await ensureWpConfig( tmpDir, PHP_VERSION );

const contents = fs.readFileSync( wpConfigPath, 'utf8' );
expect( contents ).toContain( "define( 'DB_NAME', 'database_demo' );" );
expect( contents ).toContain( "define( 'WP_DEBUG', false );" );
} );

it( 'replaces the WordPress sample database placeholder for local sites', async () => {
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync( wpConfigPath, "<?php\ndefine( 'DB_NAME', 'database_name_here' );\n" );

await ensureWpConfig( tmpDir, PHP_VERSION );

expect( fs.readFileSync( wpConfigPath, 'utf8' ) ).toContain(
"define( 'DB_NAME', 'wordpress' );"
);
} );

it( 'ignores a commented-out external database name', async () => {
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync( wpConfigPath, "<?php\n// define( 'DB_NAME', 'database_demo' );\n" );

await ensureWpConfig( tmpDir, PHP_VERSION );

expect( fs.readFileSync( wpConfigPath, 'utf8' ) ).toContain(
"define( 'DB_NAME', 'wordpress' );"
);
} );
} );
73 changes: 59 additions & 14 deletions apps/cli/php/wp-config-transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ public function to_file( string $path ): void {
* @return bool True if the constant is defined, false otherwise.
*/
public function constant_exists( string $name ): bool {
return count( $this->collect_constant_definitions( $name ) ) > 0;
}

/**
* Locate every `define()` call for a constant.
*
* @param string $name The name of the constant.
* @return array Argument locations for each matching call, in source order.
*/
private function collect_constant_definitions( string $name ): array {
$definitions = array();
foreach ( $this->tokens as $i => $token ) {
$is_string_token = is_array( $token ) && T_STRING === $token[0];
if ( $is_string_token && 'define' === strtolower( $token[1] ) ) {
Expand All @@ -88,11 +99,30 @@ public function constant_exists( string $name ): bool {
array_slice( $this->tokens, $args[0][0], $args[0][1] )
);
if ( $name === $const_name ) {
return true;
$definitions[] = $args;
}
}
}
return false;
return $definitions;
}

/**
* Check whether a constant is defined with the given string value.
*
* @param string $name The name of the constant.
* @param string $value The expected value of the constant.
* @return bool True if the constant is defined with the expected value.
*/
public function constant_equals( string $name, string $value ): bool {
$definitions = $this->collect_constant_definitions( $name );
if ( 0 === count( $definitions ) ) {
return false;
}

$const_value = $this->evaluate_constant_value(
array_slice( $this->tokens, $definitions[0][1][0], $definitions[0][1][1] )
Comment thread
atirna marked this conversation as resolved.
Outdated
);
return $value === $const_value;
}

/**
Expand All @@ -119,18 +149,8 @@ public function define_constant( string $name, $value ): void {

// Collect all locations where the constant value needs to be updated.
$updates = array();
foreach ( $this->tokens as $i => $token ) {
$is_string_token = is_array( $token ) && T_STRING === $token[0];
if ( $is_string_token && 'define' === strtolower( $token[1] ) ) {
$args = $this->collect_function_call_argument_locations( $i );
$const_name = $this->evaluate_constant_name(
array_slice( $this->tokens, $args[0][0], $args[0][1] )
);

if ( $name === $const_name ) {
$updates[] = $args[1];
}
}
foreach ( $this->collect_constant_definitions( $name ) as $args ) {
$updates[] = $args[1];
}

// Modify the token array to define the constant. Apply updates in reverse
Expand Down Expand Up @@ -310,6 +330,31 @@ private function evaluate_constant_name( array $name_tokens ): ?string {
return eval( 'return ' . $name_token[1] . ';' );
}

/**
* Evaluate a string constant value from its tokens.
*
* @param array $value_tokens The tokens containing the constant value.
* @return string|null The evaluated value, or null when it is not a string literal.
*/
private function evaluate_constant_value( array $value_tokens ): ?string {
$value_token = null;
foreach ( $value_tokens as $token ) {
if ( $this->is_whitespace( $token ) ) {
continue;
}
if ( ! is_array( $token ) || T_CONSTANT_ENCAPSED_STRING !== $token[0] ) {
return null;
}
$value_token = $token;
}

if ( null === $value_token ) {
return null;
}

return eval( 'return ' . $value_token[1] . ';' );
}

/**
* Skip whitespace and comment tokens and return the location of the first
* non-whitespace and non-comment token after the specified start location.
Expand Down