false, // Display value in raw format without quotes. 'anchor' => $anchor_default, // Config placement anchor string (may vary by host). 'separator' => PHP_EOL, // Separator between config definition and anchor string. 'placement' => 'before', // Config placement direction (insert before or after). ); [ $raw, $anchor, $separator, $placement ] = array_values( array_merge( $defaults, $options ) ); $raw = (bool) $raw; $anchor = (string) $anchor; $separator = (string) $separator; $placement = (string) $placement; // Validate constant name against allowlist when using raw values. if ( $raw && 'constant' === $type && ! in_array( $name, self::$allowed_raw_constants, true ) ) { throw new \Exception( 'Constant "' . $name . '" is not in the allowed raw-value list.' ); } if ( self::ANCHOR_EOF === $anchor ) { $contents = self::$wp_config_src . self::normalize( $type, $name, self::format_value( $value, $raw ) ); } else { if ( false === strpos( self::$wp_config_src, $anchor ) ) { throw new \Exception( 'Unable to locate placement anchor.' ); } $new_src = self::normalize( $type, $name, self::format_value( $value, $raw ) ); $new_src = ( 'after' === $placement ) ? $anchor . $separator . $new_src : $new_src . $separator . $anchor; $contents = str_replace( $anchor, $new_src, self::$wp_config_src ); } return self::save( $contents ); } /** * Updates an existing config in the wp-config.php file. * * @throws \Exception If the config value provided is not a string. * * @param string $type Config type (constant or variable). * @param string $name Config name. * @param string $value Config value. * @param array $options (optional) Array of special behavior options. * * @return bool * * @since 1.1.0 */ public static function update( $type, $name, $value, array $options = array() ) { self::auto_init(); $value = var_export( $value, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export if ( ! is_string( $value ) ) { throw new \Exception( 'Config value must be a string.' ); } $defaults = array( 'add' => true, // Add the config if missing. 'raw' => false, // Display value in raw format without quotes. 'normalize' => false, // Normalize config output using WP Coding Standards. ); [ $add, $raw, $normalize ] = array_values( array_merge( $defaults, $options ) ); $add = (bool) $add; $raw = (bool) $raw; $normalize = (bool) $normalize; // Validate constant name against allowlist when using raw values. if ( $raw && 'constant' === $type && ! in_array( $name, self::$allowed_raw_constants, true ) ) { throw new \Exception( 'Constant "' . $name . '" is not in the allowed raw-value list.' ); } if ( ! self::exists( $type, $name ) ) { return ( $add ) ? self::add( $type, $name, $value, $options ) : false; } $old_src = self::$wp_configs[ $type ][ $name ]['src']; $old_value = self::$wp_configs[ $type ][ $name ]['value']; $new_value = self::format_value( $value, $raw ); if ( $normalize ) { $new_src = self::normalize( $type, $name, $new_value ); } else { $new_parts = self::$wp_configs[ $type ][ $name ]['parts']; $new_parts[1] = str_replace( $old_value, $new_value, $new_parts[1] ); // Only edit the value part. $new_src = implode( '', $new_parts ); } $contents = preg_replace( sprintf( '/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m', preg_quote( trim( $old_src ), '/' ) ), '$1' . str_replace( '$', '\$', trim( $new_src ) ), self::$wp_config_src ); return self::save( $contents ); } /** * Removes a config from the wp-config.php file. * * @param string $type Config type (constant or variable). * @param string $name Config name. * * @return bool * * @since 1.1.0 */ public static function remove( $type, $name ) { self::auto_init(); if ( ! self::exists( $type, $name ) ) { return false; } $pattern = sprintf( '/(?<=^|;|<\?php\s|<\?\s)%s\s*(\S|$)/m', preg_quote( self::$wp_configs[ $type ][ $name ]['src'], '/' ) ); $contents = preg_replace( $pattern, '$1', self::$wp_config_src ); return self::save( $contents ); } /** * Applies formatting to a config value. * * @throws \Exception When a raw value is requested for an empty string. * * @param string $value Config value. * @param bool $raw Display value in raw format without quotes. * * @return mixed * * @since 1.1.0 */ protected static function format_value( $value, $raw ) { if ( $raw && '' === trim( $value ) ) { throw new \Exception( 'Raw value for empty string not supported.' ); } return ( $raw ) ? $value : var_export( $value, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export } /** * Normalizes the source output for a name/value pair. * * @throws \Exception If the requested config type does not support normalization. * * @param string $type Config type (constant or variable). * @param string $name Config name. * @param mixed $value Config value. * * @return string * * @since 1.1.0 */ protected static function normalize( $type, $name, $value ) { if ( 'constant' === $type ) { $placeholder = "define( '%s', %s );"; } elseif ( 'variable' === $type ) { $placeholder = '$%s = %s;'; } else { throw new \Exception( "Unable to normalize config type '{$type}'." ); } return sprintf( $placeholder, $name, $value ); } /** * Detect if the current environment is WP Engine. * * Uses a few heuristics: known WP Engine constants, environment variables, * or the `$_SERVER['SERVER_SOFTWARE']` string. * @return bool True if WP Engine is detected, false otherwise. * * @since 1.2.0 */ protected static function is_wp_engine() { if ( defined( 'WPENGINE' ) || defined( 'WPE_APIKEY' ) || defined( 'WPE_CLUSTER' ) ) { return true; } if ( getenv( 'WPENGINE' ) || getenv( 'WPE_APIKEY' ) || getenv( 'IS_WPE' ) ) { return true; } $server_software = $_SERVER['SERVER_SOFTWARE'] ?? ''; if ( $server_software && false !== stripos( $server_software, 'wp engine' ) ) { return true; } return false; } /** * Parses the source of a wp-config.php file. * * @param string $src Config file source. * * @return array * * @since 1.1.0 */ protected static function parse_wp_config( $src ) { self::auto_init(); $configs = array(); $configs['constant'] = array(); $configs['variable'] = array(); // Strip comments. foreach ( token_get_all( $src ) as $token ) { if ( in_array( $token[0], array( T_COMMENT, T_DOC_COMMENT ), true ) ) { if ( '//' === $token[1] ) { // For empty line comments, actually remove empty line comments instead of all double-slashes. // See: https://github.com/wp-cli/wp-config-transformer/issues/47 . $src = preg_replace( '/' . preg_quote( '//', '/' ) . '$/m', '', $src ); } else { $src = str_replace( $token[1], '', $src ); } } } preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)((?:,\s*(?:true|false)\s*)?\)\s*;)/ims', $src, $constants ); preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*\$(\w+)\s*=)(\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*;)/ims', $src, $variables ); if ( ! empty( $constants[0] ) && ! empty( $constants[1] ) && ! empty( $constants[2] ) && ! empty( $constants[3] ) && ! empty( $constants[4] ) && ! empty( $constants[5] ) ) { foreach ( $constants[2] as $index => $name ) { $configs['constant'][ $name ] = array( 'src' => $constants[0][ $index ], 'value' => $constants[4][ $index ], 'parts' => array( $constants[1][ $index ], $constants[3][ $index ], $constants[5][ $index ], ), ); } } if ( ! empty( $variables[0] ) && ! empty( $variables[1] ) && ! empty( $variables[2] ) && ! empty( $variables[3] ) && ! empty( $variables[4] ) ) { // Remove duplicate(s), last definition wins. $variables[2] = array_reverse( array_unique( array_reverse( $variables[2], true ) ), true ); foreach ( $variables[2] as $index => $name ) { $configs['variable'][ $name ] = array( 'src' => $variables[0][ $index ], 'value' => $variables[4][ $index ], 'parts' => array( $variables[1][ $index ], $variables[3][ $index ], ), ); } } return $configs; } /** * Saves new contents to the wp-config.php file. * * @throws \Exception If the config file content provided is empty. * @throws \Exception If there is a failure when saving the wp-config.php file. * * @param string $contents New config contents. * * @return bool * * @since 1.1.0 */ protected static function save( $contents ) { self::auto_init(); if ( ! trim( $contents ) ) { throw new \Exception( 'Cannot save the config file with empty contents.' ); } if ( $contents === self::$wp_config_src ) { return false; } $result = file_put_contents( self::$wp_config_path, $contents, LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents if ( false === $result ) { throw new \Exception( 'Failed to update the config file.' ); } return true; } } }