File: //wordpress/plugins/wp-cloud-client/1.2.1/src/Debug/WpConfigEditor.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Debug;
/**
* Reads and writes WordPress constants in wp-config.php.
*
* SetConstant() toggles a boolean define(); preserves non-boolean values.
* ApplyConstants() does a batched read-modify-write for mixed types (bool/string/int);
* and replaces any existing define() regardless of value type.
* If a constant is absent it is inserted before the "That's all, stop editing!" marker,
* or before the require_once wp-settings.php line on minimal wp-config.php files that
* omit the standard marker (e.g. Automattic atomic platform).
*/
final class WpConfigEditor {
/**
* Absolute path to wp-config.php.
*
* @var string
*/
private string $config_path;
/**
* Class constructor.
*
* @param string $config_path Explicit path to wp-config.php; auto-detected when empty.
*
* @throws \RuntimeException If wp-config.php cannot be located.
*/
public function __construct( string $config_path = '' ) {
$this->config_path = '' !== $config_path ? $config_path : $this->findConfigPath();
}
/**
* Return the absolute path to the wp-config.php file being edited.
*
* @return string Absolute file path.
*/
public function getConfigPath(): string {
return $this->config_path;
}
/**
* Return current boolean values for the four debug constants.
*
* A null value means the constant is not defined in the file.
*
* @return array{WP_DEBUG: bool|null, WP_DEBUG_LOG: bool|null, WP_DEBUG_DISPLAY: bool|null, SCRIPT_DEBUG: bool|null}
*
* @throws \RuntimeException If the file cannot be read.
*/
public function getConstants(): array {
$content = $this->readFile();
return [
'WP_DEBUG' => $this->parseConstant( $content, 'WP_DEBUG' ),
'WP_DEBUG_LOG' => $this->parseConstant( $content, 'WP_DEBUG_LOG' ),
'WP_DEBUG_DISPLAY' => $this->parseConstant( $content, 'WP_DEBUG_DISPLAY' ),
'SCRIPT_DEBUG' => $this->parseConstant( $content, 'SCRIPT_DEBUG' ),
];
}
/**
* Set a single boolean constant in wp-config.php.
*
* Inserts a new define() if the constant is not already present.
* Non-boolean defines (e.g. WP_DEBUG_LOG = '/path/to/log') are preserved untouched.
*
* @param string $name Constant name (e.g. 'WP_DEBUG').
* @param bool $value Desired value.
*
* @throws \RuntimeException If the file cannot be read or written.
*/
public function setConstant( string $name, bool $value ): void {
$content = $this->readFile();
$value_str = $value ? 'true' : 'false';
if ( preg_match( $this->definePattern( $name ), $content ) ) {
// Existing boolean define — replace it.
$repl = "define( '{$name}', {$value_str} )";
$content = preg_replace_callback( $this->definePattern( $name ), static fn() => $repl, $content ) ?? $content;
} elseif ( preg_match( $this->existsPattern( $name ), $content ) ) {
// Non-boolean define exists (e.g. string path) — preserve it, do not insert a duplicate.
return;
} else {
$content = $this->insertDefine( $content, $name, $value_str );
}
$this->writeFile( $content );
}
/**
* Set a single string constant in wp-config.php.
*
* Replaces the existing define() regardless of its current value type,
* or inserts a new one if the constant is absent. The value is single-quoted.
*
* @param string $name Constant name (e.g. 'DOMAIN_CURRENT_SITE').
* @param string $value Desired string value.
*
* @throws \RuntimeException If the file cannot be read or written.
*/
public function setStringConstant( string $name, string $value ): void {
$this->setValueConstant( $name, "'" . addslashes( $value ) . "'" );
}
/**
* Set a single integer constant in wp-config.php.
*
* Replaces the existing define() regardless of its current value type,
* or inserts a new one if the constant is absent.
*
* @param string $name Constant name (e.g. 'SITE_ID_CURRENT_SITE').
* @param int $value Desired integer value.
*
* @throws \RuntimeException If the file cannot be read or written.
*/
public function setIntConstant( string $name, int $value ): void {
$this->setValueConstant( $name, (string) $value );
}
/**
* Apply multiple constants in a single read–modify–write cycle.
*
* Any existing define() for a given constant is replaced regardless of its
* current value type; absent constants are inserted before the stop-editing
* marker. A single file write prevents a partial-state window when several
* constants must be updated together.
*
* @param array<string, bool|string|int> $constants Map of constant name => value.
*
* @throws \RuntimeException If the file cannot be read or written.
*/
public function applyConstants( array $constants ): void {
$content = $this->readFile();
foreach ( $constants as $name => $value ) {
$value_str = $this->toValueStr( $value );
$content = $this->applyOne( $content, $name, $value_str );
}
$this->writeFile( $content );
}
/**
* Locate wp-config.php using standard WordPress path conventions.
*
* @return string Absolute path.
*
* @throws \RuntimeException If the file cannot be found.
*/
private function findConfigPath(): string {
$candidates = [
ABSPATH . 'wp-config.php',
dirname( ABSPATH ) . '/wp-config.php',
];
foreach ( $candidates as $path ) {
if ( file_exists( $path ) ) {
return $path;
}
}
throw new \RuntimeException( 'wp-config.php not found.' );
}
/**
* Read the config file contents.
*
* @return string File contents.
*
* @throws \RuntimeException If the file is not readable or read fails.
*/
private function readFile(): string {
if ( ! is_readable( $this->config_path ) ) {
throw new \RuntimeException( sprintf( 'Cannot read wp-config.php at %s.', $this->config_path ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
$content = file_get_contents( $this->config_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( false === $content ) {
throw new \RuntimeException( sprintf( 'Cannot read wp-config.php at %s.', $this->config_path ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $content;
}
/**
* Write content back to the config file.
*
* @param string $content New file content.
*
* @throws \RuntimeException If the write fails.
*/
private function writeFile( string $content ): void {
$result = file_put_contents( $this->config_path, $content, LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
if ( false === $result ) {
$err = error_get_last()['message'] ?? 'unknown error';
throw new \RuntimeException( sprintf( 'Cannot write wp-config.php at %s: %s', $this->config_path, $err ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
}
/**
* Write a raw define() value for $name, replacing any existing define() of any type.
*
* @param string $name Constant name.
* @param string $valueStr Ready-to-insert value string (e.g. "'example.com'" or "1").
*
* @throws \RuntimeException If the file cannot be read or written.
*/
private function setValueConstant( string $name, string $valueStr ): void {
$content = $this->readFile();
$content = $this->applyOne( $content, $name, $valueStr );
$this->writeFile( $content );
}
/**
* Convert a typed value to its PHP define() string representation.
*
* @param bool|string|int $value Value to convert.
* @return string Ready-to-insert value string.
*/
private function toValueStr( bool|string|int $value ): string {
if ( is_bool( $value ) ) {
return $value ? 'true' : 'false';
}
if ( is_int( $value ) ) {
return (string) $value;
}
// A string starting with '$' is a raw PHP expression (e.g. $_SERVER['HTTP_HOST']).
// Pass it through unquoted so wp-config.php evaluates it at runtime.
if ( str_starts_with( (string) $value, '$' ) ) {
return (string) $value;
}
return "'" . addslashes( $value ) . "'";
}
/**
* Apply a single constant to in-memory content without touching disk.
*
* Uses preg_replace_callback so the replacement is literal — avoids
* preg_replace back-reference interpretation of $N / \N in the value string.
*
* @param string $content Current file content.
* @param string $name Constant name.
* @param string $valueStr Ready-to-insert value string.
* @return string Updated content.
*/
private function applyOne( string $content, string $name, string $valueStr ): string {
$replacement = "define( '{$name}', {$valueStr} )";
if ( preg_match( $this->existsPattern( $name ), $content ) ) {
// Match the value explicitly by type so a ')' inside a string value cannot
// truncate the match and leave trailing junk in wp-config.php.
// The \$\S* branch handles raw PHP expressions such as $_SERVER['HTTP_HOST'].
return preg_replace_callback(
'/define\s*\(\s*[\'"]' . preg_quote( $name, '/' ) . '[\'"]\s*,\s*(?:true|false|-?\d+|\'[^\']*\'|"[^"]*"|\$\S*)\s*\)/i',
static fn() => $replacement,
$content
) ?? $content;
}
return $this->insertDefine( $content, $name, $valueStr );
}
/**
* Build a regex that matches a boolean define() call for the given constant name.
*
* @param string $name Constant name.
* @return string Regex pattern.
*/
private function definePattern( string $name ): string {
return '/define\s*\(\s*[\'"]' . preg_quote( $name, '/' ) . '[\'"]\s*,\s*(true|false)\s*\)/i';
}
/**
* Build a regex that detects any define() for the given constant name, regardless of value type.
*
* @param string $name Constant name.
* @return string Regex pattern.
*/
private function existsPattern( string $name ): string {
return '/define\s*\(\s*[\'"]' . preg_quote( $name, '/' ) . '[\'"]\s*,/';
}
/**
* Insert a define() before the standard WP stop-editing marker, or append at end.
*
* @param string $content Current file content.
* @param string $name Constant name.
* @param string $value_str Value string to insert.
* @return string Updated file content.
*/
private function insertDefine( string $content, string $name, string $value_str ): string {
$line = "define( '{$name}', {$value_str} );";
// Primary: standard WordPress stop-editing marker.
$marker = "/* That's all, stop editing!";
if ( str_contains( $content, $marker ) ) {
return str_replace( $marker, "{$line}\n{$marker}", $content );
}
// Fallback: insert before require_once wp-settings.php — covers minimal
// wp-config.php files (e.g. Automattic atomics) that omit the marker but
// always include this line. Constants must be defined before WordPress boots.
if ( preg_match( '/^(.*?)(require(?:_once)?\s*[\'"]?.*?wp-settings\.php[\'"]?\s*;)/ms', $content, $m ) ) {
return $m[1] . $line . "\n" . $m[2] . substr( $content, strlen( $m[1] ) + strlen( $m[2] ) );
}
// Last resort: append at end. May land after wp-settings.php require — constants
// will have no effect at runtime but avoids losing data on write.
return $content . "\n{$line}\n";
}
/**
* Parse a single boolean constant from file content.
*
* @param string $content File content.
* @param string $name Constant name.
* @return bool|null True/false if found, null if absent.
*/
private function parseConstant( string $content, string $name ): ?bool {
if ( preg_match( $this->definePattern( $name ), $content, $matches ) ) {
return 'true' === strtolower( $matches[1] );
}
return null;
}
}