Skip to content

Improve wp-settings.php detection in runner #6038

@sclark3-godaddy

Description

@sclark3-godaddy

Bug Report

Describe the current, buggy behavior

Currently, you must have a require AND wp-settings.php on the same line.

  • They can use require or require_once
  • They can use parenthesis require_once('....'); or not require_once '....';
  • They can use ABSPATH require_once ABSPATH . 'wp-settings.php'; or not require_once 'wp-settings.php';
  • They can have any other whitespace or use single / double / combination of quotation

The logic for this happens in a very basic foreach loop to identify the require call in a single line:

$found_wp_settings = false;
$lines_to_run = [];
foreach ( $wp_config_code as $line ) {
if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) {
$found_wp_settings = true;
continue;
}
$lines_to_run[] = $line;
}
if ( ! $found_wp_settings ) {
WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' );
}

What breaks is when you use other valid forms of PHP to require the file. This may be due to typos or preference of a developer but they all still result in a working WordPress installation and admin area but broken WP-CLI commands.

Describe how other contributors can replicate this bug

  • Edit your wp-config.php file and in the wp-settings.php line at the bottom, change it to the below example
  • Run any WP-CLI command like wp db prefix or wp post list
  • See the error: Error: Strange wp-config.php file: wp-settings.php is not loaded directly.

wp-config.php change:

require_once
ABSPATH . 'wp-settings.php';

Also valid:

require_once
(ABSPATH . 'wp-settings.php');

Describe what you expect as the correct outcome

I would expect WP-CLI to work if the WP admin area is working.

Let us know what environment you are running this on

Encountered on actual customer sites on a large host

Provide a possible solution

Maybe just do one regex check across all of the wp-config.php code instead of requiring it to have one line. This is a bit complicated since the method WP_CLI\Runner::get_wp_config_code() itself intends to get the config code to run excluding that require.

But I'd assume something like this would be better:

	public function get_wp_config_code( $wp_config_path = '' ) {
		if ( empty( $wp_config_path ) ) {
			$wp_config_path = Utils\locate_wp_config();
		}

		$wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) );

		// Detect and strip byte-order marks (BOMs).
		// This code assumes they can only be found on the first line.
		foreach ( self::BYTE_ORDER_MARKS as $bom_name => $bom_sequence ) {
			WP_CLI::debug( "Looking for {$bom_name} BOM", 'bootstrap' );

			$length = strlen( $bom_sequence );

			while ( substr( $wp_config_code[0], 0, $length ) === $bom_sequence ) {
				WP_CLI::warning(
					"{$bom_name} byte-order mark (BOM) detected in wp-config.php file, stripping it for parsing."
				);

				$wp_config_code[0] = substr( $wp_config_code[0], $length );
			}
		}

		$wp_config_code = implode( "\n", $wp_config_code );

		preg_match_all( '/^\s*(require|require_once)\s*.+wp-settings\.php.+;[ \t]*$/m', $wp_config_code, $matches );

		if ( empty( $matches[0] ) ) {
			WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' );
		}

		$source = str_replace( $matches[0], '', $wp_config_code );
		$source = Utils\replace_path_consts( $source, $wp_config_path );
		return preg_replace( '|^\s*\<\?php\s*|', '', $source );
	}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions