wp-studio 1.7.7-alpha1
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/LICENSE.md +257 -0
- package/README.md +87 -0
- package/dist/cli/_events-BeOo0LuG.js +116 -0
- package/dist/cli/appdata-07CF2rhg.js +21090 -0
- package/dist/cli/archive-xDmkN4wb.js +15942 -0
- package/dist/cli/browser-CgWK-yoe.js +44 -0
- package/dist/cli/certificate-manager-DdBumKZp.js +250 -0
- package/dist/cli/create-BHVhkvTx.js +80 -0
- package/dist/cli/create-ZS29BDDi.js +40999 -0
- package/dist/cli/delete-BgQn-elT.js +56 -0
- package/dist/cli/delete-g8pgaLna.js +132 -0
- package/dist/cli/get-wordpress-version-BwSCJujO.js +18 -0
- package/dist/cli/index-7pbG_s_U.js +434 -0
- package/dist/cli/index-BXRYeCYG.js +1393 -0
- package/dist/cli/index-T3F1GwxX.js +2668 -0
- package/dist/cli/is-errno-exception-t38xF2pB.js +6 -0
- package/dist/cli/list-BE_UBjL5.js +105 -0
- package/dist/cli/list-DKz0XxM7.js +1032 -0
- package/dist/cli/logger-actions-OaIvl-ai.js +45 -0
- package/dist/cli/login-B4PkfKOu.js +82 -0
- package/dist/cli/logout-BC9gKlTj.js +48 -0
- package/dist/cli/main.js +5 -0
- package/dist/cli/mu-plugins-GEfKsl5U.js +530 -0
- package/dist/cli/passwords-DyzWd9Xi.js +80 -0
- package/dist/cli/process-manager-daemon.js +327 -0
- package/dist/cli/process-manager-ipc-AUZeYYDT.js +454 -0
- package/dist/cli/proxy-daemon.js +197 -0
- package/dist/cli/run-wp-cli-command-BctnMDWG.js +88 -0
- package/dist/cli/sequential-BQFuixXz.js +46 -0
- package/dist/cli/server-files-C_oy-mnI.js +26 -0
- package/dist/cli/set-DknhAZpw.js +327 -0
- package/dist/cli/site-utils-CfsabjUn.js +243 -0
- package/dist/cli/snapshots-6XE53y_F.js +874 -0
- package/dist/cli/sqlite-integration-H4OwSlwR.js +83 -0
- package/dist/cli/start-CRJqm09_.js +90 -0
- package/dist/cli/status-CWNHIOaY.js +44 -0
- package/dist/cli/status-CWWx9jYF.js +110 -0
- package/dist/cli/stop-CQosmjqA.js +117 -0
- package/dist/cli/update-BgL2HKHW.js +101 -0
- package/dist/cli/validation-error-DqLxqQuA.js +40 -0
- package/dist/cli/wordpress-server-child.js +514 -0
- package/dist/cli/wordpress-server-ipc-Dwsg9jSb.js +140 -0
- package/dist/cli/wordpress-server-manager-CtiuJqEb.js +566 -0
- package/dist/cli/wordpress-version-utils-B6UVeTh_.js +51 -0
- package/dist/cli/wp-UGSnlkN0.js +103 -0
- package/package.json +73 -0
- package/patches/@wp-playground+wordpress+3.1.12.patch +28 -0
- package/scripts/postinstall-npm.mjs +38 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { readdir, unlink, mkdtemp, writeFile } from "fs/promises";
|
|
2
|
+
import { tmpdir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
async function createLoaderMuPlugin() {
|
|
5
|
+
try {
|
|
6
|
+
const tempDir = await mkdtemp(join(tmpdir(), "studio-loader-"));
|
|
7
|
+
const loaderPath = join(tempDir, "99-studio-loader.php");
|
|
8
|
+
const loaderContent = `<?php
|
|
9
|
+
/**
|
|
10
|
+
* Studio MU-Plugins Loader
|
|
11
|
+
* Loads Studio-specific mu-plugins from /internal/studio/mu-plugins/
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Define database constants if not already defined. It fixes the error
|
|
15
|
+
// for imported sites that don't have those defined e.g. WP Cloud and
|
|
16
|
+
// include plugins which try to access those directly e.g. Mailpoet
|
|
17
|
+
if ( ! defined( 'DB_NAME' ) ) define( 'DB_NAME', 'database_name_here' );
|
|
18
|
+
if ( ! defined( 'DB_USER' ) ) define( 'DB_USER', 'username_here' );
|
|
19
|
+
if ( ! defined( 'DB_PASSWORD' ) ) define( 'DB_PASSWORD', 'password_here' );
|
|
20
|
+
if ( ! defined( 'DB_HOST' ) ) define( 'DB_HOST', 'localhost' );
|
|
21
|
+
if ( ! defined( 'DB_CHARSET' ) ) define( 'DB_CHARSET', 'utf8' );
|
|
22
|
+
if ( ! defined( 'DB_COLLATE' ) ) define( 'DB_COLLATE', '' );
|
|
23
|
+
|
|
24
|
+
// Set environment type to local if not already defined
|
|
25
|
+
if ( ! defined( 'WP_ENVIRONMENT_TYPE' ) ) define( 'WP_ENVIRONMENT_TYPE', 'local' );
|
|
26
|
+
|
|
27
|
+
$studio_mu_plugins_dir = '/internal/studio/mu-plugins';
|
|
28
|
+
|
|
29
|
+
if ( is_dir( $studio_mu_plugins_dir ) ) {
|
|
30
|
+
$files = glob( $studio_mu_plugins_dir . '/*.php' );
|
|
31
|
+
if ( $files ) {
|
|
32
|
+
// Sort files to ensure consistent loading order
|
|
33
|
+
sort( $files );
|
|
34
|
+
foreach ( $files as $file ) {
|
|
35
|
+
if ( is_file( $file ) ) {
|
|
36
|
+
require_once $file;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
await writeFile(loaderPath, loaderContent);
|
|
43
|
+
return loaderPath;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Failed to create loader mu-plugin: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function getStandardMuPlugins(options) {
|
|
49
|
+
const muPlugins = [];
|
|
50
|
+
muPlugins.push({
|
|
51
|
+
filename: "0-tmp-fix-qm-plugin-sapi.php",
|
|
52
|
+
content: `<?php
|
|
53
|
+
// This is a temporary fix for a Query Manager plugin, which isn't rendered in wp-admin if sapi is "cli" (it's the case for wordpress-playground).
|
|
54
|
+
// See https://github.com/WordPress/wordpress-playground/pull/2424#issuecomment-3686951491
|
|
55
|
+
// It's not the best fix, but it's simple and for consistency it's the same as used in wordpress-playground (https://github.com/WordPress/wordpress-playground/pull/2415)
|
|
56
|
+
define('QM_TESTS', true);
|
|
57
|
+
`
|
|
58
|
+
});
|
|
59
|
+
muPlugins.push({
|
|
60
|
+
filename: "0-https-for-reverse-proxy.php",
|
|
61
|
+
content: `<?php
|
|
62
|
+
// See https://developer.wordpress.org/advanced-administration/security/https/#using-a-reverse-proxy
|
|
63
|
+
if( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false ){
|
|
64
|
+
$_SERVER['HTTPS'] = 'on';
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
});
|
|
68
|
+
muPlugins.push({
|
|
69
|
+
filename: "0-redirect-to-siteurl-constant.php",
|
|
70
|
+
content: `<?php
|
|
71
|
+
// See https://core.trac.wordpress.org/ticket/33821#comment:10
|
|
72
|
+
add_action( 'init', function() {
|
|
73
|
+
if ( ! defined( 'WP_SITEURL' ) ) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$current_host = $_SERVER['HTTP_HOST'] ?? '';
|
|
78
|
+
|
|
79
|
+
if ( preg_match( '/^localhost:\\d+$/', $current_host ) ) {
|
|
80
|
+
$wp_siteurl_host = parse_url( WP_SITEURL, PHP_URL_HOST );
|
|
81
|
+
$wp_siteurl_port = parse_url( WP_SITEURL, PHP_URL_PORT );
|
|
82
|
+
$wp_siteurl_host_with_port = $wp_siteurl_host;
|
|
83
|
+
if ( $wp_siteurl_port ) {
|
|
84
|
+
$wp_siteurl_host_with_port .= ':' . $wp_siteurl_port;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ( $current_host === $wp_siteurl_host_with_port ) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$requested_uri = $_SERVER['REQUEST_URI'] ?? '/';
|
|
92
|
+
wp_redirect( rtrim( WP_SITEURL, '/' ) . $requested_uri, 302 );
|
|
93
|
+
exit;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
`
|
|
97
|
+
});
|
|
98
|
+
muPlugins.push({
|
|
99
|
+
filename: "0-allowed-redirect-hosts.php",
|
|
100
|
+
content: `<?php
|
|
101
|
+
// Needed because gethostbyname( <host> ) returns
|
|
102
|
+
// a private network IP address for some reason.
|
|
103
|
+
add_filter( 'allowed_redirect_hosts', function( $hosts ) {
|
|
104
|
+
$redirect_hosts = array(
|
|
105
|
+
'wordpress.org',
|
|
106
|
+
'api.wordpress.org',
|
|
107
|
+
'downloads.wordpress.org',
|
|
108
|
+
'themes.svn.wordpress.org',
|
|
109
|
+
'fonts.gstatic.com',
|
|
110
|
+
);
|
|
111
|
+
return array_merge( $hosts, $redirect_hosts );
|
|
112
|
+
} );
|
|
113
|
+
add_filter('http_request_host_is_external', '__return_true', 20, 3 );
|
|
114
|
+
`
|
|
115
|
+
});
|
|
116
|
+
muPlugins.push({
|
|
117
|
+
filename: "0-thumbnails.php",
|
|
118
|
+
content: `<?php
|
|
119
|
+
// Facilitates the taking of screenshots to be used as thumbnails.
|
|
120
|
+
if ( isset( $_GET['studio-hide-adminbar'] ) ) {
|
|
121
|
+
add_filter( 'show_admin_bar', '__return_false' );
|
|
122
|
+
}
|
|
123
|
+
`
|
|
124
|
+
});
|
|
125
|
+
muPlugins.push({
|
|
126
|
+
filename: "0-check-theme-availability.php",
|
|
127
|
+
content: `<?php
|
|
128
|
+
function check_current_theme_availability() {
|
|
129
|
+
// Get the current theme's directory
|
|
130
|
+
$current_theme = wp_get_theme();
|
|
131
|
+
$theme_dir = get_theme_root() . '/' . $current_theme->stylesheet;
|
|
132
|
+
|
|
133
|
+
if ( !is_dir( $theme_dir ) ) {
|
|
134
|
+
$all_themes = wp_get_themes();
|
|
135
|
+
$available_themes = [];
|
|
136
|
+
|
|
137
|
+
foreach ($all_themes as $theme_slug => $theme_obj) {
|
|
138
|
+
if ($theme_slug != $current_theme->get_stylesheet()) {
|
|
139
|
+
$available_themes[$theme_slug] = $theme_obj;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!empty($available_themes)) {
|
|
144
|
+
$new_theme_slug = array_keys($available_themes)[0];
|
|
145
|
+
switch_theme($new_theme_slug);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
add_action('after_setup_theme', 'check_current_theme_availability');
|
|
150
|
+
`
|
|
151
|
+
});
|
|
152
|
+
muPlugins.push({
|
|
153
|
+
filename: "0-permalinks.php",
|
|
154
|
+
content: `<?php
|
|
155
|
+
// Support permalinks without "index.php"
|
|
156
|
+
add_filter( 'got_url_rewrite', '__return_true' );
|
|
157
|
+
`
|
|
158
|
+
});
|
|
159
|
+
muPlugins.push({
|
|
160
|
+
filename: "0-wp-admin-trailing-slash.php",
|
|
161
|
+
content: `<?php
|
|
162
|
+
/**
|
|
163
|
+
* Add trailing slash to wp-admin URLs
|
|
164
|
+
* This ensures /wp-admin redirects to /wp-admin/
|
|
165
|
+
*/
|
|
166
|
+
add_action( 'init', function() {
|
|
167
|
+
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
|
|
168
|
+
|
|
169
|
+
// Check if this is a wp-admin request without trailing slash
|
|
170
|
+
if ( $request_uri === '/wp-admin' || preg_match( '#^/wp-admin$#', $request_uri ) ) {
|
|
171
|
+
$redirect_url = '/wp-admin/';
|
|
172
|
+
|
|
173
|
+
// Preserve query string if present
|
|
174
|
+
if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
|
|
175
|
+
$redirect_url .= '?' . $_SERVER['QUERY_STRING'];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
wp_redirect( $redirect_url, 301 );
|
|
179
|
+
exit;
|
|
180
|
+
}
|
|
181
|
+
}, 1 );
|
|
182
|
+
`
|
|
183
|
+
});
|
|
184
|
+
muPlugins.push({
|
|
185
|
+
filename: "0-deactivate-jetpack-modules.php",
|
|
186
|
+
content: `<?php
|
|
187
|
+
// Disable Jetpack Protect 2FA for local auto-login purpose
|
|
188
|
+
add_action( 'jetpack_active_modules', 'jetpack_deactivate_modules' );
|
|
189
|
+
function jetpack_deactivate_modules( $active ) {
|
|
190
|
+
if ( ( $index = array_search('protect', $active, true) ) !== false ) {
|
|
191
|
+
unset( $active[ $index ] );
|
|
192
|
+
}
|
|
193
|
+
return $active;
|
|
194
|
+
}
|
|
195
|
+
`
|
|
196
|
+
});
|
|
197
|
+
muPlugins.push({
|
|
198
|
+
filename: "0-suppress-dns-get-record-warnings.php",
|
|
199
|
+
content: `<?php
|
|
200
|
+
set_error_handler(function($severity, $message, $file, $line) {
|
|
201
|
+
if ($severity === E_WARNING && strpos($message, "dns_get_record(): dns_get_record() always returns an empty array in PHP.wasm.") === 0) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
});
|
|
206
|
+
`
|
|
207
|
+
});
|
|
208
|
+
if (!options.isWpAutoUpdating) {
|
|
209
|
+
muPlugins.push({
|
|
210
|
+
filename: "0-disable-auto-updates.php",
|
|
211
|
+
content: `<?php
|
|
212
|
+
// Disable auto-updates
|
|
213
|
+
add_filter( 'allow_dev_auto_core_updates', '__return_false' );
|
|
214
|
+
add_filter( 'allow_minor_auto_core_updates', '__return_false' );
|
|
215
|
+
add_filter( 'allow_major_auto_core_updates', '__return_false' );
|
|
216
|
+
`
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
muPlugins.push({
|
|
220
|
+
filename: "0-enable-auto-updates.php",
|
|
221
|
+
content: `<?php
|
|
222
|
+
// Enable auto-updates
|
|
223
|
+
add_filter( 'allow_dev_auto_core_updates', '__return_true' );
|
|
224
|
+
add_filter( 'allow_minor_auto_core_updates', '__return_true' );
|
|
225
|
+
add_filter( 'allow_major_auto_core_updates', '__return_true' );
|
|
226
|
+
`
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
muPlugins.push({
|
|
230
|
+
filename: "0-http-request-timeout.php",
|
|
231
|
+
content: `<?php
|
|
232
|
+
// Use low-speed timeout instead of hard timeout to handle both large downloads and stalled connections
|
|
233
|
+
// - Allows large plugin downloads (e.g., Jetpack 33MB) to complete with reasonable internet speeds
|
|
234
|
+
// - Fails fast if connection stalls (speed drops below 1KB/s for 30 seconds)
|
|
235
|
+
// - Provides quick feedback for genuinely broken/unresponsive servers
|
|
236
|
+
// Match WordPress core's timeout for plugin downloads (300s)
|
|
237
|
+
add_filter( 'http_request_timeout', function() {
|
|
238
|
+
return 300; // 5 minutes - matches WordPress core, low-speed timeout catches stalls
|
|
239
|
+
} );
|
|
240
|
+
|
|
241
|
+
add_action('http_api_curl', function($curl, $url, $options) {
|
|
242
|
+
// Abort if connection can't be established within 30 seconds
|
|
243
|
+
curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, 30 );
|
|
244
|
+
|
|
245
|
+
// Abort if speed drops below 1KB/s for 30 consecutive seconds
|
|
246
|
+
// This allows slow but steady downloads while catching truly stalled connections
|
|
247
|
+
curl_setopt( $curl, CURLOPT_LOW_SPEED_LIMIT, 1024 ); // 1KB/s minimum
|
|
248
|
+
curl_setopt( $curl, CURLOPT_LOW_SPEED_TIME, 30 ); // Must stay above limit for 30s
|
|
249
|
+
return $curl;
|
|
250
|
+
}, 1, 3);
|
|
251
|
+
`
|
|
252
|
+
});
|
|
253
|
+
muPlugins.push({
|
|
254
|
+
filename: "0-tmp-fix-hide-plugins-spinner.php",
|
|
255
|
+
content: `<?php
|
|
256
|
+
// This is a temporary fix for a page-optimize bug that causes spinner icons to show all the time in the plugins list auto-update column
|
|
257
|
+
|
|
258
|
+
add_action( 'admin_enqueue_scripts', 'studio_patch_auto_update_spinner_style', 999 );
|
|
259
|
+
function studio_patch_auto_update_spinner_style() {
|
|
260
|
+
$current_screen = get_current_screen();
|
|
261
|
+
if ( isset( $current_screen->id ) && 'plugins' === $current_screen->id ) {
|
|
262
|
+
wp_add_inline_style(
|
|
263
|
+
'dashicons',
|
|
264
|
+
'.toggle-auto-update .dashicons.hidden { display: none; }'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
`
|
|
269
|
+
});
|
|
270
|
+
muPlugins.push({
|
|
271
|
+
filename: "0-sqlite-command.php",
|
|
272
|
+
content: `<?php
|
|
273
|
+
// Ensure SQLite command can find the plugin
|
|
274
|
+
add_filter( 'sqlite_command_sqlite_plugin_directories', function( $directories ) {
|
|
275
|
+
$directories[] = '/wordpress/wp-content/mu-plugins/sqlite-database-integration';
|
|
276
|
+
return $directories;
|
|
277
|
+
} );
|
|
278
|
+
`
|
|
279
|
+
});
|
|
280
|
+
muPlugins.push({
|
|
281
|
+
filename: "0-studio-cli-commands.php",
|
|
282
|
+
content: `<?php
|
|
283
|
+
/**
|
|
284
|
+
* Studio WP-CLI Commands
|
|
285
|
+
*
|
|
286
|
+
* Provides custom WP-CLI commands for Studio functionality.
|
|
287
|
+
*/
|
|
288
|
+
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Gets theme details for the current site.
|
|
294
|
+
*
|
|
295
|
+
* ## EXAMPLES
|
|
296
|
+
*
|
|
297
|
+
* wp studio get-theme-details
|
|
298
|
+
*
|
|
299
|
+
* @when after_wp_load
|
|
300
|
+
*/
|
|
301
|
+
WP_CLI::add_command( 'studio get-theme-details', function() {
|
|
302
|
+
$theme = wp_get_theme();
|
|
303
|
+
$result = [
|
|
304
|
+
'name' => $theme->get( 'Name' ),
|
|
305
|
+
'path' => $theme->get_stylesheet_directory(),
|
|
306
|
+
'slug' => $theme->get_stylesheet(),
|
|
307
|
+
'isBlockTheme' => $theme->is_block_theme(),
|
|
308
|
+
'supportsWidgets' => current_theme_supports( 'widgets' ),
|
|
309
|
+
'supportsMenus' => (bool) ( get_registered_nav_menus() || current_theme_supports( 'menus' ) ),
|
|
310
|
+
];
|
|
311
|
+
echo json_encode( $result );
|
|
312
|
+
} );
|
|
313
|
+
`
|
|
314
|
+
});
|
|
315
|
+
muPlugins.push({
|
|
316
|
+
filename: "0-studio-admin-api.php",
|
|
317
|
+
content: `<?php
|
|
318
|
+
/**
|
|
319
|
+
* Studio Admin API
|
|
320
|
+
*
|
|
321
|
+
* Provides a persistent endpoint for admin operations that can reuse
|
|
322
|
+
* the already-loaded WordPress instance, avoiding the overhead of
|
|
323
|
+
* loading wp-load.php multiple times.
|
|
324
|
+
*
|
|
325
|
+
* This endpoint should only be accessible locally.
|
|
326
|
+
*/
|
|
327
|
+
|
|
328
|
+
// Check if this is an API request before WordPress routing
|
|
329
|
+
$is_api_request = isset( $_GET['studio-admin-api'] ) ||
|
|
330
|
+
strpos( $_SERVER['REQUEST_URI'] ?? '', 'studio-admin-api' ) !== false;
|
|
331
|
+
|
|
332
|
+
if ( ! $is_api_request ) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
add_action( 'plugins_loaded', function() {
|
|
337
|
+
|
|
338
|
+
// Security: Only allow POST requests with the correct action
|
|
339
|
+
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' || empty( $_POST['action'] ) ) {
|
|
340
|
+
status_header( 400 );
|
|
341
|
+
header( 'Content-Type: application/json' );
|
|
342
|
+
echo json_encode( [ 'error' => 'Invalid request' ] );
|
|
343
|
+
exit;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
$action = $_POST['action'];
|
|
347
|
+
$result = null;
|
|
348
|
+
|
|
349
|
+
switch ( $action ) {
|
|
350
|
+
case 'set_admin_password':
|
|
351
|
+
$has_password = ! empty( $_POST['password'] );
|
|
352
|
+
$username = ! empty( $_POST['username'] ) ? sanitize_user( $_POST['username'] ) : 'admin';
|
|
353
|
+
// Fallback to 'admin' if sanitize_user() strips all characters (e.g., !@#$%)
|
|
354
|
+
if ( empty( $username ) ) {
|
|
355
|
+
$username = 'admin';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
$user = get_user_by( 'login', $username );
|
|
359
|
+
$provided_email = ! empty( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : '';
|
|
360
|
+
|
|
361
|
+
if ( $user ) {
|
|
362
|
+
if ( $has_password ) {
|
|
363
|
+
wp_set_password( $_POST['password'], $user->ID );
|
|
364
|
+
}
|
|
365
|
+
if ( $provided_email ) {
|
|
366
|
+
wp_update_user( array( 'ID' => $user->ID, 'user_email' => $provided_email ) );
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
// Creating a new user requires a password
|
|
370
|
+
if ( ! $has_password ) {
|
|
371
|
+
status_header( 400 );
|
|
372
|
+
header( 'Content-Type: application/json' );
|
|
373
|
+
echo json_encode( [ 'error' => 'Password is required to create a new admin user' ] );
|
|
374
|
+
exit;
|
|
375
|
+
}
|
|
376
|
+
// WordPress doesn't support renaming user_login, so we create a new admin user.
|
|
377
|
+
// The old user is left intact — this is intentional.
|
|
378
|
+
// Generate a unique email to avoid conflicts with existing users
|
|
379
|
+
$email = $provided_email ? $provided_email : 'admin@localhost.com';
|
|
380
|
+
if ( ! $provided_email ) {
|
|
381
|
+
$counter = 1;
|
|
382
|
+
while ( email_exists( $email ) && $counter < 100 ) {
|
|
383
|
+
$email = 'admin' . $counter . '@localhost.com';
|
|
384
|
+
$counter++;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
$user_data = array(
|
|
389
|
+
'user_login' => $username,
|
|
390
|
+
'user_pass' => $_POST['password'],
|
|
391
|
+
'user_email' => $email,
|
|
392
|
+
'role' => 'administrator',
|
|
393
|
+
);
|
|
394
|
+
$insert_result = wp_insert_user( $user_data );
|
|
395
|
+
if ( is_wp_error( $insert_result ) ) {
|
|
396
|
+
status_header( 400 );
|
|
397
|
+
header( 'Content-Type: application/json' );
|
|
398
|
+
echo json_encode( [ 'error' => $insert_result->get_error_message() ] );
|
|
399
|
+
exit;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Store the admin username in options for auto-login to use
|
|
403
|
+
update_option( 'studio_admin_username', $username );
|
|
404
|
+
$result = [ 'success' => true ];
|
|
405
|
+
break;
|
|
406
|
+
|
|
407
|
+
default:
|
|
408
|
+
status_header( 400 );
|
|
409
|
+
header( 'Content-Type: application/json' );
|
|
410
|
+
echo json_encode( [ 'error' => 'Unknown action' ] );
|
|
411
|
+
exit;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
status_header( 200 );
|
|
415
|
+
header( 'Content-Type: application/json' );
|
|
416
|
+
echo json_encode( $result );
|
|
417
|
+
exit;
|
|
418
|
+
}, 1 );
|
|
419
|
+
`
|
|
420
|
+
});
|
|
421
|
+
muPlugins.push({
|
|
422
|
+
filename: "0-auto-login.php",
|
|
423
|
+
content: `<?php
|
|
424
|
+
/**
|
|
425
|
+
* Auto-Login Endpoint
|
|
426
|
+
*
|
|
427
|
+
* Provides /studio-auto-login endpoint for automatic authentication
|
|
428
|
+
* Usage: /studio-auto-login?redirect_to=/wp-admin/
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
// Intercept requests to /studio-auto-login
|
|
432
|
+
add_action( 'init', function() {
|
|
433
|
+
$request_uri = $_SERVER['REQUEST_URI'] ?? '';
|
|
434
|
+
if ( strpos( $request_uri, '/studio-auto-login' ) === false ) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if ( is_user_logged_in() ) {
|
|
439
|
+
$redirect_url = isset( $_GET['redirect_to'] ) ? $_GET['redirect_to'] : home_url();
|
|
440
|
+
wp_safe_redirect( $redirect_url );
|
|
441
|
+
exit;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
$username = get_option( 'studio_admin_username', 'admin' );
|
|
445
|
+
$user = get_user_by( 'login', $username );
|
|
446
|
+
if ( ! $user ) {
|
|
447
|
+
wp_die( 'Auto-login failed: admin user not found' );
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
wp_set_current_user( $user->ID, $user->user_login );
|
|
451
|
+
wp_set_auth_cookie( $user->ID, true, is_ssl() );
|
|
452
|
+
do_action( 'wp_login', $user->user_login, $user );
|
|
453
|
+
$redirect_url = isset( $_GET['redirect_to'] ) ? $_GET['redirect_to'] : home_url();
|
|
454
|
+
wp_safe_redirect( $redirect_url );
|
|
455
|
+
exit;
|
|
456
|
+
|
|
457
|
+
}, 1 );
|
|
458
|
+
`
|
|
459
|
+
});
|
|
460
|
+
return muPlugins;
|
|
461
|
+
}
|
|
462
|
+
async function createMuPluginsDirectory(options) {
|
|
463
|
+
try {
|
|
464
|
+
const tempDir = await mkdtemp(join(tmpdir(), "studio-mu-plugins-"));
|
|
465
|
+
const muPlugins = getStandardMuPlugins({
|
|
466
|
+
isWpAutoUpdating: options.isWpAutoUpdating
|
|
467
|
+
});
|
|
468
|
+
for (const plugin of muPlugins) {
|
|
469
|
+
const pluginPath = join(tempDir, plugin.filename);
|
|
470
|
+
await writeFile(pluginPath, plugin.content);
|
|
471
|
+
}
|
|
472
|
+
return tempDir;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
throw new Error(`Failed to create mu-plugins directory: ${error}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function getMuPlugins(options) {
|
|
478
|
+
const studioMuPluginsHostPath = await createMuPluginsDirectory(options);
|
|
479
|
+
const loaderMuPluginHostPath = await createLoaderMuPlugin();
|
|
480
|
+
return [studioMuPluginsHostPath, loaderMuPluginHostPath];
|
|
481
|
+
}
|
|
482
|
+
const LEGACY_MU_PLUGIN_FILENAMES = [
|
|
483
|
+
// Current mu-plugins (from getStandardMuPlugins)
|
|
484
|
+
"0-allowed-redirect-hosts.php",
|
|
485
|
+
"0-auto-login.php",
|
|
486
|
+
"0-check-theme-availability.php",
|
|
487
|
+
"0-deactivate-jetpack-modules.php",
|
|
488
|
+
"0-disable-auto-updates.php",
|
|
489
|
+
"0-enable-auto-updates.php",
|
|
490
|
+
"0-http-request-timeout.php",
|
|
491
|
+
"0-https-for-reverse-proxy.php",
|
|
492
|
+
"0-permalinks.php",
|
|
493
|
+
"0-redirect-to-siteurl-constant.php",
|
|
494
|
+
"0-sqlite-command.php",
|
|
495
|
+
"0-studio-admin-api.php",
|
|
496
|
+
"0-studio-cli-commands.php",
|
|
497
|
+
"0-suppress-dns-get-record-warnings.php",
|
|
498
|
+
"0-thumbnails.php",
|
|
499
|
+
"0-tmp-fix-hide-plugins-spinner.php",
|
|
500
|
+
"0-tmp-fix-qm-plugin-sapi.php",
|
|
501
|
+
"0-wp-admin-trailing-slash.php",
|
|
502
|
+
// Retired mu-plugins from older Studio versions
|
|
503
|
+
"0-32bit-integer-warnings.php",
|
|
504
|
+
"0-dns-functions.php",
|
|
505
|
+
"0-sqlite.php",
|
|
506
|
+
"0-wp-config-constants-polyfill.php"
|
|
507
|
+
];
|
|
508
|
+
async function cleanupLegacyMuPlugins(sitePath) {
|
|
509
|
+
const muPluginsDir = join(sitePath, "wp-content", "mu-plugins");
|
|
510
|
+
let entries;
|
|
511
|
+
try {
|
|
512
|
+
entries = await readdir(muPluginsDir);
|
|
513
|
+
} catch {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const legacySet = new Set(LEGACY_MU_PLUGIN_FILENAMES);
|
|
517
|
+
const filesToRemove = entries.filter((name) => legacySet.has(name));
|
|
518
|
+
await Promise.all(
|
|
519
|
+
filesToRemove.map(async (name) => {
|
|
520
|
+
try {
|
|
521
|
+
await unlink(join(muPluginsDir, name));
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
export {
|
|
528
|
+
cleanupLegacyMuPlugins as c,
|
|
529
|
+
getMuPlugins as g
|
|
530
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { __ } from "@wordpress/i18n";
|
|
2
|
+
var cjs = {};
|
|
3
|
+
var hasRequiredCjs;
|
|
4
|
+
function requireCjs() {
|
|
5
|
+
if (hasRequiredCjs) return cjs;
|
|
6
|
+
hasRequiredCjs = 1;
|
|
7
|
+
(function(exports$1) {
|
|
8
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
9
|
+
exports$1.generatePassword = exports$1.EXTRA_SPECIAL_CHARS = exports$1.SPECIAL_CHARS = exports$1.DIGITS = exports$1.ALPHABET = void 0;
|
|
10
|
+
exports$1.ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
11
|
+
exports$1.DIGITS = "0123456789";
|
|
12
|
+
exports$1.SPECIAL_CHARS = "!@#$%^&*()";
|
|
13
|
+
exports$1.EXTRA_SPECIAL_CHARS = "-_ []{}<>~`+=,.;:/?|";
|
|
14
|
+
function generatePassword({ length = 24, useNumbers = true, useSpecialChars = true, useExtraSpecialChars = false } = {}) {
|
|
15
|
+
let characterPool = exports$1.ALPHABET;
|
|
16
|
+
if (useNumbers) {
|
|
17
|
+
characterPool += exports$1.DIGITS;
|
|
18
|
+
}
|
|
19
|
+
if (useSpecialChars) {
|
|
20
|
+
characterPool += exports$1.SPECIAL_CHARS;
|
|
21
|
+
}
|
|
22
|
+
if (useExtraSpecialChars) {
|
|
23
|
+
characterPool += exports$1.EXTRA_SPECIAL_CHARS;
|
|
24
|
+
}
|
|
25
|
+
const randomNumber = new Uint8Array(1);
|
|
26
|
+
let password = "";
|
|
27
|
+
for (let i = 0; i < length; i++) {
|
|
28
|
+
do {
|
|
29
|
+
globalThis.crypto.getRandomValues(randomNumber);
|
|
30
|
+
} while (randomNumber[0] >= characterPool.length);
|
|
31
|
+
password += characterPool[randomNumber[0]];
|
|
32
|
+
}
|
|
33
|
+
return password;
|
|
34
|
+
}
|
|
35
|
+
exports$1.generatePassword = generatePassword;
|
|
36
|
+
})(cjs);
|
|
37
|
+
return cjs;
|
|
38
|
+
}
|
|
39
|
+
var cjsExports = /* @__PURE__ */ requireCjs();
|
|
40
|
+
function createPassword() {
|
|
41
|
+
return encodePassword(cjsExports.generatePassword());
|
|
42
|
+
}
|
|
43
|
+
function encodePassword(password) {
|
|
44
|
+
const bytes = new TextEncoder().encode(password);
|
|
45
|
+
const binary = String.fromCharCode(...bytes);
|
|
46
|
+
return btoa(binary);
|
|
47
|
+
}
|
|
48
|
+
function decodePassword(encodedPassword) {
|
|
49
|
+
const binary = atob(encodedPassword);
|
|
50
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
51
|
+
return new TextDecoder().decode(bytes);
|
|
52
|
+
}
|
|
53
|
+
function validateAdminEmail(email) {
|
|
54
|
+
if (!email.trim()) {
|
|
55
|
+
return __("Admin email cannot be empty.");
|
|
56
|
+
}
|
|
57
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
58
|
+
return __("Please enter a valid email address.");
|
|
59
|
+
}
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
function validateAdminUsername(username) {
|
|
63
|
+
if (!username.trim()) {
|
|
64
|
+
return __("Admin username cannot be empty.");
|
|
65
|
+
}
|
|
66
|
+
if (!/^[a-zA-Z0-9_.@-]+$/.test(username)) {
|
|
67
|
+
return __("Username can only contain letters, numbers, and _.@- characters.");
|
|
68
|
+
}
|
|
69
|
+
if (username.length > 60) {
|
|
70
|
+
return __("Username must be 60 characters or fewer.");
|
|
71
|
+
}
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
validateAdminEmail as a,
|
|
76
|
+
createPassword as c,
|
|
77
|
+
decodePassword as d,
|
|
78
|
+
encodePassword as e,
|
|
79
|
+
validateAdminUsername as v
|
|
80
|
+
};
|