PluginProbe
WP Debugging / 2.11.24
WP Debugging v2.11.24
2.12.6 2.12.5 trunk 2.10.0 2.10.1 2.10.2 2.11.0 2.11.1 2.11.10 2.11.11 2.11.12 2.11.13 2.11.14 2.11.15 2.11.16 2.11.17 2.11.18 2.11.19 2.11.2 2.11.20 2.11.21 2.11.22 2.11.23 2.11.24 2.11.3 All 59 releases
wp-debugging / vendor / wp-cli / wp-config-transformer / src / WPConfigTransformer.php

WPConfigTransformer.php in WP Debugging 2.11.24, at vendor/wp-cli/wp-config-transformer/src/WPConfigTransformer.php

364 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Transforms a wp-config.php file.
5 */
6 class WPConfigTransformer {
7 /**
8 * Append to end of file
9 */
10 const ANCHOR_EOF = 'EOF';
11
12 /**
13 * Path to the wp-config.php file.
14 *
15 * @var string
16 */
17 protected $wp_config_path;
18
19 /**
20 * Original source of the wp-config.php file.
21 *
22 * @var string
23 */
24 protected $wp_config_src;
25
26 /**
27 * Array of parsed configs.
28 *
29 * @var array
30 */
31 protected $wp_configs = array();
32
33 /**
34 * Instantiates the class with a valid wp-config.php.
35 *
36 * @throws Exception If the wp-config.php file is missing.
37 * @throws Exception If the wp-config.php file is not writable.
38 *
39 * @param string $wp_config_path Path to a wp-config.php file.
40 */
41 public function __construct( $wp_config_path ) {
42 $basename = basename( $wp_config_path );
43
44 if ( ! file_exists( $wp_config_path ) ) {
45 throw new Exception( "{$basename} does not exist." );
46 }
47
48 if ( ! is_writable( $wp_config_path ) ) {
49 throw new Exception( "{$basename} is not writable." );
50 }
51
52 $this->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 $pattern = sprintf( '/(?<=^|;|<\?php\s|<\?\s)%s\s*(\S|$)/m', preg_quote( $this->wp_configs[ $type ][ $name ]['src'], '/' ) );
230 $contents = preg_replace( $pattern, '$1', $this->wp_config_src );
231
232 return $this->save( $contents );
233 }
234
235 /**
236 * Applies formatting to a config value.
237 *
238 * @throws Exception When a raw value is requested for an empty string.
239 *
240 * @param string $value Config value.
241 * @param bool $raw Display value in raw format without quotes.
242 *
243 * @return mixed
244 */
245 protected function format_value( $value, $raw ) {
246 if ( $raw && '' === trim( $value ) ) {
247 throw new Exception( 'Raw value for empty string not supported.' );
248 }
249
250 return ( $raw ) ? $value : var_export( $value, true );
251 }
252
253 /**
254 * Normalizes the source output for a name/value pair.
255 *
256 * @throws Exception If the requested config type does not support normalization.
257 *
258 * @param string $type Config type (constant or variable).
259 * @param string $name Config name.
260 * @param mixed $value Config value.
261 *
262 * @return string
263 */
264 protected function normalize( $type, $name, $value ) {
265 if ( 'constant' === $type ) {
266 $placeholder = "define( '%s', %s );";
267 } elseif ( 'variable' === $type ) {
268 $placeholder = '$%s = %s;';
269 } else {
270 throw new Exception( "Unable to normalize config type '{$type}'." );
271 }
272
273 return sprintf( $placeholder, $name, $value );
274 }
275
276 /**
277 * Parses the source of a wp-config.php file.
278 *
279 * @param string $src Config file source.
280 *
281 * @return array
282 */
283 protected function parse_wp_config( $src ) {
284 $configs = array();
285 $configs['constant'] = array();
286 $configs['variable'] = array();
287
288 // Strip comments.
289 foreach ( token_get_all( $src ) as $token ) {
290 if ( in_array( $token[0], array( T_COMMENT, T_DOC_COMMENT ), true ) ) {
291 if ( '//' === $token[1] ) {
292 // For empty line comments, actually remove empty line comments instead of all double-slashes.
293 // See: https://github.com/wp-cli/wp-config-transformer/issues/47
294 $src = preg_replace( '/' . preg_quote( '//', '/' ) . '$/m', '', $src );
295 } else {
296 $src = str_replace( $token[1], '', $src );
297 }
298 }
299 }
300
301 preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)((?:,\s*(?:true|false)\s*)?\)\s*;)/ims', $src, $constants );
302 preg_match_all( '/(?<=^|;|<\?php\s|<\?\s)(\h*\$(\w+)\s*=)(\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*;)/ims', $src, $variables );
303
304 if ( ! empty( $constants[0] ) && ! empty( $constants[1] ) && ! empty( $constants[2] ) && ! empty( $constants[3] ) && ! empty( $constants[4] ) && ! empty( $constants[5] ) ) {
305 foreach ( $constants[2] as $index => $name ) {
306 $configs['constant'][ $name ] = array(
307 'src' => $constants[0][ $index ],
308 'value' => $constants[4][ $index ],
309 'parts' => array(
310 $constants[1][ $index ],
311 $constants[3][ $index ],
312 $constants[5][ $index ],
313 ),
314 );
315 }
316 }
317
318 if ( ! empty( $variables[0] ) && ! empty( $variables[1] ) && ! empty( $variables[2] ) && ! empty( $variables[3] ) && ! empty( $variables[4] ) ) {
319 // Remove duplicate(s), last definition wins.
320 $variables[2] = array_reverse( array_unique( array_reverse( $variables[2], true ) ), true );
321 foreach ( $variables[2] as $index => $name ) {
322 $configs['variable'][ $name ] = array(
323 'src' => $variables[0][ $index ],
324 'value' => $variables[4][ $index ],
325 'parts' => array(
326 $variables[1][ $index ],
327 $variables[3][ $index ],
328 ),
329 );
330 }
331 }
332
333 return $configs;
334 }
335
336 /**
337 * Saves new contents to the wp-config.php file.
338 *
339 * @throws Exception If the config file content provided is empty.
340 * @throws Exception If there is a failure when saving the wp-config.php file.
341 *
342 * @param string $contents New config contents.
343 *
344 * @return bool
345 */
346 protected function save( $contents ) {
347 if ( ! trim( $contents ) ) {
348 throw new Exception( 'Cannot save the config file with empty contents.' );
349 }
350
351 if ( $contents === $this->wp_config_src ) {
352 return false;
353 }
354
355 $result = file_put_contents( $this->wp_config_path, $contents, LOCK_EX );
356
357 if ( false === $result ) {
358 throw new Exception( 'Failed to update the config file.' );
359 }
360
361 return true;
362 }
363 }
364