├── .actrc ├── .editorconfig ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json └── src └── WPConfigTransformer.php /.actrc: -------------------------------------------------------------------------------- 1 | # Configuration file for nektos/act. 2 | # See https://github.com/nektos/act#configuration 3 | -P ubuntu-latest=shivammathur/node:latest 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | # From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | indent_style = tab 17 | 18 | [{*.yml,*.feature,.jshintrc,*.json}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [{*.txt,wp-config-sample.php}] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We appreciate you taking the initiative to contribute to this project. 5 | 6 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 7 | 8 | Read through our [contributing guidelines in the handbook](https://make.wordpress.org/cli/handbook/contributing/) for a thorough introduction to how you can get involved. Following these guidelines helps to communicate that you respect the time of other contributors on the project. In turn, they’ll do their best to reciprocate that respect when working with you, across timezones and around the world. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2011-2018 WP-CLI Development Group (https://github.com/wp-cli/wp-config-transformer/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Config Transformer 2 | 3 | Programmatically edit a `wp-config.php` file. 4 | 5 | [![Testing](https://github.com/wp-cli/wp-config-transformer/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/wp-config-transformer/actions/workflows/testing.yml) 6 | 7 | Quick links: [Using](#using) | [Options](#options) | [How it works](#how-it-works) | [Testing](#testing) 8 | 9 | ## Using 10 | 11 | ### Instantiate 12 | 13 | ```php 14 | $config_transformer = new WPConfigTransformer( '/path/to/wp-config.php' ); 15 | ``` 16 | 17 | ### Edit constants 18 | 19 | ```php 20 | $config_transformer->update( 'constant', 'WP_DEBUG', 'true', array( 'raw' => true ) ); 21 | $config_transformer->add( 'constant', 'MY_SPECIAL_CONFIG', 'foo' ); 22 | $config_transformer->remove( 'constant', 'MY_SPECIAL_CONFIG' ); 23 | ``` 24 | 25 | ### Edit variables 26 | 27 | ```php 28 | $config_transformer->update( 'variable', 'table_prefix', 'wp_custom_' ); 29 | $config_transformer->add( 'variable', 'my_special_global', 'foo' ); 30 | $config_transformer->remove( 'variable', 'my_special_global' ); 31 | ``` 32 | 33 | ### Check for existence 34 | 35 | ```php 36 | if ( $config_transformer->exists( 'constant', 'MY_SPECIAL_CONFIG' ) ) { 37 | // do stuff 38 | } 39 | 40 | if ( $config_transformer->exists( 'variable', 'my_special_global' ) ) { 41 | // do stuff 42 | } 43 | ``` 44 | 45 | ## Options 46 | 47 | Special behaviors when adding or updating configs are available using the options array. 48 | 49 | ### Normalization 50 | 51 | In contrast to the "edit in place" strategy above, there is the option to normalize the output during a config update and effectively replace the existing syntax with output that adheres to WP Coding Standards. 52 | 53 | Let's reconsider a poorly-formatted example: 54 | 55 | ```php 56 | define ( 'WP_DEBUG' , 57 | false, false ) 58 | ; 59 | ``` 60 | 61 | This time running: 62 | 63 | ```php 64 | $config_transformer->update( 'constant', 'WP_DEBUG', 'true', array( 'raw' => true, 'normalize' => true ) ); 65 | ``` 66 | 67 | Now we will get an output of: 68 | 69 | ```php 70 | define( 'WP_DEBUG', true ); 71 | ``` 72 | 73 | Nice! 74 | 75 | ### Raw format 76 | 77 | Suppose you want to change your `ABSPATH` config _(gasp!)_. To do that, we can run: 78 | 79 | ```php 80 | $config_transformer->update( 'constant', 'ABSPATH', "dirname( __FILE__ ) . '/somewhere/else/'", array( 'raw' => true ) ); 81 | ``` 82 | 83 | The `raw` option means that instead of placing the value inside the config as a string `"dirname( __FILE__ ) . '/somewhere/else/'"` it will become unquoted (and executable) syntax `dirname( __FILE__ ) . '/somewhere/else/'`. 84 | 85 | ### Anchor string 86 | 87 | The anchor string is the piece of text that additions will be anchored to. 88 | 89 | ```php 90 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'anchor' => '/** Absolute path to the WordPress directory' ) ); // Default 91 | ``` 92 | 93 | ### Anchor placement 94 | 95 | By default, new configs will be placed before the anchor string. 96 | 97 | ```php 98 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'placement' => 'before' ) ); // Default 99 | $config_transformer->update( 'constant', 'BAZ', 'qux', array( 'placement' => 'after' ) ); 100 | ``` 101 | 102 | ### Anchor separator 103 | 104 | By default, the separator between a new config and its anchor string is an EOL ("\n" on *nix and "\r\n" on Windows). 105 | 106 | ```php 107 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'separator' => PHP_EOL . PHP_EOL ) ); // Default 108 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'separator' => PHP_EOL ) ); 109 | ``` 110 | 111 | ### Add if missing 112 | 113 | By default, when attempting to update a config that doesn't exist, one will be added. This behavior can be overridden by specifying the `add` option and setting it to `false`. 114 | 115 | ```php 116 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'add' => true ) ); // Default 117 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'add' => false ) ); 118 | ``` 119 | 120 | If the constant `FOO` exists, it will be updated in-place. And if not, the update will return `false`: 121 | 122 | ```php 123 | $config_transformer->exists( 'constant', 'FOO' ); // Returns false 124 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'add' => false ) ); // Returns false 125 | ``` 126 | 127 | ## How it works 128 | 129 | ### Parsing configs 130 | 131 | Constants: https://regex101.com/r/6AeNGP/4 132 | 133 | Variables: https://regex101.com/r/cSLZZz/4 134 | 135 | ### Editing in place 136 | 137 | Due to the unsemantic nature of the `wp-config.php` file, and PHP's loose syntax in general, the WP Config Transformer takes an "edit in place" strategy in order to preserve the original formatting and whatever other obscurities may be taking place in the block. After all, we only care about transforming values, not constant or variable names. 138 | 139 | To achieve this, the following steps are performed: 140 | 141 | 1. A PHP block containing a config is split into distinct parts. 142 | 2. Only the part containing the config value is targeted for replacement. 143 | 3. The parts are reassembled with the new value in place. 144 | 4. The old PHP block is replaced with the new PHP block. 145 | 146 | Consider the following horrifically-valid PHP block, that also happens to be using the optional (and rare) 3rd argument for constant case-sensitivity: 147 | 148 | ```php 149 | define ( 'WP_DEBUG' , 150 | false, false ) 151 | ; 152 | ``` 153 | 154 | The "edit in place" strategy means that running: 155 | 156 | ```php 157 | $config_transformer->update( 'constant', 'WP_DEBUG', 'true', array( 'raw' => true ) ); 158 | ``` 159 | 160 | Will give us a result that safely changes _only_ the value, leaving the formatting and additional argument(s) unscathed: 161 | 162 | ```php 163 | define ( 'WP_DEBUG' , 164 | true, false ) 165 | ; 166 | ``` 167 | 168 | ### Option forwarding 169 | 170 | Any option supported by the `add()` method can also be passed through the `update()` method and forwarded along when the config does not exist. 171 | 172 | For example, you want to update the `FOO` constant in-place if it exists, otherwise it should be added to a special location: 173 | 174 | ```php 175 | $config_transformer->update( 'constant', 'FOO', 'bar', array( 'anchor' => '/** My special location' ) ); 176 | ``` 177 | 178 | Which has the same effect as the long-form logic: 179 | 180 | ```php 181 | if ( $config_transformer->exists( 'constant', 'FOO' ) ) { 182 | $config_transformer->update( 'constant', 'FOO', 'bar' ); 183 | } else { 184 | $config_transformer->add( 'constant', 'FOO', 'bar', array( 'anchor' => '/** My special area' ) ); 185 | } 186 | ``` 187 | 188 | Of course the exception to this is if you are using the `add => false` option, in which case the update will return `false` and no config will be added. 189 | 190 | ### Known issues 191 | 192 | 1. Regex will only match one config definition per line. 193 | 194 | **CORRECT** 195 | ```php 196 | define( 'WP_DEBUG', true ); 197 | define( 'WP_SCRIPT_DEBUG', true ); 198 | $table_prefix = 'wp_'; 199 | $my_var = 'foo'; 200 | ``` 201 | 202 | **INCORRECT** 203 | ```php 204 | define( 'WP_DEBUG', true ); define( 'WP_SCRIPT_DEBUG', true ); 205 | $table_prefix = 'wp_'; $my_var = 'foo'; 206 | ``` 207 | 208 | 2. If the third argument in `define()` is used, it _must_ be a boolean. 209 | 210 | **CORRECT** 211 | ```php 212 | define( 'WP_DEBUG', true, false ); 213 | define( 'WP_DEBUG', true, FALSE ); 214 | define( 'foo', true, true ); 215 | define( 'foo', true, TRUE ); 216 | ``` 217 | 218 | **INCORRECT** 219 | ```php 220 | define( 'WP_DEBUG', true, 0 ); 221 | define( 'WP_DEBUG', true, 'yes' ); 222 | define( 'WP_DEBUG', true, 'this comma, will break everything' ); 223 | ``` 224 | 225 | ## Testing 226 | 227 | ```bash 228 | $ composer global require phpunit/phpunit 229 | $ composer install 230 | $ phpunit 231 | ``` 232 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cli/wp-config-transformer", 3 | "description": "Programmatically edit a wp-config.php file.", 4 | "homepage": "https://github.com/wp-cli/wp-config-transformer", 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Frankie Jarrett", 10 | "email": "fjarrett@gmail.com" 11 | } 12 | ], 13 | "config": { 14 | "process-timeout": 7200, 15 | "sort-packages": true, 16 | "allow-plugins": { 17 | "dealerdirect/phpcodesniffer-composer-installer": true, 18 | "johnpbloch/wordpress-core-installer": true 19 | } 20 | }, 21 | "require": { 22 | "php": ">=7.2.24" 23 | }, 24 | "require-dev": { 25 | "wp-cli/wp-cli-tests": "^4.0" 26 | }, 27 | "autoload": { 28 | "files": [ "src/WPConfigTransformer.php" ] 29 | }, 30 | "minimum-stability": "dev", 31 | "prefer-stable": true, 32 | "scripts": { 33 | "behat": "run-behat-tests", 34 | "behat-rerun": "rerun-behat-tests", 35 | "lint": "run-linter-tests", 36 | "phpcs": "run-phpcs-tests", 37 | "phpcbf": "run-phpcbf-cleanup", 38 | "phpunit": "run-php-unit-tests", 39 | "prepare-tests": "install-package-tests", 40 | "test": [ 41 | "@lint", 42 | "@phpcs", 43 | "@phpunit", 44 | "@behat" 45 | ] 46 | }, 47 | "extra": { 48 | "branch-alias": { 49 | "dev-main": "1.x-dev" 50 | } 51 | }, 52 | "support": { 53 | "issues": "https://github.com/wp-cli/wp-config-transformer/issues" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/WPConfigTransformer.php: -------------------------------------------------------------------------------- 1 | wp_config_path = $wp_config_path; 53 | } 54 | 55 | /** 56 | * Checks if a config exists in the wp-config.php file. 57 | * 58 | * @throws Exception If the wp-config.php file is empty. 59 | * @throws Exception If the requested config type is invalid. 60 | * 61 | * @param string $type Config type (constant or variable). 62 | * @param string $name Config name. 63 | * 64 | * @return bool 65 | */ 66 | public function exists( $type, $name ) { 67 | $wp_config_src = file_get_contents( $this->wp_config_path ); 68 | 69 | if ( ! trim( $wp_config_src ) ) { 70 | throw new Exception( 'Config file is empty.' ); 71 | } 72 | // Normalize the newline to prevent an issue coming from OSX. 73 | $this->wp_config_src = str_replace( array( "\r\n", "\n\r", "\r" ), "\n", $wp_config_src ); 74 | $this->wp_configs = $this->parse_wp_config( $this->wp_config_src ); 75 | 76 | if ( ! isset( $this->wp_configs[ $type ] ) ) { 77 | throw new Exception( "Config type '{$type}' does not exist." ); 78 | } 79 | 80 | return isset( $this->wp_configs[ $type ][ $name ] ); 81 | } 82 | 83 | /** 84 | * Get the value of a config in the wp-config.php file. 85 | * 86 | * @throws Exception If the wp-config.php file is empty. 87 | * @throws Exception If the requested config type is invalid. 88 | * 89 | * @param string $type Config type (constant or variable). 90 | * @param string $name Config name. 91 | * 92 | * @return string|null 93 | */ 94 | public function get_value( $type, $name ) { 95 | $wp_config_src = file_get_contents( $this->wp_config_path ); 96 | 97 | if ( ! trim( $wp_config_src ) ) { 98 | throw new Exception( 'Config file is empty.' ); 99 | } 100 | 101 | $this->wp_config_src = $wp_config_src; 102 | $this->wp_configs = $this->parse_wp_config( $this->wp_config_src ); 103 | 104 | if ( ! isset( $this->wp_configs[ $type ] ) ) { 105 | throw new Exception( "Config type '{$type}' does not exist." ); 106 | } 107 | 108 | return $this->wp_configs[ $type ][ $name ]['value']; 109 | } 110 | 111 | /** 112 | * Adds a config to the wp-config.php file. 113 | * 114 | * @throws Exception If the config value provided is not a string. 115 | * @throws Exception If the config placement anchor could not be located. 116 | * 117 | * @param string $type Config type (constant or variable). 118 | * @param string $name Config name. 119 | * @param string $value Config value. 120 | * @param array $options (optional) Array of special behavior options. 121 | * 122 | * @return bool 123 | */ 124 | public function add( $type, $name, $value, array $options = array() ) { 125 | if ( ! is_string( $value ) ) { 126 | throw new Exception( 'Config value must be a string.' ); 127 | } 128 | 129 | if ( $this->exists( $type, $name ) ) { 130 | return false; 131 | } 132 | 133 | $defaults = array( 134 | 'raw' => false, // Display value in raw format without quotes. 135 | 'anchor' => "/* That's all, stop editing!", // Config placement anchor string. 136 | 'separator' => PHP_EOL, // Separator between config definition and anchor string. 137 | 'placement' => 'before', // Config placement direction (insert before or after). 138 | ); 139 | 140 | list( $raw, $anchor, $separator, $placement ) = array_values( array_merge( $defaults, $options ) ); 141 | 142 | $raw = (bool) $raw; 143 | $anchor = (string) $anchor; 144 | $separator = (string) $separator; 145 | $placement = (string) $placement; 146 | 147 | if ( self::ANCHOR_EOF === $anchor ) { 148 | $contents = $this->wp_config_src . $this->normalize( $type, $name, $this->format_value( $value, $raw ) ); 149 | } else { 150 | if ( false === strpos( $this->wp_config_src, $anchor ) ) { 151 | throw new Exception( 'Unable to locate placement anchor.' ); 152 | } 153 | 154 | $new_src = $this->normalize( $type, $name, $this->format_value( $value, $raw ) ); 155 | $new_src = ( 'after' === $placement ) ? $anchor . $separator . $new_src : $new_src . $separator . $anchor; 156 | $contents = str_replace( $anchor, $new_src, $this->wp_config_src ); 157 | } 158 | 159 | return $this->save( $contents ); 160 | } 161 | 162 | /** 163 | * Updates an existing config in the wp-config.php file. 164 | * 165 | * @throws Exception If the config value provided is not a string. 166 | * 167 | * @param string $type Config type (constant or variable). 168 | * @param string $name Config name. 169 | * @param string $value Config value. 170 | * @param array $options (optional) Array of special behavior options. 171 | * 172 | * @return bool 173 | */ 174 | public function update( $type, $name, $value, array $options = array() ) { 175 | if ( ! is_string( $value ) ) { 176 | throw new Exception( 'Config value must be a string.' ); 177 | } 178 | 179 | $defaults = array( 180 | 'add' => true, // Add the config if missing. 181 | 'raw' => false, // Display value in raw format without quotes. 182 | 'normalize' => false, // Normalize config output using WP Coding Standards. 183 | ); 184 | 185 | list( $add, $raw, $normalize ) = array_values( array_merge( $defaults, $options ) ); 186 | 187 | $add = (bool) $add; 188 | $raw = (bool) $raw; 189 | $normalize = (bool) $normalize; 190 | 191 | if ( ! $this->exists( $type, $name ) ) { 192 | return ( $add ) ? $this->add( $type, $name, $value, $options ) : false; 193 | } 194 | 195 | $old_src = $this->wp_configs[ $type ][ $name ]['src']; 196 | $old_value = $this->wp_configs[ $type ][ $name ]['value']; 197 | $new_value = $this->format_value( $value, $raw ); 198 | 199 | if ( $normalize ) { 200 | $new_src = $this->normalize( $type, $name, $new_value ); 201 | } else { 202 | $new_parts = $this->wp_configs[ $type ][ $name ]['parts']; 203 | $new_parts[1] = str_replace( $old_value, $new_value, $new_parts[1] ); // Only edit the value part. 204 | $new_src = implode( '', $new_parts ); 205 | } 206 | 207 | $contents = preg_replace( 208 | sprintf( '/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m', preg_quote( trim( $old_src ), '/' ) ), 209 | '$1' . str_replace( '$', '\$', trim( $new_src ) ), 210 | $this->wp_config_src 211 | ); 212 | 213 | return $this->save( $contents ); 214 | } 215 | 216 | /** 217 | * Removes a config from the wp-config.php file. 218 | * 219 | * @param string $type Config type (constant or variable). 220 | * @param string $name Config name. 221 | * 222 | * @return bool 223 | */ 224 | public function remove( $type, $name ) { 225 | if ( ! $this->exists( $type, $name ) ) { 226 | return false; 227 | } 228 | 229 | if ( 'constant' === $type ) { 230 | $pattern = sprintf( 231 | "/\bdefine\s*\(\s*['\"]%s['\"]\s*,\s*(('[^']*'|\"[^\"]*\")|\s*(?:[\s\S]*?))\s*\)\s*;\s*/mi", 232 | preg_quote( $name, '/' ) 233 | ); 234 | } else { 235 | $pattern = sprintf( 236 | '/^\s*\$%s\s*=\s*[\s\S]*?;\s*$/mi', 237 | preg_quote( $name, '/' ) 238 | ); 239 | } 240 | 241 | $contents = preg_replace( $pattern, '', $this->wp_config_src ); 242 | 243 | return $this->save( $contents ); 244 | } 245 | 246 | /** 247 | * Applies formatting to a config value. 248 | * 249 | * @throws Exception When a raw value is requested for an empty string. 250 | * 251 | * @param string $value Config value. 252 | * @param bool $raw Display value in raw format without quotes. 253 | * 254 | * @return mixed 255 | */ 256 | protected function format_value( $value, $raw ) { 257 | if ( $raw && '' === trim( $value ) ) { 258 | throw new Exception( 'Raw value for empty string not supported.' ); 259 | } 260 | 261 | return ( $raw ) ? $value : var_export( $value, true ); 262 | } 263 | 264 | /** 265 | * Normalizes the source output for a name/value pair. 266 | * 267 | * @throws Exception If the requested config type does not support normalization. 268 | * 269 | * @param string $type Config type (constant or variable). 270 | * @param string $name Config name. 271 | * @param mixed $value Config value. 272 | * 273 | * @return string 274 | */ 275 | protected function normalize( $type, $name, $value ) { 276 | if ( 'constant' === $type ) { 277 | $placeholder = "define( '%s', %s );"; 278 | } elseif ( 'variable' === $type ) { 279 | $placeholder = '$%s = %s;'; 280 | } else { 281 | throw new Exception( "Unable to normalize config type '{$type}'." ); 282 | } 283 | 284 | return sprintf( $placeholder, $name, $value ); 285 | } 286 | 287 | /** 288 | * Parses the source of a wp-config.php file. 289 | * 290 | * @param string $src Config file source. 291 | * 292 | * @return array 293 | */ 294 | protected function parse_wp_config( $src ) { 295 | $configs = array(); 296 | $configs['constant'] = array(); 297 | $configs['variable'] = array(); 298 | 299 | // Strip comments. 300 | foreach ( token_get_all( $src ) as $token ) { 301 | if ( in_array( $token[0], array( T_COMMENT, T_DOC_COMMENT ), true ) ) { 302 | if ( '//' === $token[1] ) { 303 | // For empty line comments, actually remove empty line comments instead of all double-slashes. 304 | // See: https://github.com/wp-cli/wp-config-transformer/issues/47 305 | $src = preg_replace( '/' . preg_quote( '//', '/' ) . '$/m', '', $src ); 306 | } else { 307 | $src = str_replace( $token[1], '', $src ); 308 | } 309 | } 310 | } 311 | 312 | preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)((?:,\s*(?:true|false)\s*)?\)\s*;)/ims', $src, $constants ); 313 | preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*\$(\w+)\s*=)(\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*;)/ims', $src, $variables ); 314 | 315 | if ( ! empty( $constants[0] ) && ! empty( $constants[1] ) && ! empty( $constants[2] ) && ! empty( $constants[3] ) && ! empty( $constants[4] ) && ! empty( $constants[5] ) ) { 316 | foreach ( $constants[2] as $index => $name ) { 317 | $configs['constant'][ $name ] = array( 318 | 'src' => $constants[0][ $index ], 319 | 'value' => $constants[4][ $index ], 320 | 'parts' => array( 321 | $constants[1][ $index ], 322 | $constants[3][ $index ], 323 | $constants[5][ $index ], 324 | ), 325 | ); 326 | } 327 | } 328 | 329 | if ( ! empty( $variables[0] ) && ! empty( $variables[1] ) && ! empty( $variables[2] ) && ! empty( $variables[3] ) && ! empty( $variables[4] ) ) { 330 | // Remove duplicate(s), last definition wins. 331 | $variables[2] = array_reverse( array_unique( array_reverse( $variables[2], true ) ), true ); 332 | foreach ( $variables[2] as $index => $name ) { 333 | $configs['variable'][ $name ] = array( 334 | 'src' => $variables[0][ $index ], 335 | 'value' => $variables[4][ $index ], 336 | 'parts' => array( 337 | $variables[1][ $index ], 338 | $variables[3][ $index ], 339 | ), 340 | ); 341 | } 342 | } 343 | 344 | return $configs; 345 | } 346 | 347 | /** 348 | * Saves new contents to the wp-config.php file. 349 | * 350 | * @throws Exception If the config file content provided is empty. 351 | * @throws Exception If there is a failure when saving the wp-config.php file. 352 | * 353 | * @param string $contents New config contents. 354 | * 355 | * @return bool 356 | */ 357 | protected function save( $contents ) { 358 | if ( ! trim( $contents ) ) { 359 | throw new Exception( 'Cannot save the config file with empty contents.' ); 360 | } 361 | 362 | if ( $contents === $this->wp_config_src ) { 363 | return false; 364 | } 365 | 366 | $result = file_put_contents( $this->wp_config_path, $contents, LOCK_EX ); 367 | 368 | if ( false === $result ) { 369 | throw new Exception( 'Failed to update the config file.' ); 370 | } 371 | 372 | return true; 373 | } 374 | } 375 | --------------------------------------------------------------------------------