@wp-playground/client 0.0.1

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.
Files changed (3) hide show
  1. package/index.d.ts +427 -0
  2. package/index.js +465 -0
  3. package/package.json +26 -0
package/index.d.ts ADDED
@@ -0,0 +1,427 @@
1
+ // Generated by dts-bundle-generator v7.2.0
2
+
3
+ export type PHPRequestHeaders = Record<string, string>;
4
+ export interface FileInfo {
5
+ key: string;
6
+ name: string;
7
+ type: string;
8
+ data: Uint8Array;
9
+ }
10
+ export interface PHPRequest {
11
+ /**
12
+ * Request path following the domain:port part.
13
+ */
14
+ relativeUri?: string;
15
+ /**
16
+ * Path of the .php file to execute.
17
+ */
18
+ scriptPath?: string;
19
+ /**
20
+ * Request protocol.
21
+ */
22
+ protocol?: string;
23
+ /**
24
+ * Request method. Default: `GET`.
25
+ */
26
+ method?: "GET" | "POST" | "HEAD" | "OPTIONS" | "PATCH" | "PUT" | "DELETE";
27
+ /**
28
+ * Request headers.
29
+ */
30
+ headers?: PHPRequestHeaders;
31
+ /**
32
+ * Request body without the files.
33
+ */
34
+ body?: string;
35
+ /**
36
+ * Uploaded files.
37
+ */
38
+ fileInfos?: FileInfo[];
39
+ /**
40
+ * The code snippet to eval instead of a php file.
41
+ */
42
+ code?: string;
43
+ }
44
+ export interface PHPResponse {
45
+ /**
46
+ * The exit code of the script. `0` is a success, while
47
+ * `1` and `2` indicate an error.
48
+ */
49
+ exitCode: number;
50
+ /**
51
+ * Response body. Contains the output from `echo`,
52
+ * `print`, inline HTML etc.
53
+ */
54
+ body: ArrayBuffer;
55
+ /**
56
+ * PHP errors.
57
+ */
58
+ errors: string;
59
+ /**
60
+ * Response headers.
61
+ */
62
+ headers: Record<string, string[]>;
63
+ /**
64
+ * Response HTTP status code, e.g. 200.
65
+ */
66
+ httpStatusCode: number;
67
+ }
68
+ export type PHPRuntimeId = number;
69
+ export interface WithPHPIniBindings {
70
+ setPhpIniPath(path: string): void;
71
+ setPhpIniEntry(key: string, value: string): void;
72
+ }
73
+ export interface WithCLI {
74
+ /**
75
+ * Starts a PHP CLI session with given arguments.
76
+ *
77
+ * Can only be used when PHP was compiled with the CLI SAPI.
78
+ * Cannot be used in conjunction with `run()`.
79
+ *
80
+ * @param argv - The arguments to pass to the CLI.
81
+ * @returns The exit code of the CLI session.
82
+ */
83
+ cli(argv: string[]): Promise<number>;
84
+ }
85
+ export interface WithNodeFilesystem {
86
+ /**
87
+ * Mounts a Node.js filesystem to a given path in the PHP filesystem.
88
+ *
89
+ * @param settings - The Node.js filesystem settings.
90
+ * @param path - The path to mount the filesystem to.
91
+ * @see {@link https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.mount}
92
+ */
93
+ mount(settings: any, path: string): void;
94
+ }
95
+ export interface WithFilesystem {
96
+ /**
97
+ * Recursively creates a directory with the given path in the PHP filesystem.
98
+ * For example, if the path is `/root/php/data`, and `/root` already exists,
99
+ * it will create the directories `/root/php` and `/root/php/data`.
100
+ *
101
+ * @param path - The directory path to create.
102
+ */
103
+ mkdirTree(path: string): void;
104
+ /**
105
+ * Reads a file from the PHP filesystem and returns it as a string.
106
+ *
107
+ * @throws {@link ErrnoError} – If the file doesn't exist.
108
+ * @param path - The file path to read.
109
+ * @returns The file contents.
110
+ */
111
+ readFileAsText(path: string): string;
112
+ /**
113
+ * Reads a file from the PHP filesystem and returns it as an array buffer.
114
+ *
115
+ * @throws {@link ErrnoError} – If the file doesn't exist.
116
+ * @param path - The file path to read.
117
+ * @returns The file contents.
118
+ */
119
+ readFileAsBuffer(path: string): Uint8Array;
120
+ /**
121
+ * Overwrites data in a file in the PHP filesystem.
122
+ * Creates a new file if one doesn't exist yet.
123
+ *
124
+ * @param path - The file path to write to.
125
+ * @param data - The data to write to the file.
126
+ */
127
+ writeFile(path: string, data: string | Uint8Array): void;
128
+ /**
129
+ * Removes a file from the PHP filesystem.
130
+ *
131
+ * @throws {@link ErrnoError} – If the file doesn't exist.
132
+ * @param path - The file path to remove.
133
+ */
134
+ unlink(path: string): void;
135
+ /**
136
+ * Lists the files and directories in the given directory.
137
+ *
138
+ * @param path - The directory path to list.
139
+ * @returns The list of files and directories in the given directory.
140
+ */
141
+ listFiles(path: string): string[];
142
+ /**
143
+ * Checks if a directory exists in the PHP filesystem.
144
+ *
145
+ * @param path – The path to check.
146
+ * @returns True if the path is a directory, false otherwise.
147
+ */
148
+ isDir(path: string): boolean;
149
+ /**
150
+ * Checks if a file (or a directory) exists in the PHP filesystem.
151
+ *
152
+ * @param path - The file path to check.
153
+ * @returns True if the file exists, false otherwise.
154
+ */
155
+ fileExists(path: string): boolean;
156
+ }
157
+ export interface WithRun {
158
+ /**
159
+ * Dispatches a PHP request.
160
+ * Cannot be used in conjunction with `cli()`.
161
+ *
162
+ * @example
163
+ * ```js
164
+ * const output = php.run('<?php echo "Hello world!";');
165
+ * console.log(output.stdout); // "Hello world!"
166
+ * ```
167
+ *
168
+ * @example
169
+ * ```js
170
+ * console.log(php.run(`<?php
171
+ * $fp = fopen('php://stderr', 'w');
172
+ * fwrite($fp, "Hello, world!");
173
+ * `));
174
+ * // {"exitCode":0,"stdout":"","stderr":["Hello, world!"]}
175
+ * ```
176
+ *
177
+ * @param request - PHP Request data.
178
+ */
179
+ run(request?: PHPRequest): PHPResponse;
180
+ }
181
+ export type MountSettings = {
182
+ root: string;
183
+ mountpoint: string;
184
+ };
185
+ declare class PHP implements WithPHPIniBindings, WithFilesystem, WithNodeFilesystem, WithCLI, WithRun {
186
+ #private;
187
+ /**
188
+ * Initializes a PHP runtime.
189
+ *
190
+ * @internal
191
+ * @param PHPRuntime - Optional. PHP Runtime ID as initialized by loadPHPRuntime.
192
+ */
193
+ constructor(PHPRuntimeId?: PHPRuntimeId);
194
+ initializeRuntime(runtimeId: PHPRuntimeId): void;
195
+ setPhpIniPath(path: string): void;
196
+ setPhpIniEntry(key: string, value: string): void;
197
+ run(request?: PHPRequest): PHPResponse;
198
+ cli(argv: string[]): Promise<number>;
199
+ setSkipShebang(shouldSkip: boolean): void;
200
+ addServerGlobalEntry(key: string, value: string): void;
201
+ mkdirTree(path: string): void;
202
+ readFileAsText(path: string): string;
203
+ readFileAsBuffer(path: string): Uint8Array;
204
+ writeFile(path: string, data: string | Uint8Array): void;
205
+ unlink(path: string): void;
206
+ listFiles(path: string): string[];
207
+ isDir(path: string): boolean;
208
+ fileExists(path: string): boolean;
209
+ mount(settings: MountSettings, path: string): void;
210
+ }
211
+ export type PHPServerRequest = Pick<PHPRequest, "method" | "headers"> & {
212
+ files?: Record<string, File>;
213
+ } & ({
214
+ absoluteUrl: string;
215
+ relativeUrl?: never;
216
+ } | {
217
+ absoluteUrl?: never;
218
+ relativeUrl: string;
219
+ }) & ((Pick<PHPRequest, "body"> & {
220
+ formData?: never;
221
+ }) | {
222
+ body?: never;
223
+ formData: Record<string, unknown>;
224
+ });
225
+ declare class PHPServer {
226
+ #private;
227
+ /**
228
+ * The PHP instance
229
+ */
230
+ php: PHP;
231
+ /**
232
+ * @param php - The PHP instance.
233
+ * @param config - Server configuration.
234
+ */
235
+ constructor(php: PHP, config?: PHPServerConfigation);
236
+ /**
237
+ * Converts a path to an absolute URL based at the PHPServer
238
+ * root.
239
+ *
240
+ * @param path The server path to convert to an absolute URL.
241
+ * @returns The absolute URL.
242
+ */
243
+ pathToInternalUrl(path: string): string;
244
+ /**
245
+ * Converts an absolute URL based at the PHPServer to a relative path
246
+ * without the server pathname and scope.
247
+ *
248
+ * @param internalUrl An absolute URL based at the PHPServer root.
249
+ * @returns The relative path.
250
+ */
251
+ internalUrlToPath(internalUrl: string): string;
252
+ /**
253
+ * The absolute URL of this PHPServer instance.
254
+ */
255
+ get absoluteUrl(): string;
256
+ /**
257
+ * The absolute URL of this PHPServer instance.
258
+ */
259
+ get documentRoot(): string;
260
+ /**
261
+ * Serves the request – either by serving a static file, or by
262
+ * dispatching it to the PHP runtime.
263
+ *
264
+ * @param request - The request.
265
+ * @returns The response.
266
+ */
267
+ request(request: PHPServerRequest): Promise<PHPResponse>;
268
+ }
269
+ export interface PHPServerConfigation {
270
+ /**
271
+ * The directory in the PHP filesystem where the server will look
272
+ * for the files to serve. Default: `/var/www`.
273
+ */
274
+ documentRoot?: string;
275
+ /**
276
+ * Server URL. Used to populate $_SERVER details like HTTP_HOST.
277
+ */
278
+ absoluteUrl?: string;
279
+ /**
280
+ * Callback used by the PHPServer to decide whether
281
+ * the requested path refers to a PHP file or a static file.
282
+ */
283
+ isStaticFilePath?: (path: string) => boolean;
284
+ }
285
+ export interface WithRequest {
286
+ /**
287
+ * Sends the request to the server.
288
+ *
289
+ * When cookies are present in the response, this method stores
290
+ * them and sends them with any subsequent requests.
291
+ *
292
+ * When a redirection is present in the response, this method
293
+ * follows it by discarding a response and sending a subsequent
294
+ * request.
295
+ *
296
+ * @param request - The request.
297
+ * @param redirects - Internal. The number of redirects handled so far.
298
+ * @returns PHPServer response.
299
+ */
300
+ request(request: PHPServerRequest, redirects?: number): Promise<PHPResponse>;
301
+ }
302
+ declare class PHPBrowser implements WithRequest {
303
+ #private;
304
+ server: PHPServer;
305
+ /**
306
+ * @param server - The PHP server to browse.
307
+ * @param config - The browser configuration.
308
+ */
309
+ constructor(server: PHPServer, config?: PHPBrowserConfiguration);
310
+ request(request: PHPServerRequest, redirects?: number): Promise<PHPResponse>;
311
+ }
312
+ export interface PHPBrowserConfiguration {
313
+ /**
314
+ * Should handle redirects internally?
315
+ */
316
+ handleRedirects?: boolean;
317
+ /**
318
+ * The maximum number of redirects to follow internally. Once
319
+ * exceeded, request() will return the redirecting response.
320
+ */
321
+ maxRedirects?: number;
322
+ }
323
+ export interface MonitoredModule {
324
+ dependencyFilename: string;
325
+ dependenciesTotalSize: number;
326
+ }
327
+ declare class EmscriptenDownloadMonitor extends EventTarget {
328
+ #private;
329
+ constructor(modules?: MonitoredModule[]);
330
+ getEmscriptenArgs(): {
331
+ dataFileDownloads: Record<string, any>;
332
+ };
333
+ setModules(modules: MonitoredModule[]): void;
334
+ }
335
+ export interface DownloadProgress {
336
+ /**
337
+ * The number of bytes loaded so far.
338
+ */
339
+ loaded: number;
340
+ /**
341
+ * The total number of bytes to load.
342
+ */
343
+ total: number;
344
+ }
345
+ export type ProgressMode =
346
+ /**
347
+ * Real-time progress is used when we get real-time reports
348
+ * about the progress.
349
+ */
350
+ "REAL_TIME"
351
+ /**
352
+ * Slowly increment progress is used when we don't know how long
353
+ * an operation will take and just want to keep slowly incrementing
354
+ * the progress bar.
355
+ */
356
+ | "SLOWLY_INCREMENT";
357
+ declare class ProgressObserver extends EventTarget {
358
+ #private;
359
+ progress: number;
360
+ mode: ProgressMode;
361
+ caption: string;
362
+ partialObserver(progressBudget: number, caption?: string): (progress: CustomEvent<DownloadProgress>) => void;
363
+ slowlyIncrementBy(progress: number): void;
364
+ get totalProgress(): number;
365
+ }
366
+ export type Promisify<T> = {
367
+ [P in keyof T]: T[P] extends (...args: infer A) => infer R ? R extends void | Promise<any> ? T[P] : (...args: A) => Promise<ReturnType<T[P]>> : Promise<T[P]>;
368
+ };
369
+ export interface WithPathConversion {
370
+ pathToInternalUrl(path: string): Promise<string>;
371
+ internalUrlToPath(internalUrl: string): Promise<string>;
372
+ }
373
+ export interface WithProgress {
374
+ onDownloadProgress(callback: (progress: CustomEvent<ProgressEvent>) => void): Promise<void>;
375
+ }
376
+ declare class PHPClient implements Promisify<WithRequest & WithPHPIniBindings & WithFilesystem & WithRun & WithProgress & WithPathConversion> {
377
+ absoluteUrl: Promise<string>;
378
+ documentRoot: Promise<string>;
379
+ constructor(browser: PHPBrowser, monitor?: EmscriptenDownloadMonitor);
380
+ pathToInternalUrl(path: string): Promise<string>;
381
+ internalUrlToPath(internalUrl: string): Promise<string>;
382
+ onDownloadProgress(callback: (progress: CustomEvent<ProgressEvent>) => void): Promise<void>;
383
+ request(request: PHPServerRequest, redirects?: number): Promise<PHPResponse>;
384
+ run(request?: PHPRequest | undefined): Promise<PHPResponse>;
385
+ setPhpIniPath(path: string): void;
386
+ setPhpIniEntry(key: string, value: string): void;
387
+ mkdirTree(path: string): void;
388
+ readFileAsText(path: string): Promise<string>;
389
+ readFileAsBuffer(path: string): Promise<Uint8Array>;
390
+ writeFile(path: string, data: string | Uint8Array): void;
391
+ unlink(path: string): void;
392
+ listFiles(path: string): Promise<string[]>;
393
+ isDir(path: string): Promise<boolean>;
394
+ fileExists(path: string): Promise<boolean>;
395
+ }
396
+ declare class PlaygroundWorkerClientClass extends PHPClient {
397
+ scope: Promise<string>;
398
+ wordPressVersion: Promise<string>;
399
+ phpVersion: Promise<string>;
400
+ constructor(browser: PHPBrowser, monitor: EmscriptenDownloadMonitor, scope: string, wordPressVersion: string, phpVersion: string);
401
+ getWordPressModuleDetails(): Promise<{
402
+ staticAssetsDirectory: string;
403
+ defaultTheme: any;
404
+ }>;
405
+ }
406
+ declare const publicApi: PlaygroundWorkerClientClass & {
407
+ isReady: () => Promise<void>;
408
+ };
409
+ export type PlaygroundWorkerClient = typeof publicApi;
410
+ export interface WebClientMixin {
411
+ onNavigation(fn: (url: string) => void): Promise<void>;
412
+ goTo(requestedPath: string): Promise<void>;
413
+ getCurrentURL(): Promise<string>;
414
+ setIframeSandboxFlags(flags: string[]): Promise<void>;
415
+ onDownloadProgress: PlaygroundWorkerClient["onDownloadProgress"];
416
+ }
417
+ export type PlaygroundClient = WebClientMixin & PlaygroundWorkerClient;
418
+ export declare function exportFile(playground: PlaygroundClient): Promise<void>;
419
+ export declare function importFile(playground: PlaygroundClient, file: File): Promise<boolean>;
420
+ export declare function login(playground: PlaygroundClient, user?: string, password?: string): Promise<void>;
421
+ export declare function installTheme(playground: PlaygroundClient, themeZipFile: File, options?: any): Promise<void>;
422
+ export declare function installPlugin(playground: PlaygroundClient, pluginZipFile: File, options?: any): Promise<void>;
423
+ export declare function installThemeFromDirectory(playground: PlaygroundClient, themeZipName: string, progressBudget?: number, progress?: ProgressObserver): Promise<void>;
424
+ export declare function installPluginsFromDirectory(playground: PlaygroundClient, pluginsZipNames: string[], progressBudget?: number, progress?: ProgressObserver): Promise<void>;
425
+ export declare function connectPlayground(iframe: HTMLIFrameElement, playgroundUrl: string): Promise<PlaygroundClient>;
426
+
427
+ export {};
package/index.js ADDED
@@ -0,0 +1,465 @@
1
+ import * as m from "comlink";
2
+ import { cloneResponseMonitorProgress as U } from "@wp-playground/php-wasm-progress";
3
+ (function() {
4
+ return typeof window < "u" ? "WEB" : typeof WorkerGlobalScope < "u" && self instanceof WorkerGlobalScope ? "WORKER" : "NODE";
5
+ })();
6
+ function C(e) {
7
+ S();
8
+ const o = e instanceof Worker ? e : m.windowEndpoint(e);
9
+ return m.wrap(o);
10
+ }
11
+ function S() {
12
+ m.transferHandlers.set("EVENT", {
13
+ canHandle: (e) => e instanceof CustomEvent,
14
+ serialize: (e) => [
15
+ {
16
+ detail: e.detail
17
+ },
18
+ []
19
+ ],
20
+ deserialize: (e) => e
21
+ }), m.transferHandlers.set("FUNCTION", {
22
+ canHandle: (e) => typeof e == "function",
23
+ serialize(e) {
24
+ console.debug("[Comlink][Performance] Proxying a function");
25
+ const { port1: o, port2: i } = new MessageChannel();
26
+ return m.expose(e, o), [i, [i]];
27
+ },
28
+ deserialize(e) {
29
+ return e.start(), m.wrap(e);
30
+ }
31
+ });
32
+ }
33
+ (function() {
34
+ return navigator.userAgent.toLowerCase().indexOf("firefox") > -1 ? "iframe" : "webworker";
35
+ })();
36
+ var y = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}, F = {}, q = {
37
+ get exports() {
38
+ return F;
39
+ },
40
+ set exports(e) {
41
+ F = e;
42
+ }
43
+ };
44
+ (function(e, o) {
45
+ (function(i, s) {
46
+ s();
47
+ })(y, function() {
48
+ function i(t, n) {
49
+ return typeof n > "u" ? n = { autoBom: !1 } : typeof n != "object" && (console.warn("Deprecated: Expected third argument to be a object"), n = { autoBom: !n }), n.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type) ? new Blob(["\uFEFF", t], { type: t.type }) : t;
50
+ }
51
+ function s(t, n, c) {
52
+ var r = new XMLHttpRequest();
53
+ r.open("GET", t), r.responseType = "blob", r.onload = function() {
54
+ l(r.response, n, c);
55
+ }, r.onerror = function() {
56
+ console.error("could not download file");
57
+ }, r.send();
58
+ }
59
+ function d(t) {
60
+ var n = new XMLHttpRequest();
61
+ n.open("HEAD", t, !1);
62
+ try {
63
+ n.send();
64
+ } catch {
65
+ }
66
+ return 200 <= n.status && 299 >= n.status;
67
+ }
68
+ function u(t) {
69
+ try {
70
+ t.dispatchEvent(new MouseEvent("click"));
71
+ } catch {
72
+ var n = document.createEvent("MouseEvents");
73
+ n.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), t.dispatchEvent(n);
74
+ }
75
+ }
76
+ var a = typeof window == "object" && window.window === window ? window : typeof self == "object" && self.self === self ? self : typeof y == "object" && y.global === y ? y : void 0, f = a.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent), l = a.saveAs || (typeof window != "object" || window !== a ? function() {
77
+ } : "download" in HTMLAnchorElement.prototype && !f ? function(t, n, c) {
78
+ var r = a.URL || a.webkitURL, p = document.createElement("a");
79
+ n = n || t.name || "download", p.download = n, p.rel = "noopener", typeof t == "string" ? (p.href = t, p.origin === location.origin ? u(p) : d(p.href) ? s(t, n, c) : u(p, p.target = "_blank")) : (p.href = r.createObjectURL(t), setTimeout(function() {
80
+ r.revokeObjectURL(p.href);
81
+ }, 4e4), setTimeout(function() {
82
+ u(p);
83
+ }, 0));
84
+ } : "msSaveOrOpenBlob" in navigator ? function(t, n, c) {
85
+ if (n = n || t.name || "download", typeof t != "string")
86
+ navigator.msSaveOrOpenBlob(i(t, c), n);
87
+ else if (d(t))
88
+ s(t, n, c);
89
+ else {
90
+ var r = document.createElement("a");
91
+ r.href = t, r.target = "_blank", setTimeout(function() {
92
+ u(r);
93
+ });
94
+ }
95
+ } : function(t, n, c, r) {
96
+ if (r = r || open("", "_blank"), r && (r.document.title = r.document.body.innerText = "downloading..."), typeof t == "string")
97
+ return s(t, n, c);
98
+ var p = t.type === "application/octet-stream", h = /constructor/i.test(a.HTMLElement) || a.safari, b = /CriOS\/[\d]+/.test(navigator.userAgent);
99
+ if ((b || p && h || f) && typeof FileReader < "u") {
100
+ var $ = new FileReader();
101
+ $.onloadend = function() {
102
+ var v = $.result;
103
+ v = b ? v : v.replace(/^data:[^;]*;/, "data:attachment/file;"), r ? r.location.href = v : location = v, r = null;
104
+ }, $.readAsDataURL(t);
105
+ } else {
106
+ var g = a.URL || a.webkitURL, w = g.createObjectURL(t);
107
+ r ? r.location = w : location.href = w, r = null, setTimeout(function() {
108
+ g.revokeObjectURL(w);
109
+ }, 4e4);
110
+ }
111
+ });
112
+ a.saveAs = l.saveAs = l, e.exports = l;
113
+ });
114
+ })(q);
115
+ const P = `<?php
116
+
117
+ function generateZipFile($exportPath, $databasePath, $docRoot) {
118
+ $zip = new ZipArchive;
119
+ $res = $zip->open($exportPath, ZipArchive::CREATE);
120
+ if ($res === TRUE) {
121
+ $zip->addFile($databasePath);
122
+ $directories = array();
123
+ $directories[] = $docRoot . '/';
124
+
125
+ while(sizeof($directories)) {
126
+ $dir = array_pop($directories);
127
+
128
+ if ($handle = opendir($dir)) {
129
+ while (false !== ($entry = readdir($handle))) {
130
+ if ($entry == '.' || $entry == '..') {
131
+ continue;
132
+ }
133
+
134
+ $entry = $dir . $entry;
135
+
136
+ if (
137
+ is_dir($entry) &&
138
+ strpos($entry, 'wp-content/database') == false &&
139
+ strpos($entry, 'wp-includes') == false
140
+ ) {
141
+ $directory_path = $entry . '/';
142
+ array_push($directories, $directory_path);
143
+ } else if (is_file($entry)) {
144
+ $zip->addFile($entry);
145
+ }
146
+ }
147
+ closedir($handle);
148
+ }
149
+ }
150
+ $zip->close();
151
+ chmod($exportPath, 0777);
152
+ }
153
+ }
154
+
155
+ function readFileFromZipArchive($pathToZip, $pathToFile) {
156
+ chmod($pathToZip, 0777);
157
+ $zip = new ZipArchive;
158
+ $res = $zip->open($pathToZip);
159
+ if ($res === TRUE) {
160
+ $file = $zip->getFromName($pathToFile);
161
+ echo $file;
162
+ }
163
+ }
164
+
165
+ function importZipFile($pathToZip) {
166
+ $zip = new ZipArchive;
167
+ $res = $zip->open($pathToZip);
168
+ if ($res === TRUE) {
169
+ $counter = 0;
170
+ while ($zip->statIndex($counter)) {
171
+ $file = $zip->statIndex($counter);
172
+ $filePath = $file['name'];
173
+ if (!file_exists(dirname($filePath))) {
174
+ mkdir(dirname($filePath), 0777, true);
175
+ }
176
+ $overwrite = fopen($filePath, 'w');
177
+ fwrite($overwrite, $zip->getFromIndex($counter));
178
+ $counter++;
179
+ }
180
+ $zip->close();
181
+ }
182
+ }
183
+ `, R = "databaseExport.xml", T = "/" + R;
184
+ async function I(e) {
185
+ const o = await e.request({
186
+ relativeUrl: "/wp-admin/export.php?download=true&&content=all"
187
+ }), i = new TextDecoder().decode(
188
+ o.body
189
+ );
190
+ await e.writeFile(T, i);
191
+ const s = await e.wordPressVersion, d = await e.phpVersion, u = await e.documentRoot, a = `wordpress-playground--wp${s}--php${d}.zip`, f = `/${a}`, l = await e.run({
192
+ code: P + ` generateZipFile('${f}', '${T}', '${u}');`
193
+ });
194
+ if (l.exitCode !== 0)
195
+ throw l.errors;
196
+ const t = await e.readFileAsBuffer(a), n = new File([t], a);
197
+ F.saveAs(n);
198
+ }
199
+ async function L(e, o) {
200
+ if (
201
+ // eslint-disable-next-line no-alert
202
+ !confirm(
203
+ "Are you sure you want to import this file? Previous data will be lost."
204
+ )
205
+ )
206
+ return !1;
207
+ const i = await o.arrayBuffer(), s = new Uint8Array(i), d = "/import.zip";
208
+ await e.writeFile(d, s);
209
+ const u = await e.run({
210
+ code: P + ` readFileFromZipArchive('${d}', '${T}');`
211
+ });
212
+ if (u.exitCode !== 0)
213
+ throw u.errors;
214
+ const a = new TextDecoder().decode(
215
+ u.body
216
+ ), f = new File(
217
+ [a],
218
+ R
219
+ ), l = await e.request({
220
+ relativeUrl: "/wp-admin/admin.php?import=wordpress"
221
+ }), n = new DOMParser().parseFromString(
222
+ new TextDecoder().decode(l.body),
223
+ "text/html"
224
+ ).getElementById("import-upload-form")?.getAttribute("action"), c = await e.request({
225
+ relativeUrl: `/wp-admin/${n}`,
226
+ method: "POST",
227
+ files: { import: f }
228
+ }), p = new DOMParser().parseFromString(
229
+ new TextDecoder().decode(c.body),
230
+ "text/html"
231
+ ).querySelector(
232
+ "#wpbody-content form"
233
+ ), h = p?.getAttribute(
234
+ "action"
235
+ ), b = (p?.querySelector(
236
+ "input[name='_wpnonce']"
237
+ )).value, $ = (p?.querySelector(
238
+ "input[name='_wp_http_referer']"
239
+ )).value, g = (p?.querySelector(
240
+ "input[name='import_id']"
241
+ )).value;
242
+ await e.request({
243
+ relativeUrl: h,
244
+ method: "POST",
245
+ formData: {
246
+ _wpnonce: b,
247
+ _wp_http_referer: $,
248
+ import_id: g
249
+ }
250
+ });
251
+ const w = await e.run({
252
+ code: P + ` importZipFile('${d}');`
253
+ });
254
+ if (w.exitCode !== 0)
255
+ throw w.errors;
256
+ return !0;
257
+ }
258
+ async function k(e, o = "admin", i = "password") {
259
+ await e.request({
260
+ relativeUrl: "/wp-login.php"
261
+ }), await e.request({
262
+ relativeUrl: "/wp-login.php",
263
+ method: "POST",
264
+ formData: {
265
+ log: o,
266
+ pwd: i,
267
+ rememberme: "forever"
268
+ }
269
+ });
270
+ }
271
+ function x(e) {
272
+ return new DOMParser().parseFromString(O(e), "text/html");
273
+ }
274
+ function O(e) {
275
+ return new TextDecoder().decode(e.body);
276
+ }
277
+ function A(e) {
278
+ const o = e.split(".").shift().replace("-", " ");
279
+ return o.charAt(0).toUpperCase() + o.slice(1).toLowerCase();
280
+ }
281
+ async function z(e, o, i = {}) {
282
+ const s = "activate" in i ? i.activate : !0, d = await e.request({
283
+ relativeUrl: "/wp-admin/theme-install.php"
284
+ }), u = x(d), a = new FormData(
285
+ u.querySelector(".wp-upload-form")
286
+ ), { themezip: f, ...l } = Object.fromEntries(
287
+ a.entries()
288
+ ), t = await e.request({
289
+ relativeUrl: "/wp-admin/update.php?action=upload-theme",
290
+ method: "POST",
291
+ formData: l,
292
+ files: { themezip: o }
293
+ });
294
+ if (s) {
295
+ const n = x(t), c = n.querySelector(
296
+ "#wpbody-content > .wrap"
297
+ );
298
+ if (c?.textContent?.includes(
299
+ "Theme installation failed."
300
+ )) {
301
+ console.error(c?.textContent);
302
+ return;
303
+ }
304
+ const r = n.querySelector(
305
+ "#wpbody-content .activatelink, .update-from-upload-actions .button.button-primary"
306
+ );
307
+ if (!r) {
308
+ console.error('The "activate" button was not found.');
309
+ return;
310
+ }
311
+ const p = r.attributes.getNamedItem("href").value, h = new URL(
312
+ p,
313
+ await e.pathToInternalUrl("/wp-admin/")
314
+ ).toString();
315
+ await e.request({
316
+ absoluteUrl: h
317
+ });
318
+ }
319
+ }
320
+ async function D(e, o, i = {}) {
321
+ const s = "activate" in i ? i.activate : !0, d = await e.request({
322
+ relativeUrl: "/wp-admin/plugin-install.php?tab=upload"
323
+ }), u = x(d), a = new FormData(
324
+ u.querySelector(".wp-upload-form")
325
+ ), { pluginzip: f, ...l } = Object.fromEntries(
326
+ a.entries()
327
+ ), t = await e.request({
328
+ relativeUrl: "/wp-admin/update.php?action=upload-plugin",
329
+ method: "POST",
330
+ formData: l,
331
+ files: { pluginzip: o }
332
+ });
333
+ if (s) {
334
+ const r = x(t).querySelector("#wpbody-content .button.button-primary").attributes.getNamedItem("href").value, p = new URL(
335
+ r,
336
+ await e.pathToInternalUrl("/wp-admin/")
337
+ ).toString();
338
+ await e.request({
339
+ absoluteUrl: p
340
+ });
341
+ }
342
+ async function n(c, r) {
343
+ return await e.writeFile(
344
+ c,
345
+ r(await e.readFileAsText(c))
346
+ );
347
+ }
348
+ await e.isDir("/wordpress/wp-content/plugins/gutenberg") && !await e.fileExists("/wordpress/.gutenberg-patched") && (await e.writeFile("/wordpress/.gutenberg-patched", "1"), await n(
349
+ "/wordpress/wp-content/plugins/gutenberg/build/block-editor/index.js",
350
+ (c) => c.replace(
351
+ /srcDoc:("[^"]+"|[^,]+)/g,
352
+ 'src:"/wp-includes/empty.html"'
353
+ )
354
+ ), await n(
355
+ "/wordpress/wp-content/plugins/gutenberg/build/block-editor/index.min.js",
356
+ (c) => c.replace(
357
+ /srcDoc:("[^"]+"|[^,]+)/g,
358
+ 'src:"/wp-includes/empty.html"'
359
+ )
360
+ ));
361
+ }
362
+ async function j(e, o, i = 0, s) {
363
+ let d = await fetch("/plugin-proxy?theme=" + o);
364
+ if (s && (d = U(
365
+ d,
366
+ s.partialObserver(
367
+ i / 2,
368
+ `Installing ${A(o)} theme...`
369
+ )
370
+ ), s.slowlyIncrementBy(i / 2)), d.status === 200) {
371
+ const u = new File([await d.blob()], o);
372
+ try {
373
+ await z(e, u);
374
+ } catch (a) {
375
+ console.error(
376
+ `Proceeding without the ${o} theme. Could not install it in wp-admin. The original error was: ${a}`
377
+ ), console.error(a);
378
+ }
379
+ } else
380
+ console.error(
381
+ `Proceeding without the ${o} theme. Could not download the zip bundle from https://downloads.wordpress.org/themes/${o} – Is the file name correct?`
382
+ );
383
+ }
384
+ async function B(e, o, i = 0, s) {
385
+ const d = new E(), u = new E(), a = i / o.length;
386
+ await new Promise((f) => {
387
+ for (const l of o)
388
+ d.enqueue(async () => {
389
+ let t = await fetch(
390
+ "/plugin-proxy?plugin=" + l
391
+ );
392
+ return s && (t = U(
393
+ t,
394
+ s.partialObserver(
395
+ a * 0.66,
396
+ `Installing ${A(
397
+ l
398
+ )} plugin...`
399
+ )
400
+ )), t.status !== 200 ? (console.error(
401
+ `Proceeding without the ${l} plugin. Could not download the zip bundle from https://downloads.wordpress.org/plugin/${l} – Is the file name correct?`
402
+ ), null) : new File([await t.blob()], l);
403
+ });
404
+ d.addEventListener("resolved", (l) => {
405
+ u.enqueue(async () => {
406
+ if (l.detail) {
407
+ s?.slowlyIncrementBy(a * 0.33);
408
+ try {
409
+ await D(e, l.detail);
410
+ } catch (t) {
411
+ console.error(
412
+ `Proceeding without the ${l.detail.name} plugin. Could not install it in wp-admin. The original error was: ${t}`
413
+ ), console.error(t);
414
+ }
415
+ }
416
+ });
417
+ }), u.addEventListener("empty", () => {
418
+ u.resolved === o.length && f(null);
419
+ });
420
+ });
421
+ }
422
+ class E extends EventTarget {
423
+ #e = [];
424
+ #t = !1;
425
+ #n = 0;
426
+ get resolved() {
427
+ return this.#n;
428
+ }
429
+ async enqueue(o) {
430
+ this.#e.push(o), this.#o();
431
+ }
432
+ async #o() {
433
+ if (!this.#t)
434
+ try {
435
+ for (this.#t = !0; this.#e.length; ) {
436
+ const o = this.#e.shift();
437
+ if (!o)
438
+ break;
439
+ const i = await o();
440
+ ++this.#n, this.dispatchEvent(
441
+ new CustomEvent("resolved", { detail: i })
442
+ );
443
+ }
444
+ } finally {
445
+ this.#t = !1, this.dispatchEvent(new CustomEvent("empty"));
446
+ }
447
+ }
448
+ }
449
+ async function M(e, o) {
450
+ e.src = o, await new Promise((s) => {
451
+ e.addEventListener("load", s, !1);
452
+ });
453
+ const i = C(e.contentWindow);
454
+ return await i.absoluteUrl, i;
455
+ }
456
+ export {
457
+ M as connectPlayground,
458
+ I as exportFile,
459
+ L as importFile,
460
+ D as installPlugin,
461
+ B as installPluginsFromDirectory,
462
+ z as installTheme,
463
+ j as installThemeFromDirectory,
464
+ k as login
465
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@wp-playground/client",
3
+ "version": "0.0.1",
4
+ "description": "WordPress Playground client",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/WordPress/wordpress-playground"
8
+ },
9
+ "homepage": "https://developer.wordpress.org/playground",
10
+ "author": "The WordPress contributors",
11
+ "contributors": [
12
+ {
13
+ "name": "Adam Zielinski",
14
+ "email": "adam@adamziel.com",
15
+ "url": "https://github.com/adamziel"
16
+ }
17
+ ],
18
+ "license": "Apache-2.0",
19
+ "type": "module",
20
+ "main": "index.js",
21
+ "types": "index.d.ts",
22
+ "dependencies": {
23
+ "comlink": "4.4.1",
24
+ "@wp-playground/php-wasm-progress": "0.0.1"
25
+ }
26
+ }