HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: /wordpress/plugins/wp-cloud-client/1.1.0/src/Debug/WpConfigEditor.php
<?php

declare(strict_types=1);

namespace VPlugins\WPCloudClient\Debug;

/**
 * Reads and writes WordPress debug constants in wp-config.php.
 *
 * Only toggles boolean define() calls. Constants defined with non-boolean
 * values (e.g. WP_DEBUG_LOG = '/path/to/log') are detected so we never
 * insert a duplicate, but their existing value is preserved untouched.
 * If a constant is absent it is inserted before the
 * "That's all, stop editing!" marker.
 */
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.
	 *
	 * @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.
			$content = preg_replace( $this->definePattern( $name ), "define( '{$name}', {$value_str} )", $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 );
	}

	/**
	 * 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
		}
	}

	/**
	 * 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*\)/';
	}

	/**
	 * Build a regex that detects any define() for the given constant name, regardless of value type.
	 *
	 * Used to prevent inserting a duplicate define() when a non-boolean value already exists.
	 *
	 * @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 'true' or 'false'.
	 * @return string Updated file content.
	 */
	private function insertDefine( string $content, string $name, string $value_str ): string {
		$marker = "/* That's all, stop editing!";
		$line   = "define( '{$name}', {$value_str} );";

		if ( str_contains( $content, $marker ) ) {
			return str_replace( $marker, "{$line}\n{$marker}", $content );
		}

		// Marker missing (heavily customised wp-config.php). Appending at end may
		// land below the wp-settings.php require, in which case the define won't
		// affect WP runtime. Acceptable trade-off vs. failing the request.
		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;
	}
}