Skip to content

Commit 36013ef

Browse files
committed
Patch WordPress files with JavaScript APIs instead of a huge eval()
1 parent fed433e commit 36013ef

File tree

3 files changed

+44
-138
lines changed

3 files changed

+44
-138
lines changed

esbuild-packages.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ const baseConfig = {
5555
target: ['chrome90', 'firefox60', 'safari11'],
5656
bundle: true,
5757
external: ['xmlhttprequest'],
58+
loader: {
59+
'.php': 'text',
60+
},
5861
}
5962

6063
function getInternalDependencies() {

packages/php-wasm/src/php.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export default class PHP {
4040
this.PHPModule = PHPModule;
4141

4242
this.mkdirTree = PHPModule.FS.mkdirTree;
43-
this.readFile = PHPModule.FS.readFile;
43+
const textDecoder = new TextDecoder()
44+
this.readFile = path => textDecoder.decode(PHPModule.FS.readFile(path));
4445
this.writeFile = PHPModule.FS.writeFile;
4546
this.unlink = PHPModule.FS.unlink;
4647
this.pathExists = path => {

packages/wordpress-wasm/src/worker-thread.js

Lines changed: 39 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -56,146 +56,48 @@ async function loadWordPress({ php, workerEnv }) {
5656
}
5757

5858
function patchWordPressFiles(php, absoluteUrl) {
59-
// @TODO: do this via JavaScript
60-
const result = php.run(`<?php
61-
file_put_contents( "${DOCROOT}/.absolute-url", "${absoluteUrl}" );
62-
if ( ! file_exists( "${DOCROOT}/.wordpress-patched" ) ) {
63-
// Patching WordPress in the worker provides a faster feedback loop than
64-
// rebuilding it every time. Follow the example below to patch WordPress
65-
// before the first request is dispatched:
66-
//
67-
// file_put_contents(
68-
// '${DOCROOT}/wp-content/db.php',
69-
// str_replace(
70-
// '$exploded_parts = $values_data;',
71-
// '$exploded_parts = array( $values_data );',
72-
// file_get_contents('${DOCROOT}/wp-content/db.php')
73-
// )
74-
// );
75-
76-
// WORKAROUND:
77-
// For some reason, the in-browser WordPress doesn't respect the site
78-
// URL preset during the installation. Also, it disables the block editing
79-
// experience by default.
80-
file_put_contents(
81-
'${DOCROOT}/wp-includes/plugin.php',
82-
file_get_contents('${DOCROOT}/wp-includes/plugin.php') . "\n"
83-
.'add_filter( "option_home", function($url) { return getenv("HOST") ?: file_get_contents(__DIR__ . "/../.absolute-url"); }, 10000 );' . "\n"
84-
.'add_filter( "option_siteurl", function($url) { return getenv("HOST") ?: file_get_contents(__DIR__."/../.absolute-url"); }, 10000 );' . "\n"
85-
);
86-
87-
// DISABLE SITE HEALTH:
88-
file_put_contents(
89-
'${DOCROOT}/wp-includes/default-filters.php',
90-
preg_replace(
91-
'!add_filter[^;]+wp_maybe_grant_site_health_caps[^;]+;!i',
92-
'',
93-
file_get_contents('${DOCROOT}/wp-includes/default-filters.php')
94-
)
95-
);
96-
97-
// WORKAROUND:
98-
// The fsockopen transport erroneously reports itself as a working transport. Let's force
99-
// it to report it won't work.
100-
file_put_contents(
101-
'${DOCROOT}/wp-includes/Requests/Transport/fsockopen.php',
102-
str_replace(
103-
'public static function test',
104-
'public static function test( $capabilities = array() ) { return false; } public static function test2',
105-
file_get_contents( '${DOCROOT}/wp-includes/Requests/Transport/fsockopen.php' )
106-
)
107-
);
108-
file_put_contents(
109-
'${DOCROOT}/wp-includes/Requests/Transport/cURL.php',
110-
str_replace(
111-
'public static function test',
112-
'public static function test( $capabilities = array() ) { return false; } public static function test2',
113-
file_get_contents( '${DOCROOT}/wp-includes/Requests/Transport/cURL.php' )
114-
)
59+
function patchFile(path, callback) {
60+
php.writeFile(path,
61+
callback(php.readFile(path))
11562
);
116-
117-
mkdir( '${DOCROOT}/wp-content/mu-plugins' );
118-
file_put_contents(
119-
'${DOCROOT}/wp-content/mu-plugins/requests_transport_fetch.php',
120-
<<<'PATCH'
121-
<?php
122-
class Requests_Transport_Fetch implements Requests_Transport { public $headers = ''; public function __construct() { } public function __destruct() { } public function request( $url, $headers = array(), $data = array(), $options = array() ) { if ( str_contains( $url, '/wp-cron.php' ) ) { return false; } $headers = Requests::flatten( $headers ); if ( ! empty( $data ) ) { $data_format = $options['data_format']; if ( $data_format === 'query' ) { $url = self::format_get( $url, $data ); $data = ''; } elseif ( ! is_string( $data ) ) { $data = http_build_query( $data, null, '&' ); } } $request = json_encode( json_encode( array( 'headers' => $headers, 'data' => $data, 'url' => $url, 'method' => $options['type'], ) ) );
123-
$js = <<<JAVASCRIPT
124-
const request = JSON.parse({$request});
125-
console.log("Requesting " + request.url);
126-
const xhr = new XMLHttpRequest();
127-
xhr.open(
128-
request.method,
129-
request.url,
130-
false // false makes the xhr synchronous
131-
);
132-
xhr.withCredentials = false;
133-
for ( var name in request.headers ) {
134-
if(name.toLowerCase() !== "content-type") {
135-
// xhr.setRequestHeader(name, request.headers[name]);
136-
}
137-
}
138-
xhr.send(request.data);
139-
140-
[
141-
"HTTP/1.1 " + xhr.status + " " + xhr.statusText,
142-
xhr.getAllResponseHeaders(),
143-
"",
144-
xhr.responseText
145-
].join("\\\\r\\\\n");
146-
JAVASCRIPT;
147-
$this->headers = vrzno_eval( $js ); return $this->headers; } public function request_multiple( $requests, $options ) { $responses = array(); $class = get_class( $this ); foreach ( $requests as $id => $request ) { try { $handler = new $class(); $responses[ $id ] = $handler->request( $request['url'], $request['headers'], $request['data'], $request['options'] ); $request['options']['hooks']->dispatch( 'transport.internal.parse_response', array( &$responses[ $id ], $request ) ); } catch ( Requests_Exception $e ) { $responses[ $id ] = $e; } if ( ! is_string( $responses[ $id ] ) ) { $request['options']['hooks']->dispatch( 'multiple.request.complete', array( &$responses[ $id ], $id ) ); } } return $responses; } protected static function format_get( $url, $data ) { if ( ! empty( $data ) ) { $query = ''; $url_parts = parse_url( $url ); if ( empty( $url_parts['query'] ) ) { $url_parts['query'] = ''; } else { $query = $url_parts['query']; } $query .= '&' . http_build_query( $data, null, '&' ); $query = trim( $query, '&' ); if ( empty( $url_parts['query'] ) ) { $url .= '?' . $query; } else { $url = str_replace( $url_parts['query'], $query, $url ); } } return $url; } public static function test( $capabilities = array() ) { if ( ! function_exists( 'vrzno_eval' ) ) { return false; } if ( vrzno_eval( "typeof XMLHttpRequest;" ) !== 'function' ) { return false; } return true; } }
148-
149-
if(defined('USE_FETCH_FOR_REQUESTS') && USE_FETCH_FOR_REQUESTS) {
150-
Requests::add_transport( 'Requests_Transport_Fetch' );
151-
}
152-
PATCH
153-
);
154-
155-
if ( false ) {
156-
// Activate the development plugin.
157-
$file_php_path = '${DOCROOT}/wp-includes/functions.php';
158-
$file_php = file_get_contents($file_php_path);
63+
}
15964

160-
if (strpos($file_php, "start-test-snippet") !== false) {
161-
$file_php = substr($file_php, 0, strpos($file_php, "// start-test-snippet"));
65+
// Force the site URL to be $absoluteUrl:
66+
// Interestingly, it doesn't work when put in a mu-plugin.
67+
patchFile(`${DOCROOT}/wp-includes/plugin.php`, (contents) =>
68+
contents + `
69+
function _wasm_wp_force_site_url() {
70+
return ${JSON.stringify(absoluteUrl)};
16271
}
72+
add_filter( "option_home", '_wasm_wp_force_site_url', 10000 );
73+
add_filter( "option_siteurl", '_wasm_wp_force_site_url', 10000 );
74+
`
75+
);
16376

164-
$file_php .= <<<'ADMIN'
165-
// start-test-snippet
166-
add_action('init', function() {
167-
require_once '${DOCROOT}/wp-admin/includes/plugin.php';
168-
$plugin = 'my-plugin/my-plugin.php';
169-
if(!is_plugin_active($plugin)) {
170-
$result = activate_plugin( $plugin, '', is_network_admin() );
171-
if ( is_wp_error( $result ) ) {
172-
if ( 'unexpected_output' === $result->get_error_code() ) {
173-
var_dump($result->get_error_data());
174-
die();
175-
} else {
176-
wp_die( $result );
177-
}
178-
}
179-
}
180-
});
181-
// end-test-snippet
182-
ADMIN;
183-
184-
file_put_contents(
185-
$file_php_path,
186-
$file_php
187-
);
188-
}
189-
touch("${DOCROOT}/.wordpress-patched");
190-
}
191-
`);
192-
if (result.exitCode !== 0) {
193-
throw new Error(
194-
{
195-
message: 'WordPress setup failed',
196-
result,
197-
},
198-
result.exitCode
199-
);
77+
// Force the fsockopen and cUrl transports to report they don't work:
78+
const transports = [
79+
`${DOCROOT}/wp-includes/Requests/Transport/fsockopen.php`,
80+
`${DOCROOT}/wp-includes/Requests/Transport/cURL.php`
81+
];
82+
for (const transport of transports) {
83+
patchFile(transport, (contents) => contents.replace(
84+
'public static function test',
85+
'public static function test( $capabilities = array() ) { return false; } public static function test2'
86+
));
20087
}
88+
89+
// Disable site health:
90+
patchFile(`${DOCROOT}/wp-includes/default-filters.php`, contents => (
91+
contents.replace(
92+
/add_filter[^;]+wp_maybe_grant_site_health_caps[^;]+;/i,
93+
''
94+
)
95+
));
96+
97+
// Add fetch() transport for HTTP requests():
98+
php.mkdirTree(`${DOCROOT}/wp-content/mu-plugins`);
99+
php.writeFile(
100+
`${DOCROOT}/wp-content/mu-plugins/requests_transport_fetch.php`,
101+
require('../wordpress/requests_transport_fetch.php')
102+
);
201103
}

0 commit comments

Comments
 (0)