Skip to content
Draft
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
16 changes: 16 additions & 0 deletions features/config-create.feature
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,22 @@ Feature: Create a wp-config file
my\password
"""

Scenario: DB charset values with special characters are escaped
Given an empty directory
And WP files

When I run `wp config create --skip-check --dbname=somedb --dbuser=someuser --dbpass=somepassword --dbcharset="utf8mb4'latin1\legacy"`
Then the wp-config.php file should contain:
"""
define( 'DB_CHARSET', 'utf8mb4\'latin1\\legacy' )
"""

When I run `wp config get DB_CHARSET`
Then STDOUT should be:
"""
utf8mb4'latin1\legacy
"""

Scenario: wp-config.php in parent folder should not prevent config create in subfolder
Given an empty directory
And a wp-config.php file:
Expand Down
163 changes: 128 additions & 35 deletions src/Config_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ private static function get_initial_locale() {
* Success: Generated 'wp-config.php' file.
*/
public function create( $_, $assoc_args ) {
$provided_assoc_args = $assoc_args;

if ( ! Utils\get_flag_value( $assoc_args, 'force' ) ) {
if ( isset( $assoc_args['config-file'] ) && file_exists( $assoc_args['config-file'] ) ) {
$this->config_file_already_exist_error( basename( $assoc_args['config-file'] ) );
Expand Down Expand Up @@ -304,26 +306,144 @@ public function create( $_, $assoc_args ) {
}
}

foreach ( $assoc_args as $key => $value ) {
$assoc_args[ $key ] = $this->escape_config_value( $key, $value );
$template_args = array_merge(
$defaults,
[
'keys-and-salts' => false,
'keys-and-salts-alt' => '',
'extra-php' => '',
]
);

if ( ! empty( $assoc_args['keys-and-salts'] ) ) {
$template_args['keys-and-salts'] = true;
$template_args['auth-key'] = '';
$template_args['secure-auth-key'] = '';
$template_args['logged-in-key'] = '';
$template_args['nonce-key'] = '';
$template_args['auth-salt'] = '';
$template_args['secure-auth-salt'] = '';
$template_args['logged-in-salt'] = '';
$template_args['nonce-salt'] = '';
$template_args['wp-cache-key-salt'] = '';
} elseif ( ! empty( $assoc_args['keys-and-salts-alt'] ) ) {
$template_args['keys-and-salts-alt'] = $assoc_args['keys-and-salts-alt'];
}

// 'extra-php' from STDIN is retrieved after escaping to avoid breaking
// the PHP code.
if ( Utils\get_flag_value( $assoc_args, 'extra-php' ) === true ) {
$assoc_args['extra-php'] = file_get_contents( 'php://stdin' );
$template_args['extra-php'] = file_get_contents( 'php://stdin' );
}

$command_root = Path::phar_safe( dirname( __DIR__ ) );
$out = Utils\mustache_render( "{$command_root}/templates/wp-config.mustache", $assoc_args );
$out = Utils\mustache_render( "{$command_root}/templates/wp-config.mustache", $template_args );

$wp_config_file_name = basename( $assoc_args['config-file'] );
$created_config_file = ! file_exists( $assoc_args['config-file'] );
$bytes_written = file_put_contents( $assoc_args['config-file'], $out );
if ( ! $bytes_written ) {
WP_CLI::error( "Could not create new '{$wp_config_file_name}' file." );
} else {
WP_CLI::success( "Generated '{$wp_config_file_name}' file." );
}

try {
$config_transformer = new WPConfigTransformer( $assoc_args['config-file'] );

$value_map = [
'dbname' => [
'type' => 'constant',
'name' => 'DB_NAME',
'anchor' => '/** Database username */',
],
'dbuser' => [
'type' => 'constant',
'name' => 'DB_USER',
'anchor' => '/** Database password */',
],
'dbpass' => [
'type' => 'constant',
'name' => 'DB_PASSWORD',
'anchor' => '/** Database hostname */',
],
'dbhost' => [
'type' => 'constant',
'name' => 'DB_HOST',
'anchor' => '/** Database charset to use in creating database tables. */',
],
'dbcharset' => [
'type' => 'constant',
'name' => 'DB_CHARSET',
'anchor' => '/** The database collate type. Don\'t change this if in doubt. */',
],
'dbcollate' => [
'type' => 'constant',
'name' => 'DB_COLLATE',
'anchor' => '/**#@+',
],
'dbprefix' => [
'type' => 'variable',
'name' => 'table_prefix',
'anchor' => '/* Add any custom values between this line and the "stop editing" line. */',
],
];

foreach ( $value_map as $arg_name => $entry ) {
if ( ! array_key_exists( $arg_name, $provided_assoc_args ) ) {
continue;
}

if ( is_string( $provided_assoc_args[ $arg_name ] ) && false !== strpos( $provided_assoc_args[ $arg_name ], '\\' ) ) {
// Use remove+add to preserve backslash escaping when writing with WPConfigTransformer.
$config_transformer->remove( $entry['type'], $entry['name'] );
$config_transformer->add(
$entry['type'],
$entry['name'],
$provided_assoc_args[ $arg_name ],
[
'anchor' => $entry['anchor'],
'placement' => 'before',
]
);
continue;
}

$config_transformer->update(
$entry['type'],
$entry['name'],
$provided_assoc_args[ $arg_name ],
[ 'add' => true ]
);
}

if ( ! empty( $assoc_args['keys-and-salts'] ) ) {
$salt_map = [
'AUTH_KEY' => 'auth-key',
'SECURE_AUTH_KEY' => 'secure-auth-key',
'LOGGED_IN_KEY' => 'logged-in-key',
'NONCE_KEY' => 'nonce-key',
'AUTH_SALT' => 'auth-salt',
'SECURE_AUTH_SALT' => 'secure-auth-salt',
'LOGGED_IN_SALT' => 'logged-in-salt',
'NONCE_SALT' => 'nonce-salt',
'WP_CACHE_KEY_SALT' => 'wp-cache-key-salt',
];

foreach ( $salt_map as $name => $arg_name ) {
if ( ! array_key_exists( $arg_name, $assoc_args ) ) {
continue;
}
$config_transformer->update( 'constant', $name, $assoc_args[ $arg_name ], [ 'add' => true ] );
}
}
} catch ( Throwable $exception ) {
$cleanup_error = '';
if ( $created_config_file && file_exists( $assoc_args['config-file'] ) ) {
if ( ! unlink( $assoc_args['config-file'] ) ) {
$cleanup_error = "\nCleanup: Could not remove '{$wp_config_file_name}' after failure.";
}
}
WP_CLI::error( "Could not process the '{$wp_config_file_name}' transformation.\nReason: {$exception->getMessage()}{$cleanup_error}" );
}

WP_CLI::success( "Generated '{$wp_config_file_name}' file." );
}

/**
Expand Down Expand Up @@ -1521,31 +1641,4 @@ private static function is_sqlite_integration_active() {
$db_dropin_contents
);
}

/**
* Escape a config value so it can be safely used within single quotes.
*
* @param string $key Key into the arguments array.
* @param mixed $value Value to escape.
* @return mixed Escaped value.
*/
private function escape_config_value( $key, $value ) {
// Skip 'extra-php', it mustn't be escaped.
if ( 'extra-php' === $key ) {
return $value;
}

// Skip 'keys-and-salts-alt' and assume they are safe.
if ( 'keys-and-salts-alt' === $key && ! empty( $value ) ) {
return $value;
}

if ( is_string( $value ) ) {
$value = str_replace( '\\', '\\\\', $value ); // Escape backslashes first
$value = str_replace( "'", "\\'", $value ); // Then escape single quotes
return $value;
}

return $value;
}
}
Loading