@wp-playground/wordpress 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,458 +1,595 @@
1
- import { phpVars as m, joinPaths as o, dirname as b, basename as y, phpVar as u } from "@php-wasm/util";
2
- import { createMemoizedFetch as P, unzipFile as f } from "@wp-playground/common";
1
+ import { phpVars as $, joinPaths as s, dirname as y, basename as b, phpVar as d } from "@php-wasm/util";
2
+ import { createMemoizedFetch as P, unzipFile as p } from "@wp-playground/common";
3
3
  import { logger as g } from "@php-wasm/logger";
4
- import { sandboxedSpawnHandlerFactory as k, PHPRequestHandler as E, PHP as R, setPhpIniEntries as T, writeFiles as c, withPHPIniValues as S } from "@php-wasm/universal";
5
- const x = `<?php
4
+ import { sandboxedSpawnHandlerFactory as T, PHPRequestHandler as E, PHP as v, setPhpIniEntries as R, writeFiles as f, withPHPIniValues as S } from "@php-wasm/universal";
5
+ const m = `<?php
6
6
 
7
7
  /**
8
- * Rewrites the wp-config.php file to ensure specific constants are defined
9
- * with specific values.
10
- *
11
- * Example:
12
- *
13
- * \`\`\`php
14
- * <?php
15
- * define('WP_DEBUG', true);
16
- * // The third define() argument is also supported:
17
- * define('SAVEQUERIES', false, true);
18
- *
19
- * // Expression
20
- * define(true ? 'WP_DEBUG_LOG' : 'WP_DEBUG_LOG', 123);
21
- *
22
- * // Guarded expressions shouldn't be wrapped twice
23
- * if(!defined(1 ? 'A' : 'B')) {
24
- * define(1 ? 'A' : 'B', 0);
25
- * }
26
- *
27
- * // More advanced expression
28
- * define((function() use($x) {
29
- * return [$x, 'a'];
30
- * })(), 123);
31
- * \`\`\`
32
- *
33
- * Rewritten with
34
- *
35
- * $constants = [
36
- * 'WP_DEBUG' => false,
37
- * 'WP_DEBUG_LOG' => true,
38
- * 'SAVEQUERIES' => true,
39
- * 'NEW_CONSTANT' => "new constant",
40
- * ];
41
- *
42
- * \`\`\`php
43
- * <?php
44
- * define('WP_DEBUG_LOG',true);
45
- * define('NEW_CONSTANT','new constant');
46
- * ?><?php
47
- * define('WP_DEBUG',false);
48
- * // The third define() argument is also supported:
49
- * define('SAVEQUERIES',true, true);
50
- *
51
- * // Expression
52
- * if(!defined($const ? 'WP_DEBUG_LOG' : 'WP_DEBUG_LOG')) {
53
- * define($const ? 'WP_DEBUG_LOG' : 'WP_DEBUG_LOG', 123);
54
- * }
55
- *
56
- * // Guarded expressions shouldn't be wrapped twice
57
- * if(!defined(1 ? 'A' : 'B')) {
58
- * define(1 ? 'A' : 'B', 0);
59
- * }
60
- *
61
- * // More advanced expression
62
- * if(!defined((function() use($x) {
63
- * return [$x, 'a'];
64
- * })())) {
65
- * define((function() use($x) {
66
- * return [$x, 'a'];
67
- * })(), 123);
68
- * }
69
- * \`\`\`
8
+ * Transforms the "wp-config.php" file.
70
9
  *
71
- * @param mixed $content A PHP file content.
72
- * @param array $constants An array of constants to define.
73
- * @param bool $when_already_defined Optional. What to do if the constant is already defined.
74
- * Possible values are:
75
- * 'rewrite' - Rewrite the constant, using the new value.
76
- * 'skip' - Skip the definition, keeping the existing value.
77
- * Default: 'rewrite'
78
- * @return string
10
+ * This parses the "wp-config.php" file contents into a token array and provides
11
+ * methods to modify it and serialize it back to a string with the modifications.
79
12
  */
80
- function rewrite_wp_config_to_define_constants($content, $constants = [], $when_already_defined = 'rewrite')
81
- {
82
- $tokens = array_reverse(token_get_all($content));
83
- $output = [];
84
- $defined_expressions = [];
85
-
86
- // Look through all the tokens and find the define calls
87
- do {
88
- $buffer = [];
89
- $name_buffer = [];
90
- $value_buffer = [];
91
- $third_arg_buffer = [];
92
-
93
- // Capture everything until the define call into output.
94
- // Capturing the define call into a buffer.
95
- // Example:
96
- // <?php echo 'a'; define (
97
- // ^^^^^^^^^^^^^^^^^^^^^^
98
- // output |buffer
99
- while ($token = array_pop($tokens)) {
100
- if (is_array($token) && $token[0] === T_STRING && (strtolower($token[1]) === 'define' || strtolower($token[1]) === 'defined')) {
101
- $buffer[] = $token;
102
- break;
103
- }
104
- $output[] = $token;
105
- }
13
+ class WP_Config_Transformer {
14
+ /**
15
+ * The tokens of the wp-config.php file.
16
+ *
17
+ * @var array<array|string>
18
+ */
19
+ private $tokens;
106
20
 
107
- // Maybe we didn't find a define call and reached the end of the file?
108
- if (!count($tokens)) {
109
- break;
110
- }
21
+ /**
22
+ * Constructor.
23
+ *
24
+ * @param string $content The contents of the wp-config.php file.
25
+ */
26
+ public function __construct( string $content ) {
27
+ $this->tokens = token_get_all( $content );
111
28
 
112
- // Keep track of the "defined" expressions that are already accounted for
113
- if($token[1] === 'defined') {
114
- $output[] = $token;
115
- $defined_expression = [];
116
- $open_parenthesis = 0;
117
- // Capture everything up to the opening parenthesis, including the parenthesis
118
- // e.g. defined (
119
- // ^^^^
120
- while ($token = array_pop($tokens)) {
121
- $output[] = $token;
122
- if ($token === "(") {
123
- ++$open_parenthesis;
124
- break;
125
- }
126
- }
29
+ // Check if the file is a valid PHP file.
30
+ $is_valid_php_file = false;
31
+ foreach ( $this->tokens as $token ) {
32
+ if ( is_array( $token ) && T_OPEN_TAG === $token[0] ) {
33
+ $is_valid_php_file = true;
34
+ break;
35
+ }
36
+ }
37
+ if ( ! $is_valid_php_file ) {
38
+ throw new Exception( "The 'wp-config.php' file is not a valid PHP file." );
39
+ }
40
+ }
127
41
 
128
- // Capture everything up to the closing parenthesis, including the parenthesis
129
- // e.g. defined (
130
- // ^^^^
131
- while ($token = array_pop($tokens)) {
132
- $output[] = $token;
133
- if ($token === ")") {
134
- --$open_parenthesis;
135
- }
136
- if ($open_parenthesis === 0) {
137
- break;
138
- }
139
- $defined_expression[] = $token;
140
- }
42
+ /**
43
+ * Create a new config transformer instance from a file.
44
+ *
45
+ * @param string $path The path to the wp-config.php file.
46
+ * @return self The new config transformer instance.
47
+ */
48
+ public static function from_file( string $path ): self {
49
+ if ( ! is_file( $path ) ) {
50
+ throw new Exception( sprintf( "The '%s' file does not exist.", $path ) );
51
+ }
52
+ return new self( file_get_contents( $path ) );
53
+ }
141
54
 
142
- $defined_expressions[] = stringify_tokens(skip_whitespace($defined_expression));
143
- continue;
144
- }
55
+ /**
56
+ * Get the transformed wp-config.php file contents.
57
+ *
58
+ * @return string The transformed wp-config.php file contents.
59
+ */
60
+ public function to_string(): string {
61
+ $output = '';
62
+ foreach ( $this->tokens as $token ) {
63
+ $output .= is_array( $token ) ? $token[1] : $token;
64
+ }
65
+ return $output;
66
+ }
145
67
 
146
- // Capture everything up to the opening parenthesis, including the parenthesis
147
- // e.g. define (
148
- // ^^^^
149
- while ($token = array_pop($tokens)) {
150
- $buffer[] = $token;
151
- if ($token === "(") {
152
- break;
153
- }
154
- }
68
+ /**
69
+ * Save the transformed wp-config.php file contents to a file.
70
+ *
71
+ * @param string $path The path to the wp-config.php file.
72
+ */
73
+ public function to_file( string $path ): void {
74
+ $result = file_put_contents( $path, $this->to_string() );
75
+ if ( false === $result ) {
76
+ throw new Exception( sprintf( "Failed to write to the '%s' file.", $path ) );
77
+ }
78
+ }
155
79
 
156
- // Capture the first argument – it's the first expression after the opening
157
- // parenthesis and before the comma:
158
- // Examples:
159
- // define("WP_DEBUG", true);
160
- // ^^^^^^^^^^^
161
- //
162
- // define(count([1,2]) > 2 ? 'WP_DEBUG' : 'FOO', true);
163
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
164
- $open_parenthesis = 0;
165
- while ($token = array_pop($tokens)) {
166
- $buffer[] = $token;
167
- if ($token === "(" || $token === "[" || $token === "{") {
168
- ++$open_parenthesis;
169
- } elseif ($token === ")" || $token === "]" || $token === "}") {
170
- --$open_parenthesis;
171
- } elseif ($token === "," && $open_parenthesis === 0) {
172
- break;
173
- }
80
+ /**
81
+ * Check if a constant is defined in the wp-config.php file.
82
+ *
83
+ * @param string $name The name of the constant.
84
+ * @return bool True if the constant is defined, false otherwise.
85
+ */
86
+ public function constant_exists( string $name ): bool {
87
+ foreach ( $this->tokens as $i => $token ) {
88
+ $is_string_token = is_array( $token ) && T_STRING === $token[0];
89
+ if ( $is_string_token && 'define' === strtolower( $token[1] ) ) {
90
+ $args = $this->collect_function_call_argument_locations( $i );
91
+ $const_name = $this->evaluate_constant_name(
92
+ array_slice( $this->tokens, $args[0][0], $args[0][1] )
93
+ );
94
+ if ( $name === $const_name ) {
95
+ return true;
96
+ }
97
+ }
98
+ }
99
+ return false;
100
+ }
174
101
 
175
- // Don't capture the comma as a part of the constant name
176
- $name_buffer[] = $token;
177
- }
102
+ /**
103
+ * Define a constant in the wp-config.php file.
104
+ *
105
+ * @param string $name The name of the constant.
106
+ * @param mixed $value The value of the constant.
107
+ */
108
+ public function define_constant( string $name, $value ): void {
109
+ // Tokenize the new constant value for insertion in the tokens array.
110
+ $definition_tokens = token_get_all(
111
+ sprintf(
112
+ "<?php define( %s, %s );\\n",
113
+ var_export( $name, true ),
114
+ var_export( $value, true )
115
+ )
116
+ );
178
117
 
179
- // Capture everything until the closing parenthesis
180
- // define("WP_DEBUG", true);
181
- // ^^^^^^
182
- $open_parenthesis = 0;
183
- $is_second_argument = true;
184
- while ($token = array_pop($tokens)) {
185
- $buffer[] = $token;
186
- if ($token === ")" && $open_parenthesis === 0) {
187
- // Final parenthesis of the define call.
188
- break;
189
- } else if ($token === "(" || $token === "[" || $token === "{") {
190
- ++$open_parenthesis;
191
- } elseif ($token === ")" || $token === "]" || $token === "}") {
192
- --$open_parenthesis;
193
- } elseif ($token === "," && $open_parenthesis === 0) {
194
- // This define call has more than 2 arguments! The third one is the
195
- // boolean value indicating $is_case_insensitive. Let's continue capturing
196
- // to $third_arg_buffer.
197
- $is_second_argument = false;
198
- }
199
- if ($is_second_argument) {
200
- $value_buffer[] = $token;
201
- } else {
202
- $third_arg_buffer[] = $token;
203
- }
204
- }
118
+ // Full constant definition statement, e.g.: define( 'WP_DEBUG', true );\\n
119
+ $define_tokens = array_slice( $definition_tokens, 1 );
205
120
 
206
- // Capture until the semicolon
207
- // define("WP_DEBUG", true) ;
208
- // ^^^
209
- while ($token = array_pop($tokens)) {
210
- $buffer[] = $token;
211
- if ($token === ";") {
212
- break;
213
- }
214
- }
121
+ // The value of the constant, e.g.: "my-database-name"
122
+ $value_tokens = array_slice( $definition_tokens, 7, -4 );
215
123
 
216
- // Decide whether $name_buffer is a constant name or an expression
217
- $name_token = null;
218
- $name_token_index = $token;
219
- $name_is_literal = true;
220
- foreach ($name_buffer as $k => $token) {
221
- if (is_array($token)) {
222
- if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT) {
223
- continue;
224
- } else if ($token[0] === T_STRING || $token[0] === T_CONSTANT_ENCAPSED_STRING) {
225
- $name_token = $token;
226
- $name_token_index = $k;
227
- } else {
228
- $name_is_literal = false;
229
- break;
230
- }
231
- } else if ($token !== "(" && $token !== ")") {
232
- $name_is_literal = false;
233
- break;
234
- }
235
- }
124
+ // Collect all locations where the constant value needs to be updated.
125
+ $updates = array();
126
+ foreach ( $this->tokens as $i => $token ) {
127
+ $is_string_token = is_array( $token ) && T_STRING === $token[0];
128
+ if ( $is_string_token && 'define' === strtolower( $token[1] ) ) {
129
+ $args = $this->collect_function_call_argument_locations( $i );
130
+ $const_name = $this->evaluate_constant_name(
131
+ array_slice( $this->tokens, $args[0][0], $args[0][1] )
132
+ );
236
133
 
237
- // We can't handle expressions as constant names. Let's wrap that define
238
- // call in an if(!defined()) statement, just in case it collides with
239
- // a constant name.
240
- if (!$name_is_literal) {
241
- // Ensure the defined expression is not already accounted for
242
- foreach ($defined_expressions as $defined_expression) {
243
- if ($defined_expression === stringify_tokens(skip_whitespace($name_buffer))) {
244
- $output = array_merge($output, $buffer);
245
- continue 2;
246
- }
247
- }
248
- $output = array_merge(
249
- $output,
250
- ["if(!defined("],
251
- $name_buffer,
252
- [")) {\\n "],
253
- ['define('],
254
- $name_buffer,
255
- [','],
256
- $value_buffer,
257
- $third_arg_buffer,
258
- [");"],
259
- ["\\n}\\n"]
260
- );
261
- continue;
262
- }
134
+ if ( $name === $const_name ) {
135
+ $updates[] = $args[1];
136
+ }
137
+ }
138
+ }
263
139
 
264
- // Yay, we have a literal constant name in the buffer now. Let's
265
- // get its value:
266
- $name = eval('return ' . $name_token[1] . ';');
140
+ // Modify the token array to define the constant. Apply updates in reverse
141
+ // order, so splices at earlier positions don't shift indices after them.
142
+ for ( $i = count( $updates ) - 1; $i >= 0; $i -= 1 ) {
143
+ list ( $value_start, $value_length ) = $updates[ $i ];
144
+ array_splice( $this->tokens, $value_start, $value_length, $value_tokens );
145
+ }
267
146
 
268
- // If the constant name is not in the list of constants we're looking,
269
- // we can ignore it.
270
- if (!array_key_exists($name, $constants)) {
271
- $output = array_merge($output, $buffer);
272
- continue;
273
- }
147
+ // If it's a new constant, inject it at the anchor location.
148
+ if ( 0 === count( $updates ) ) {
149
+ $anchor = $this->get_new_constant_location();
150
+ array_splice( $this->tokens, $anchor, 0, $define_tokens );
274
151
 
275
- // If "$when_already_defined" is set to 'skip', ignore the definition, and
276
- // remove the constant from the list so it doesn't get added to the output.
277
- if ('skip' === $when_already_defined) {
278
- $output = array_merge($output, $buffer);
279
- unset($constants[$name]);
280
- continue;
281
- }
152
+ /*
153
+ * Ensure at least one newline (one "\\n") before the new constant.
154
+ * This must be done after inserting the constant definition in order
155
+ * to avoid shifting the anchor location when a new token is inserted.
156
+ */
157
+ $this->ensure_newlines( $anchor - 1, 1 );
158
+ }
159
+ }
282
160
 
283
- // We now have a define() call that defines a constant we're looking for.
284
- // Let's rewrite its value to the one
285
- $output = array_merge(
286
- $output,
287
- ['define('],
288
- $name_buffer,
289
- [','],
290
- [var_export($constants[$name], true)],
291
- $third_arg_buffer,
292
- [");"]
293
- );
161
+ /**
162
+ * Define multiple constants in the wp-config.php file.
163
+ *
164
+ * @param array<string, mixed> $constants An array of name-value pairs of constants to define.
165
+ */
166
+ public function define_constants( array $constants ): void {
167
+ foreach ( $constants as $name => $value ) {
168
+ $this->define_constant( $name, $value );
169
+ }
170
+ }
294
171
 
295
- // Remove the constant from the list so we can process any remaining
296
- // constants later.
297
- unset($constants[$name]);
298
- } while (count($tokens));
299
-
300
- // Add any constants that weren't found in the file
301
- if (count($constants)) {
302
- $prepend = [
303
- "<?php \\n"
304
- ];
305
- foreach ($constants as $name => $value) {
306
- $prepend = array_merge(
307
- $prepend,
308
- [
309
- "define(",
310
- var_export($name, true),
311
- ',',
312
- var_export($value, true),
313
- ");\\n"
314
- ]
315
- );
316
- }
317
- $prepend[] = "?>";
318
- $output = array_merge(
319
- $prepend,
320
- $output
321
- );
322
- }
172
+ /**
173
+ * Inject code block into the wp-config.php file.
174
+ *
175
+ * @param string $code The code to inject.
176
+ */
177
+ public function inject_code_block( string $code ): void {
178
+ // Tokenize the injected code for insertion in the token array.
179
+ $tokens = token_get_all( sprintf( '<?php %s', trim( $code ) ) );
180
+ $code_tokens = array_slice( $tokens, 1 );
323
181
 
324
- // Translate the output tokens back into a string
325
- return stringify_tokens($output);
326
- }
182
+ // Inject the code at the anchor location.
183
+ $anchor = $this->get_injected_code_location();
184
+ array_splice( $this->tokens, $anchor, 0, $code_tokens );
327
185
 
328
- function stringify_tokens($tokens) {
329
- $output = '';
330
- foreach ($tokens as $token) {
331
- if (is_array($token)) {
332
- $output .= $token[1];
333
- } else {
334
- $output .= $token;
335
- }
336
- }
337
- return $output;
338
- }
186
+ /*
187
+ * Ensure empty line before and after the code block (at least two "\\n").
188
+ * This must be done after inserting the injected code, and the location
189
+ * AFTER must be updated prior to the location BEFORE, in order to avoid
190
+ * shifting the anchor location when a new token is inserted.
191
+ */
192
+ $this->ensure_newlines( $anchor + count( $code_tokens ), 2 );
193
+ $this->ensure_newlines( $anchor - 1, 2 );
194
+ }
339
195
 
340
- function skip_whitespace($tokens) {
341
- $output = [];
342
- foreach ($tokens as $token) {
343
- if (is_array($token) && ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)) {
344
- continue;
345
- }
346
- $output[] = $token;
347
- }
348
- return $output;
196
+ /**
197
+ * Remove code block defined by two comment fragments from the wp-config.php file.
198
+ *
199
+ * @param string $from_comment_fragment A comment fragment from which to remove the code.
200
+ * @param string $to_comment_fragment A comment fragment to which to remove the code.
201
+ */
202
+ public function remove_code_block( string $from_comment_fragment, string $to_comment_fragment ): void {
203
+ $start = $this->find_first_token_location( T_COMMENT, $from_comment_fragment );
204
+ $end = $this->find_first_token_location( T_COMMENT, $to_comment_fragment );
205
+ if ( null === $start || null === $end ) {
206
+ return;
207
+ }
208
+
209
+ // Remove the code, including the comment fragments.
210
+ array_splice( $this->tokens, $start, $end - $start + 1 );
211
+
212
+ // If previous and next tokens are whitespace, merge them.
213
+ $prev = $this->tokens[ $start - 1 ];
214
+ $next = $this->tokens[ $start ] ?? null;
215
+ if (
216
+ is_array( $prev ) && T_WHITESPACE === $prev[0]
217
+ && is_array( $next ) && T_WHITESPACE === $next[0]
218
+ ) {
219
+ $this->tokens[ $start - 1 ][1] = $prev[1] . $next[1];
220
+ array_splice( $this->tokens, $start, 1 );
221
+ }
222
+
223
+ // Remove up to two empty lines (before & after), keeping at least one.
224
+ $token = $this->tokens[ $start - 1 ];
225
+ if ( is_array( $token ) && T_WHITESPACE === $token[0] ) {
226
+ $newlines = substr_count( $token[1], "\\n" );
227
+ if ( $newlines > 2 ) {
228
+ $limit = min( $newlines - 2, 4 );
229
+ $value = $token[1];
230
+ for ( $i = 0; $limit > 0; $i += 1 ) {
231
+ if ( "\\n" === $value[ $i ] ) {
232
+ $value = substr_replace( $value, '', $i, 1 );
233
+ $limit -= 1;
234
+ }
235
+ }
236
+ $this->tokens[ $start - 1 ][1] = $value;
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Parse arguments of a function call and collect their locations.
243
+ *
244
+ * @param int $start The location of the first token of the function call.
245
+ * @return array<array<int, int>> The arguments of the function call.
246
+ */
247
+ private function collect_function_call_argument_locations( int $start ): array {
248
+ // Find location of the opening parenthesis after the function name.
249
+ $i = $start;
250
+ while ( '(' !== $this->tokens[ $i ] ) {
251
+ $i += 1;
252
+ }
253
+ $i += 1;
254
+
255
+ // Collect all function call argument locations.
256
+ $args = array();
257
+ $arg_start = $this->skip_whitespace_and_comments( $i );
258
+ $parens_level = 0;
259
+ for ( $i = $arg_start; $i < count( $this->tokens ); $i += 1 ) {
260
+ // Skip whitespace and comments, but preserve the index of the last
261
+ // non-whitespace token to calculate the exact argument boundaries.
262
+ $prev_i = $i;
263
+ $i = $this->skip_whitespace_and_comments( $i );
264
+ $token = $this->tokens[ $i ];
265
+
266
+ if ( 0 === $parens_level && ( ',' === $token || ')' === $token ) ) {
267
+ $args[] = array( $arg_start, $prev_i - $arg_start );
268
+ if ( ',' === $token ) {
269
+ // Start of the next argument.
270
+ $arg_start = $this->skip_whitespace_and_comments( $i + 1 );
271
+ $i = $arg_start;
272
+ } else {
273
+ // End of the argument list.
274
+ break;
275
+ }
276
+ } elseif ( '(' === $token || '[' === $token || '{' === $token ) {
277
+ $parens_level += 1;
278
+ } elseif ( ')' === $token || ']' === $token || '}' === $token ) {
279
+ $parens_level -= 1;
280
+ }
281
+ }
282
+ return $args;
283
+ }
284
+
285
+ /**
286
+ * Evaluate the constant name value from its tokens.
287
+ *
288
+ * @param array $name_tokens The tokens containing the constant name.
289
+ * @return string|null The evaluated constant name.
290
+ */
291
+ private function evaluate_constant_name( array $name_tokens ): ?string {
292
+ // Decide whether the array represents a constant name or an expression.
293
+ $name_token = null;
294
+ foreach ( $name_tokens as $token ) {
295
+ if ( $this->is_whitespace( $token ) ) {
296
+ continue;
297
+ }
298
+ if ( is_array( $token ) ) {
299
+ if ( T_STRING === $token[0] || T_CONSTANT_ENCAPSED_STRING === $token[0] ) {
300
+ $name_token = $token;
301
+ } else {
302
+ return null;
303
+ }
304
+ } elseif ( '(' !== $token && ')' !== $token ) {
305
+ return null;
306
+ }
307
+ }
308
+
309
+ if ( null === $name_token ) {
310
+ return null;
311
+ }
312
+
313
+ // Get the constant name value.
314
+ return eval( 'return ' . $name_token[1] . ';' );
315
+ }
316
+
317
+ /**
318
+ * Skip whitespace and comment tokens and return the location of the first
319
+ * non-whitespace and non-comment token after the specified start location.
320
+ *
321
+ * @param int $start The start location in the token array.
322
+ * @return int The location of the first non-whitespace and non-comment token.
323
+ */
324
+ private function skip_whitespace_and_comments( int $start ): int {
325
+ for ( $i = $start; $i < count( $this->tokens ); $i += 1 ) {
326
+ if ( $this->is_whitespace( $this->tokens[ $i ] ) ) {
327
+ continue;
328
+ }
329
+ break;
330
+ }
331
+ return $i;
332
+ }
333
+
334
+ /**
335
+ * Ensure minimum number of newlines are present at the given index.
336
+ *
337
+ * @param int $index The index of the token to ensure newlines.
338
+ * @param int $count The number of newlines that should be present.
339
+ */
340
+ private function ensure_newlines( int $index, int $count ): void {
341
+ $token = $this->tokens[ $index ] ?? null;
342
+ if ( is_array( $token ) && ( T_WHITESPACE === $token[0] || T_OPEN_TAG === $token[0] ) ) {
343
+ $newlines = substr_count( $token[1], "\\n" );
344
+ if ( $newlines < $count ) {
345
+ $this->tokens[ $index ][1] .= str_repeat( "\\n", $count - $newlines );
346
+ }
347
+ } else {
348
+ $new_token = array( T_WHITESPACE, str_repeat( "\\n", $count ) );
349
+ array_splice( $this->tokens, $index, 0, array( $new_token ) );
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Get the location to inject new constant definitions in the token array.
355
+ *
356
+ * @return int The location for new constant definitions in the token array.
357
+ */
358
+ private function get_new_constant_location(): int {
359
+ // First try to find the "That's all, stop editing!" comment.
360
+ $anchor = $this->find_first_token_location( T_COMMENT, "That's all, stop editing!" );
361
+ if ( null !== $anchor ) {
362
+ return $anchor;
363
+ }
364
+
365
+ // If not found, try the "Absolute path to the WordPress directory." doc comment.
366
+ $anchor = $this->find_first_token_location( T_DOC_COMMENT, 'Absolute path to the WordPress directory.' );
367
+ if ( null !== $anchor ) {
368
+ return $anchor;
369
+ }
370
+
371
+ // If not found, try the "Sets up WordPress vars and included files." doc comment.
372
+ $anchor = $this->find_first_token_location( T_DOC_COMMENT, 'Sets up WordPress vars and included files.' );
373
+ if ( null !== $anchor ) {
374
+ return $anchor;
375
+ }
376
+
377
+ // If not found, try "require_once ABSPATH . 'wp-settings.php';".
378
+ $anchor = $this->find_first_token_location( T_REQUIRE_ONCE );
379
+ if ( null !== $anchor ) {
380
+ return $anchor;
381
+ }
382
+
383
+ // If not found, fall back to the PHP opening tag.
384
+ $open_tag_anchor = $this->find_first_token_location( T_OPEN_TAG );
385
+ if ( null !== $open_tag_anchor ) {
386
+ return $open_tag_anchor + 1;
387
+ }
388
+
389
+ // If we still don't have an anchor, the file is not a valid PHP file.
390
+ throw new Exception( "The 'wp-config.php' file is not a valid PHP file." );
391
+ }
392
+
393
+ /**
394
+ * Get the location to inject new code in the token array.
395
+ *
396
+ * @return int The location for injected code in the token array.
397
+ */
398
+ private function get_injected_code_location(): int {
399
+ // First try to find the "/** Sets up WordPress vars and included files. */" comment.
400
+ $anchor = $this->find_first_token_location( T_DOC_COMMENT, 'Sets up WordPress vars and included files.' );
401
+ if ( null !== $anchor ) {
402
+ return $anchor;
403
+ }
404
+
405
+ // If not found, try "require_once ABSPATH . 'wp-settings.php';".
406
+ $anchor = $this->find_require_wp_settings_location();
407
+ if ( null !== $anchor ) {
408
+ return $anchor;
409
+ }
410
+
411
+ // If not found, fall back to the PHP opening tag.
412
+ $open_tag_anchor = $this->find_first_token_location( T_OPEN_TAG );
413
+ if ( null !== $open_tag_anchor ) {
414
+ return $open_tag_anchor + 1;
415
+ }
416
+
417
+ // If we still don't have an anchor, the file is not a valid PHP file.
418
+ throw new Exception( "The 'wp-config.php' file is not a valid PHP file." );
419
+ }
420
+
421
+ /**
422
+ * Find location of the "wp-settings.php" require statement in the token array.
423
+ *
424
+ * This method searches for the following statement:
425
+ *
426
+ * require_once ABSPATH . 'wp-settings.php';
427
+ *
428
+ * @return int|null The location of the require statement.
429
+ */
430
+ private function find_require_wp_settings_location(): ?int {
431
+ $require_anchor = $this->find_first_token_location( T_REQUIRE_ONCE );
432
+ if ( null === $require_anchor ) {
433
+ return null;
434
+ }
435
+
436
+ $abspath = $this->tokens[ $require_anchor + 2 ] ?? null;
437
+ $path = $this->tokens[ $require_anchor + 6 ] ?? null;
438
+ if (
439
+ ( is_array( $abspath ) && 'ABSPATH' === $abspath[1] )
440
+ && ( is_array( $path ) && "'wp-settings.php'" === $path[1] )
441
+ ) {
442
+ return $require_anchor;
443
+ }
444
+ return null;
445
+ }
446
+
447
+ /**
448
+ * Find location of the first token of a given type in the token array.
449
+ *
450
+ * @param int $type The type of the token.
451
+ * @param string $search Optional. A search string to match against the token content.
452
+ * @return int|null The location of the first token.
453
+ */
454
+ private function find_first_token_location( int $type, ?string $search = null ): ?int {
455
+ foreach ( $this->tokens as $i => $token ) {
456
+ if ( is_array( $token ) && $type === $token[0] ) {
457
+ if ( null === $search || false !== strpos( $token[1], $search ) ) {
458
+ return $i;
459
+ }
460
+ }
461
+ }
462
+ return null;
463
+ }
464
+
465
+ /**
466
+ * Check if a token is whitespace or a comment.
467
+ *
468
+ * @param array|string $token The token to check.
469
+ * @return bool True if the token is whitespace or a comment.
470
+ */
471
+ private function is_whitespace( $token ): bool {
472
+ return is_array( $token )
473
+ && ( T_WHITESPACE === $token[0] || T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0] );
474
+ }
349
475
  }
350
476
  `;
351
- async function v(e, n, t, i = "rewrite") {
352
- const r = m({ wpConfigPath: n, constants: t, whenAlreadyDefined: i });
353
- if ((await e.run({
354
- code: `<?php ob_start(); ?>
355
- ${x}
356
- $wp_config_path = ${r.wpConfigPath};
357
- $wp_config = file_get_contents($wp_config_path);
358
- $new_wp_config = rewrite_wp_config_to_define_constants($wp_config, ${r.constants}, ${r.whenAlreadyDefined});
359
- $return_value = file_put_contents($wp_config_path, $new_wp_config);
360
- ob_clean();
361
- echo false === $return_value ? '0' : '1';
362
- ob_end_flush();
363
- `
364
- })).text !== "1")
365
- throw new Error("Failed to rewrite constants in wp-config.php.");
366
- }
367
- async function I(e, n) {
368
- const t = o(n, "wp-config.php"), i = {
477
+ async function I(t, e) {
478
+ const n = s(e, "wp-config.php"), i = {
369
479
  DB_NAME: "wordpress"
370
480
  };
371
- !e.fileExists(t) && e.fileExists(o(n, "wp-config-sample.php")) && await e.writeFile(
372
- t,
373
- await e.readFileAsBuffer(
374
- o(n, "wp-config-sample.php")
481
+ if (!t.fileExists(n) && t.fileExists(s(e, "wp-config-sample.php")) && await t.writeFile(
482
+ n,
483
+ await t.readFileAsBuffer(
484
+ s(e, "wp-config-sample.php")
375
485
  )
376
- ), await v(e, t, i, "skip");
486
+ ), !t.fileExists(n))
487
+ return;
488
+ const a = $({ wpConfigPath: n, constants: i });
489
+ if ((await t.run({
490
+ code: `${m}
491
+ $wp_config_path = ${a.wpConfigPath};
492
+ $transformer = WP_Config_Transformer::from_file($wp_config_path);
493
+ foreach ( ${a.constants} as $name => $value ) {
494
+ if ( ! $transformer->constant_exists( $name ) ) {
495
+ $transformer->define_constant($name, $value);
496
+ }
497
+ }
498
+ $transformer->to_file($wp_config_path);
499
+ `
500
+ })).errors.length > 0)
501
+ throw new Error("Failed to auto-configure wp-config.php.");
502
+ }
503
+ async function B(t, e, n) {
504
+ const i = $({ wpConfigPath: e, constants: n });
505
+ if ((await t.run({
506
+ code: `${m}
507
+ $wp_config_path = ${i.wpConfigPath};
508
+ $transformer = WP_Config_Transformer::from_file($wp_config_path);
509
+ $transformer->define_constants(${i.constants});
510
+ $transformer->to_file($wp_config_path);
511
+ `
512
+ })).errors.length > 0)
513
+ throw new Error("Failed to rewrite constants in wp-config.php.");
377
514
  }
378
- async function V(e) {
379
- const n = await U(e);
380
- return await L(n, e), n;
515
+ async function Q(t) {
516
+ const e = await C(t);
517
+ return await x(e, t), e;
381
518
  }
382
- async function L(e, n) {
383
- var d, a;
384
- const t = await e.getPrimaryPhp();
385
- if ((d = n.hooks) != null && d.beforeWordPressFiles && await n.hooks.beforeWordPressFiles(t), n.wordPressZip && await N(t, await n.wordPressZip), n.constants)
386
- for (const l in n.constants)
387
- t.defineConstant(l, n.constants[l]);
388
- n.dataSqlPath && (t.defineConstant("DB_DIR", b(n.dataSqlPath)), t.defineConstant("DB_FILE", y(n.dataSqlPath))), t.defineConstant("WP_HOME", n.siteUrl), t.defineConstant("WP_SITEURL", n.siteUrl), await I(t, e.documentRoot), (a = n.hooks) != null && a.beforeDatabaseSetup && await n.hooks.beforeDatabaseSetup(t);
519
+ async function x(t, e) {
520
+ var c, r;
521
+ const n = await t.getPrimaryPhp();
522
+ if ((c = e.hooks) != null && c.beforeWordPressFiles && await e.hooks.beforeWordPressFiles(n), e.wordPressZip && await N(n, await e.wordPressZip), e.constants)
523
+ for (const l in e.constants)
524
+ n.defineConstant(l, e.constants[l]);
525
+ e.dataSqlPath && (n.defineConstant("DB_DIR", y(e.dataSqlPath)), n.defineConstant("DB_FILE", b(e.dataSqlPath))), n.defineConstant("WP_HOME", e.siteUrl), n.defineConstant("WP_SITEURL", e.siteUrl), await I(n, t.documentRoot), (r = e.hooks) != null && r.beforeDatabaseSetup && await e.hooks.beforeDatabaseSetup(n);
389
526
  let i = !1;
390
- n.sqliteIntegrationPluginZip && (i = !0, await F(
391
- t,
392
- await n.sqliteIntegrationPluginZip
527
+ e.sqliteIntegrationPluginZip && (i = !0, await F(
528
+ n,
529
+ await e.sqliteIntegrationPluginZip
393
530
  ));
394
- const r = n.wordpressInstallMode ?? "download-and-install", s = !!n.dataSqlPath;
531
+ const a = e.wordpressInstallMode ?? "download-and-install", o = !!e.dataSqlPath;
395
532
  if (["download-and-install", "install-from-existing-files"].includes(
396
- r
533
+ a
397
534
  )) {
398
- await _(e, {
535
+ await h(t, {
399
536
  usesSqlite: i,
400
- hasCustomDatabasePath: s
537
+ hasCustomDatabasePath: o
401
538
  });
402
539
  try {
403
- await h(t);
540
+ await _(n);
404
541
  } catch (l) {
405
- throw s || await p(e), l;
542
+ throw o || await u(t), l;
406
543
  }
407
- s || await p(e);
408
- } else if (r === "install-from-existing-files-if-needed") {
409
- if (await _(e, {
544
+ o || await u(t);
545
+ } else if (a === "install-from-existing-files-if-needed") {
546
+ if (await h(t, {
410
547
  usesSqlite: i,
411
- hasCustomDatabasePath: s
412
- }), !await $(t))
548
+ hasCustomDatabasePath: o
549
+ }), !await w(n))
413
550
  try {
414
- await h(t);
551
+ await _(n);
415
552
  } catch (l) {
416
- throw s || await p(e), l;
553
+ throw o || await u(t), l;
417
554
  }
418
- s || await p(e);
555
+ o || await u(t);
419
556
  }
420
- return e;
557
+ return t;
421
558
  }
422
- async function _(e, {
423
- usesSqlite: n,
424
- hasCustomDatabasePath: t
559
+ async function h(t, {
560
+ usesSqlite: e,
561
+ hasCustomDatabasePath: n
425
562
  }) {
426
- const i = await e.getPrimaryPhp();
563
+ const i = await t.getPrimaryPhp();
427
564
  if (i.isFile("/internal/shared/preload/0-sqlite.php"))
428
565
  return;
429
- const r = o(
430
- e.documentRoot,
566
+ const a = s(
567
+ t.documentRoot,
431
568
  "wp-content/mu-plugins/sqlite-database-integration"
432
569
  );
433
- if (!i.isDir(r) && !n && !t)
570
+ if (!i.isDir(a) && !e && !n)
434
571
  throw new Error("Error connecting to the MySQL database.");
435
572
  }
436
- async function p(e) {
437
- const n = await e.getPrimaryPhp();
438
- if (await C(n))
573
+ async function u(t) {
574
+ const e = await t.getPrimaryPhp();
575
+ if (await O(e))
439
576
  return;
440
- if (n.isFile("/internal/shared/preload/0-sqlite.php"))
577
+ if (e.isFile("/internal/shared/preload/0-sqlite.php"))
441
578
  throw new Error("Error connecting to the SQLite database.");
442
- const i = o(
443
- e.documentRoot,
579
+ const i = s(
580
+ t.documentRoot,
444
581
  "wp-content/mu-plugins/sqlite-database-integration"
445
582
  );
446
- throw n.isDir(i) ? new Error("Error connecting to the SQLite database.") : new Error("Error connecting to the MySQL database.");
583
+ throw e.isDir(i) ? new Error("Error connecting to the SQLite database.") : new Error("Error connecting to the MySQL database.");
447
584
  }
448
- async function U(e) {
449
- const n = e.spawnHandler ?? k;
450
- async function t(r, s = !1) {
451
- const d = await e.createPhpRuntime(s), a = new R(d);
452
- if (e.sapiName && a.setSapiName(e.sapiName), r && (a.requestHandler = r), e.phpIniEntries && T(a, e.phpIniEntries), a.defineConstant("WP_SQLITE_AST_DRIVER", !0), e.constants)
453
- for (const l in e.constants)
454
- a.defineConstant(l, e.constants[l]);
455
- return s && /**
585
+ async function C(t) {
586
+ const e = t.spawnHandler ?? T;
587
+ async function n(a, o = !1) {
588
+ const c = await t.createPhpRuntime(o), r = new v(c);
589
+ if (t.sapiName && r.setSapiName(t.sapiName), a && (r.requestHandler = a), t.phpIniEntries && R(r, t.phpIniEntries), r.defineConstant("WP_SQLITE_AST_DRIVER", !0), t.constants)
590
+ for (const l in t.constants)
591
+ r.defineConstant(l, t.constants[l]);
592
+ return o && /**
456
593
  * Only the first PHP instance of the first worker created
457
594
  * during WordPress boot writes these files – otherwise we'll keep
458
595
  * overwriting them with concurrent writers living in other worker
@@ -462,45 +599,45 @@ async function U(e) {
462
599
  * mechanism. It works, because secondary workers are only booted
463
600
  * once the primary worker has fully booted.
464
601
  */
465
- !a.isFile("/internal/.boot-files-written") && (await A(a), await c(a, "/", e.createFiles || {}), await q(
466
- a,
467
- o(new URL(e.siteUrl).pathname, "phpinfo.php")
468
- ), await c(a, "/internal", {
602
+ !r.isFile("/internal/.boot-files-written") && (await U(r), await f(r, "/", t.createFiles || {}), await W(
603
+ r,
604
+ s(new URL(t.siteUrl).pathname, "phpinfo.php")
605
+ ), await f(r, "/internal", {
469
606
  ".boot-files-written": ""
470
- })), n && await a.setSpawnHandler(
471
- n(
472
- r ? () => r.instanceManager.acquirePHPInstance() : void 0
607
+ })), e && await r.setSpawnHandler(
608
+ e(
609
+ a ? () => a.instanceManager.acquirePHPInstance() : void 0
473
610
  )
474
- ), a.enableRuntimeRotation({
475
- recreateRuntime: e.createPhpRuntime,
611
+ ), r.enableRuntimeRotation({
612
+ recreateRuntime: t.createPhpRuntime,
476
613
  maxRequests: 400
477
- }), e.onPHPInstanceCreated && await e.onPHPInstanceCreated(a, { isPrimary: s }), a;
614
+ }), t.onPHPInstanceCreated && await t.onPHPInstanceCreated(r, { isPrimary: o }), r;
478
615
  }
479
616
  const i = new E({
480
- documentRoot: e.documentRoot || "/wordpress",
481
- absoluteUrl: e.siteUrl,
482
- rewriteRules: D,
483
- pathAliases: e.pathAliases,
484
- getFileNotFoundAction: e.getFileNotFoundAction ?? W,
485
- cookieStore: e.cookieStore,
617
+ documentRoot: t.documentRoot || "/wordpress",
618
+ absoluteUrl: t.siteUrl,
619
+ rewriteRules: q,
620
+ pathAliases: t.pathAliases,
621
+ getFileNotFoundAction: t.getFileNotFoundAction ?? L,
622
+ cookieStore: t.cookieStore,
486
623
  /**
487
624
  * If maxPhpInstances is 1, the PHPRequestHandler constructor needs
488
625
  * a PHP instance. Internally, it creates a SinglePHPInstanceManager
489
626
  * and uses the same PHP instance to handle all requests.
490
627
  */
491
- php: e.maxPhpInstances === 1 ? await t(void 0, !0) : void 0,
628
+ php: t.maxPhpInstances === 1 ? await n(void 0, !0) : void 0,
492
629
  /**
493
630
  * If maxPhpInstances is not 1, the PHPRequestHandler constructor needs
494
631
  * a PHP factory function. Internally, it creates a PHPProcessManager that
495
632
  * maintains a pool of reusable PHP instances.
496
633
  */
497
- phpFactory: e.maxPhpInstances !== 1 ? async ({ isPrimary: r }) => t(i, r) : void 0,
498
- maxPhpInstances: e.maxPhpInstances
634
+ phpFactory: t.maxPhpInstances !== 1 ? async ({ isPrimary: a }) => n(i, a) : void 0,
635
+ maxPhpInstances: t.maxPhpInstances
499
636
  });
500
637
  return i;
501
638
  }
502
- async function $(e) {
503
- return (await e.run({
639
+ async function w(t) {
640
+ return (await t.run({
504
641
  code: `<?php
505
642
  ob_start();
506
643
  $wp_load = getenv('DOCUMENT_ROOT') . '/wp-load.php';
@@ -514,19 +651,19 @@ async function $(e) {
514
651
  ob_end_flush();
515
652
  `,
516
653
  env: {
517
- DOCUMENT_ROOT: e.documentRoot
654
+ DOCUMENT_ROOT: t.documentRoot
518
655
  }
519
656
  })).text === "1";
520
657
  }
521
- async function h(e) {
658
+ async function _(t) {
522
659
  var i;
523
- const n = await S(
524
- e,
660
+ const e = await S(
661
+ t,
525
662
  {
526
663
  disable_functions: "fsockopen",
527
664
  allow_url_fopen: "0"
528
665
  },
529
- async () => await e.request({
666
+ async () => await t.request({
530
667
  url: "/wp-admin/install.php?step=2",
531
668
  method: "POST",
532
669
  body: {
@@ -543,14 +680,14 @@ async function h(e) {
543
680
  }
544
681
  })
545
682
  );
546
- if (!await $(e))
683
+ if (!await w(t))
547
684
  throw new Error(
548
- `Failed to install WordPress – installer responded with "${(i = n.text) == null ? void 0 : i.substring(
685
+ `Failed to install WordPress – installer responded with "${(i = e.text) == null ? void 0 : i.substring(
549
686
  0,
550
687
  100
551
688
  )}"`
552
689
  );
553
- (await e.run({
690
+ (await t.run({
554
691
  code: `<?php
555
692
  ob_start();
556
693
  $wp_load = getenv('DOCUMENT_ROOT') . '/wp-load.php';
@@ -573,18 +710,18 @@ async function h(e) {
573
710
  ob_end_flush();
574
711
  `,
575
712
  env: {
576
- DOCUMENT_ROOT: e.documentRoot
713
+ DOCUMENT_ROOT: t.documentRoot
577
714
  }
578
715
  })).text !== "1" && g.warn("Failed to default to pretty permalinks after WP install.");
579
716
  }
580
- function W(e) {
717
+ function L(t) {
581
718
  return {
582
719
  type: "internal-redirect",
583
720
  uri: "/index.php"
584
721
  };
585
722
  }
586
- async function C(e) {
587
- return (await e.run({
723
+ async function O(t) {
724
+ return (await t.run({
588
725
  code: `<?php
589
726
  ob_start();
590
727
  $wp_load = getenv('DOCUMENT_ROOT') . '/wp-load.php';
@@ -598,35 +735,35 @@ async function C(e) {
598
735
  ob_end_flush();
599
736
  `,
600
737
  env: {
601
- DOCUMENT_ROOT: e.documentRoot
738
+ DOCUMENT_ROOT: t.documentRoot
602
739
  }
603
740
  })).text === "1";
604
741
  }
605
- async function j(e) {
606
- const { php: n, reap: t } = await e.instanceManager.acquirePHPInstance();
742
+ async function V(t) {
743
+ const { php: e, reap: n } = await t.instanceManager.acquirePHPInstance();
607
744
  try {
608
- const r = (await n.run({
745
+ const a = (await e.run({
609
746
  code: `<?php
610
- require '${e.documentRoot}/wp-includes/version.php';
747
+ require '${t.documentRoot}/wp-includes/version.php';
611
748
  echo $wp_version;
612
749
  `
613
750
  })).text;
614
- if (!r)
751
+ if (!a)
615
752
  throw new Error("Unable to read loaded WordPress version.");
616
- return O(r);
753
+ return A(a);
617
754
  } finally {
618
- t();
755
+ n();
619
756
  }
620
757
  }
621
- function O(e) {
622
- if (/-(alpha|beta|RC)\d*-\d+$/.test(e))
758
+ function A(t) {
759
+ if (/-(alpha|beta|RC)\d*-\d+$/.test(t))
623
760
  return "trunk";
624
- if (/-(beta|RC)\d*$/.test(e))
761
+ if (/-(beta|RC)\d*$/.test(t))
625
762
  return "beta";
626
- const i = e.match(/^(\d+\.\d+)(?:\.\d+)?$/);
627
- return i !== null ? i[1] : e;
763
+ const i = t.match(/^(\d+\.\d+)(?:\.\d+)?$/);
764
+ return i !== null ? i[1] : t;
628
765
  }
629
- const D = [
766
+ const q = [
630
767
  /**
631
768
  * Substitutes the multisite WordPress rewrite rule:
632
769
  *
@@ -642,8 +779,8 @@ const D = [
642
779
  replacement: "$2"
643
780
  }
644
781
  ];
645
- async function A(e) {
646
- await e.mkdir("/internal/shared/mu-plugins"), await e.writeFile(
782
+ async function U(t) {
783
+ await t.mkdir("/internal/shared/mu-plugins"), await t.writeFile(
647
784
  "/internal/shared/preload/env.php",
648
785
  `<?php
649
786
 
@@ -673,7 +810,7 @@ async function A(e) {
673
810
  }
674
811
  }
675
812
  `
676
- ), await e.writeFile(
813
+ ), await t.writeFile(
677
814
  "/internal/shared/mu-plugins/1-auto-login.php",
678
815
  `<?php
679
816
  /**
@@ -830,7 +967,7 @@ async function A(e) {
830
967
  return $interval;
831
968
  });
832
969
  `
833
- ), await e.writeFile(
970
+ ), await t.writeFile(
834
971
  "/internal/shared/mu-plugins/0-playground.php",
835
972
  `<?php
836
973
  // Needed because gethostbyname( 'wordpress.org' ) returns
@@ -875,7 +1012,7 @@ async function A(e) {
875
1012
  define('ERROR_LOG_FILE', $log_file);
876
1013
  ini_set('error_log', $log_file);
877
1014
  ?>`
878
- ), await e.writeFile(
1015
+ ), await t.writeFile(
879
1016
  "/internal/shared/mu-plugins/sitemap-redirect.php",
880
1017
  `<?php
881
1018
  /**
@@ -902,7 +1039,7 @@ async function A(e) {
902
1039
  }
903
1040
  }
904
1041
  `
905
- ), await e.writeFile(
1042
+ ), await t.writeFile(
906
1043
  "/internal/shared/preload/error-handler.php",
907
1044
  `<?php
908
1045
  (function() {
@@ -955,13 +1092,13 @@ async function A(e) {
955
1092
  })();`
956
1093
  );
957
1094
  }
958
- async function q(e, n = "/phpinfo.php") {
959
- await e.writeFile(
1095
+ async function W(t, e = "/phpinfo.php") {
1096
+ await t.writeFile(
960
1097
  "/internal/shared/preload/phpinfo.php",
961
1098
  `<?php
962
1099
  // Render PHPInfo if the requested page is /phpinfo.php
963
- if ( isset($_SERVER['REQUEST_URI']) && ${u(
964
- n
1100
+ if ( isset($_SERVER['REQUEST_URI']) && ${d(
1101
+ e
965
1102
  )} === $_SERVER['REQUEST_URI'] ) {
966
1103
  phpinfo();
967
1104
  exit;
@@ -969,29 +1106,29 @@ async function q(e, n = "/phpinfo.php") {
969
1106
  `
970
1107
  );
971
1108
  }
972
- async function F(e, n) {
973
- await e.isDir("/tmp/sqlite-database-integration") && await e.rmdir("/tmp/sqlite-database-integration", {
1109
+ async function F(t, e) {
1110
+ await t.isDir("/tmp/sqlite-database-integration") && await t.rmdir("/tmp/sqlite-database-integration", {
974
1111
  recursive: !0
975
- }), await e.mkdir("/tmp/sqlite-database-integration"), await f(e, n, "/tmp/sqlite-database-integration");
976
- const t = "/internal/shared/sqlite-database-integration", i = `/tmp/sqlite-database-integration/${(await e.listFiles("/tmp/sqlite-database-integration"))[0]}`;
977
- await e.mv(i, t), await e.defineConstant("SQLITE_MAIN_FILE", "1");
978
- const s = (await e.readFileAsText(
979
- o(t, "db.copy")
1112
+ }), await t.mkdir("/tmp/sqlite-database-integration"), await p(t, e, "/tmp/sqlite-database-integration");
1113
+ const n = "/internal/shared/sqlite-database-integration", i = `/tmp/sqlite-database-integration/${(await t.listFiles("/tmp/sqlite-database-integration"))[0]}`;
1114
+ await t.mv(i, n), await t.defineConstant("SQLITE_MAIN_FILE", "1");
1115
+ const o = (await t.readFileAsText(
1116
+ s(n, "db.copy")
980
1117
  )).replace(
981
1118
  "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'",
982
- u(t)
1119
+ d(n)
983
1120
  ).replace(
984
1121
  "'{SQLITE_PLUGIN}'",
985
- u(o(t, "load.php"))
986
- ), d = o(await e.documentRoot, "wp-content/db.php"), a = `<?php
1122
+ d(s(n, "load.php"))
1123
+ ), c = s(await t.documentRoot, "wp-content/db.php"), r = `<?php
987
1124
  // Do not preload this if WordPress comes with a custom db.php file.
988
- if(file_exists(${u(d)})) {
1125
+ if(file_exists(${d(c)})) {
989
1126
  return;
990
1127
  }
991
1128
  ?>`, l = "/internal/shared/mu-plugins/sqlite-database-integration.php";
992
- await e.writeFile(l, a + s), await e.writeFile(
1129
+ await t.writeFile(l, r + o), await t.writeFile(
993
1130
  "/internal/shared/preload/0-sqlite.php",
994
- a + `<?php
1131
+ r + `<?php
995
1132
 
996
1133
  /**
997
1134
  * Loads the SQLite integration plugin before WordPress is loaded
@@ -1041,7 +1178,7 @@ class Playground_SQLite_Integration_Loader {
1041
1178
  $GLOBALS['wpdb']->$name = $value;
1042
1179
  }
1043
1180
  protected function load_sqlite_integration() {
1044
- require_once ${u(l)};
1181
+ require_once ${d(l)};
1045
1182
  }
1046
1183
  }
1047
1184
  /**
@@ -1067,7 +1204,7 @@ if(!function_exists('mysqli_connect')) {
1067
1204
  }
1068
1205
 
1069
1206
  `
1070
- ), await e.writeFile(
1207
+ ), await t.writeFile(
1071
1208
  "/internal/shared/mu-plugins/sqlite-test.php",
1072
1209
  `<?php
1073
1210
  global $wpdb;
@@ -1078,64 +1215,64 @@ if(!function_exists('mysqli_connect')) {
1078
1215
  `
1079
1216
  );
1080
1217
  }
1081
- async function N(e, n) {
1082
- e.mkdir("/tmp/unzipped-wordpress"), await f(e, n, "/tmp/unzipped-wordpress"), e.fileExists("/tmp/unzipped-wordpress/wordpress.zip") && await f(
1083
- e,
1218
+ async function N(t, e) {
1219
+ t.mkdir("/tmp/unzipped-wordpress"), await p(t, e, "/tmp/unzipped-wordpress"), t.fileExists("/tmp/unzipped-wordpress/wordpress.zip") && await p(
1220
+ t,
1084
1221
  "/tmp/unzipped-wordpress/wordpress.zip",
1085
1222
  "/tmp/unzipped-wordpress"
1086
1223
  );
1087
- let t = e.fileExists("/tmp/unzipped-wordpress/wordpress") ? "/tmp/unzipped-wordpress/wordpress" : e.fileExists("/tmp/unzipped-wordpress/build") ? "/tmp/unzipped-wordpress/build" : "/tmp/unzipped-wordpress";
1088
- if (!e.fileExists(o(t, "wp-config-sample.php"))) {
1089
- const r = e.listFiles(t);
1090
- if (r.length) {
1091
- const s = r[0];
1092
- e.fileExists(
1093
- o(t, s, "wp-config-sample.php")
1094
- ) && (t = o(t, s));
1224
+ let n = t.fileExists("/tmp/unzipped-wordpress/wordpress") ? "/tmp/unzipped-wordpress/wordpress" : t.fileExists("/tmp/unzipped-wordpress/build") ? "/tmp/unzipped-wordpress/build" : "/tmp/unzipped-wordpress";
1225
+ if (!t.fileExists(s(n, "wp-config-sample.php"))) {
1226
+ const a = t.listFiles(n);
1227
+ if (a.length) {
1228
+ const o = a[0];
1229
+ t.fileExists(
1230
+ s(n, o, "wp-config-sample.php")
1231
+ ) && (n = s(n, o));
1095
1232
  }
1096
1233
  }
1097
- const i = (r, s, d) => {
1098
- if (d.isDir(r) && d.isDir(s))
1099
- for (const a of d.listFiles(r)) {
1100
- const l = o(r, a), w = o(s, a);
1101
- i(l, w, d);
1234
+ const i = (a, o, c) => {
1235
+ if (c.isDir(a) && c.isDir(o))
1236
+ for (const r of c.listFiles(a)) {
1237
+ const l = s(a, r), k = s(o, r);
1238
+ i(l, k, c);
1102
1239
  }
1103
1240
  else {
1104
- if (d.fileExists(s)) {
1105
- const a = r.replace(
1241
+ if (c.fileExists(o)) {
1242
+ const r = a.replace(
1106
1243
  /^\/tmp\/unzipped-wordpress\//,
1107
1244
  "/"
1108
1245
  );
1109
1246
  g.warn(
1110
- `Cannot unzip WordPress files at ${s}: ${a} already exists.`
1247
+ `Cannot unzip WordPress files at ${o}: ${r} already exists.`
1111
1248
  );
1112
1249
  return;
1113
1250
  }
1114
- d.mv(r, s);
1251
+ c.mv(a, o);
1115
1252
  }
1116
1253
  };
1117
- i(t, e.documentRoot, e), e.fileExists(t) && e.rmdir(t, { recursive: !0 }), !e.fileExists(o(e.documentRoot, "wp-config.php")) && e.fileExists(o(e.documentRoot, "wp-config-sample.php")) && e.writeFile(
1118
- o(e.documentRoot, "wp-config.php"),
1119
- e.readFileAsText(
1120
- o(e.documentRoot, "/wp-config-sample.php")
1254
+ i(n, t.documentRoot, t), t.fileExists(n) && t.rmdir(n, { recursive: !0 }), !t.fileExists(s(t.documentRoot, "wp-config.php")) && t.fileExists(s(t.documentRoot, "wp-config-sample.php")) && t.writeFile(
1255
+ s(t.documentRoot, "wp-config.php"),
1256
+ t.readFileAsText(
1257
+ s(t.documentRoot, "/wp-config-sample.php")
1121
1258
  )
1122
1259
  );
1123
1260
  }
1124
- const G = P(fetch), M = "https://github.com/WordPress/WordPress/archive/refs/heads/master.zip";
1125
- async function Z(e = "latest") {
1126
- if (e === null)
1127
- e = "latest";
1128
- else if (e.startsWith("https://") || e.startsWith("http://")) {
1261
+ const D = P(fetch), M = "https://github.com/WordPress/WordPress/archive/refs/heads/master.zip";
1262
+ async function Z(t = "latest") {
1263
+ if (t === null)
1264
+ t = "latest";
1265
+ else if (t.startsWith("https://") || t.startsWith("http://")) {
1129
1266
  const i = await crypto.subtle.digest(
1130
1267
  "SHA-1",
1131
- new TextEncoder().encode(e)
1132
- ), r = Array.from(new Uint8Array(i)).map((s) => s.toString(16).padStart(2, "0")).join("");
1268
+ new TextEncoder().encode(t)
1269
+ ), a = Array.from(new Uint8Array(i)).map((o) => o.toString(16).padStart(2, "0")).join("");
1133
1270
  return {
1134
- releaseUrl: e,
1135
- version: "custom-" + r.substring(0, 8),
1271
+ releaseUrl: t,
1272
+ version: "custom-" + a.substring(0, 8),
1136
1273
  source: "inferred"
1137
1274
  };
1138
- } else if (e === "trunk" || e === "nightly") {
1275
+ } else if (t === "trunk" || t === "nightly") {
1139
1276
  const i = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1140
1277
  return {
1141
1278
  releaseUrl: `${M}?ts=${i}`,
@@ -1143,52 +1280,52 @@ async function Z(e = "latest") {
1143
1280
  source: "inferred"
1144
1281
  };
1145
1282
  }
1146
- let t = await (await G(
1283
+ let n = await (await D(
1147
1284
  "https://api.wordpress.org/core/version-check/1.7/?channel=beta"
1148
1285
  )).json();
1149
- t = t.offers.filter(
1286
+ n = n.offers.filter(
1150
1287
  (i) => i.response === "autoupdate"
1151
1288
  );
1152
- for (const i of t) {
1153
- if (e === "beta" && (i.version.includes("beta") || i.version.includes("RC")))
1289
+ for (const i of n) {
1290
+ if (t === "beta" && (i.version.includes("beta") || i.version.includes("RC")))
1154
1291
  return {
1155
1292
  releaseUrl: i.download,
1156
1293
  version: i.version,
1157
1294
  source: "api"
1158
1295
  };
1159
- if (e === "latest" && !i.version.includes("beta") && !i.version.includes("RC"))
1296
+ if (t === "latest" && !i.version.includes("beta") && !i.version.includes("RC"))
1160
1297
  return {
1161
1298
  releaseUrl: i.download,
1162
1299
  version: i.version,
1163
1300
  source: "api"
1164
1301
  };
1165
- if (i.version.substring(0, e.length) === e)
1302
+ if (i.version.substring(0, t.length) === t)
1166
1303
  return {
1167
1304
  releaseUrl: i.download,
1168
1305
  version: i.version,
1169
1306
  source: "api"
1170
1307
  };
1171
1308
  }
1172
- return e.match(/^\d+\.\d+\.0$/) && (e = e.split(".").slice(0, 2).join(".")), {
1173
- releaseUrl: `https://wordpress.org/wordpress-${e}.zip`,
1174
- version: e,
1309
+ return t.match(/^\d+\.\d+\.0$/) && (t = t.split(".").slice(0, 2).join(".")), {
1310
+ releaseUrl: `https://wordpress.org/wordpress-${t}.zip`,
1311
+ version: t,
1175
1312
  source: "inferred"
1176
1313
  };
1177
1314
  }
1178
1315
  export {
1179
- U as bootRequestHandler,
1180
- L as bootWordPress,
1181
- V as bootWordPressAndRequestHandler,
1182
- v as defineWpConfigConstants,
1316
+ C as bootRequestHandler,
1317
+ x as bootWordPress,
1318
+ Q as bootWordPressAndRequestHandler,
1319
+ B as defineWpConfigConstants,
1183
1320
  I as ensureWpConfig,
1184
- W as getFileNotFoundActionForWordPress,
1185
- j as getLoadedWordPressVersion,
1186
- q as preloadPhpInfoRoute,
1321
+ L as getFileNotFoundActionForWordPress,
1322
+ V as getLoadedWordPressVersion,
1323
+ W as preloadPhpInfoRoute,
1187
1324
  F as preloadSqliteIntegration,
1188
1325
  Z as resolveWordPressRelease,
1189
- A as setupPlatformLevelMuPlugins,
1326
+ U as setupPlatformLevelMuPlugins,
1190
1327
  N as unzipWordPress,
1191
- O as versionStringToLoadedWordPressVersion,
1192
- D as wordPressRewriteRules
1328
+ A as versionStringToLoadedWordPressVersion,
1329
+ q as wordPressRewriteRules
1193
1330
  };
1194
1331
  //# sourceMappingURL=index.js.map