From 4c9333e5eadfd1b5fb5e6ab0780fb18b8f7ae66d Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 1 Jun 2026 15:07:56 -0400 Subject: [PATCH] fix: install local browser packages through PHP --- .../src/class-wp-codebox-abilities.php | 87 ++++++++++++++++++- tests/smoke-wordpress-plugin.php | 2 +- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/wordpress-plugin/src/class-wp-codebox-abilities.php b/packages/wordpress-plugin/src/class-wp-codebox-abilities.php index c4198dd..123eeb2 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-abilities.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-abilities.php @@ -1614,10 +1614,12 @@ private static function browser_component_plugins( array $input, array $declared } $plugins[] = array( - 'url' => $package['url'], - 'slug' => $slug, - 'activate' => true, - 'provenance' => array( + 'url' => $package['url'], + 'slug' => $slug, + 'activate' => true, + 'local_package' => true, + 'sha256' => $package['sha256'], + 'provenance' => array( 'schema' => 'wp-codebox/browser-component-plugin-provenance/v1', 'source' => 'host-component-path', 'sha256' => $package['sha256'], @@ -1855,6 +1857,11 @@ private static function browser_safe_local_package_url( string $url ): string { return 'http://localhost' . $port . $path . $query . $fragment; } + private static function browser_plugin_uses_loopback_url( string $url ): bool { + $parts = wp_parse_url( $url ); + return is_array( $parts ) && 'http' === strtolower( (string) ( $parts['scheme'] ?? '' ) ) && self::is_loopback_host( (string) ( $parts['host'] ?? '' ) ); + } + private static function browser_download_remote_plugin( string $url, string $zip_path, string $slug ): true|WP_Error { $request = function_exists( 'wp_safe_remote_get' ) ? 'wp_safe_remote_get' : ( function_exists( 'wp_remote_get' ) ? 'wp_remote_get' : null ); if ( null === $request ) { @@ -2004,6 +2011,8 @@ private static function normalize_browser_plugins( array $plugins, string $field 'slug' => $slug, 'resource' => $resource, 'activate' => ! array_key_exists( 'activate', $plugin ) || (bool) $plugin['activate'], + 'local_package' => ! empty( $plugin['local_package'] ), + 'sha256' => $sha256, 'ref' => sanitize_text_field( (string) ( $plugin['ref'] ?? '' ) ), 'refType' => sanitize_key( (string) ( $plugin['refType'] ?? '' ) ), 'path' => 'git:directory' === $resource ? ltrim( str_replace( '\\', '/', (string) ( $plugin['path'] ?? '' ) ), '/' ) : '', @@ -2203,6 +2212,14 @@ private static function browser_blueprint_with_runtime( array $blueprint, array } foreach ( $runtime['plugins'] as $plugin ) { + if ( ! empty( $plugin['local_package'] ) && self::browser_plugin_uses_loopback_url( (string) ( $plugin['url'] ?? '' ) ) ) { + $steps[] = array( + 'step' => 'runPHP', + 'code' => self::browser_plugin_install_php( $plugin ), + ); + continue; + } + $plugin_data = array( 'resource' => (string) ( $plugin['resource'] ?? 'url' ), 'url' => $plugin['url'], @@ -2280,6 +2297,68 @@ private static function browser_blueprint_with_runtime( array $blueprint, array return $blueprint; } + /** @param array $plugin Plugin spec. */ + private static function browser_plugin_install_php( array $plugin ): string { + $target_folder = sanitize_key( (string) ( $plugin['targetFolderName'] ?? $plugin['slug'] ?? '' ) ); + if ( '' === $target_folder ) { + $target_folder = 'wp-codebox-runtime-plugin'; + } + + return 'open( $tmp_zip ) ) { + @unlink( $tmp_zip ); + throw new RuntimeException( "Could not open browser plugin package." ); +} + +$plugins_directory = "/wordpress/wp-content/plugins"; +if ( ! is_dir( $plugins_directory ) ) { + mkdir( $plugins_directory, 0777, true ); +} +$zip->extractTo( $plugins_directory ); +$zip->close(); +@unlink( $tmp_zip ); + +if ( $activate ) { + require_once "/wordpress/wp-load.php"; + require_once ABSPATH . "wp-admin/includes/plugin.php"; + $plugins = get_plugins( "/" . $target_folder ); + $plugin_file = ""; + foreach ( array_keys( $plugins ) as $file ) { + $plugin_file = $target_folder . "/" . $file; + break; + } + if ( "" === $plugin_file ) { + throw new RuntimeException( "Browser plugin package entry file is missing." ); + } + $result = activate_plugin( $plugin_file ); + if ( is_wp_error( $result ) ) { + throw new RuntimeException( $result->get_error_message() ); + } +} +'; + } + /** @param array $mu_plugin Mu-plugin spec. */ private static function browser_mu_plugin_install_php( array $mu_plugin ): string { if ( ! empty( $mu_plugin['local_package'] ) ) { diff --git a/tests/smoke-wordpress-plugin.php b/tests/smoke-wordpress-plugin.php index a81fcd9..3857db4 100644 --- a/tests/smoke-wordpress-plugin.php +++ b/tests/smoke-wordpress-plugin.php @@ -622,7 +622,7 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva ); $GLOBALS['wp_codebox_upload_dir'] = $previous_upload_dir; unset( $GLOBALS['wp_codebox_filters']['wp_codebox_browser_plugin_data_url_max_bytes'] ); -$assert( 'browser Playground session rewrites loopback package URLs to localhost', ! is_wp_error( $browser_local_url_package_session ) && str_starts_with( (string) ( $browser_local_url_package_session['plugins'][0]['url'] ?? '' ), 'http://localhost:63498/uploads/wp-codebox/browser-runtime-plugins/generic-caller-plugin-' ) && ! str_starts_with( (string) ( $browser_local_url_package_session['plugins'][0]['url'] ?? '' ), 'data:application/zip;base64,' ) && ( $browser_local_url_package_session['plugins'][0]['url'] ?? '' ) === ( $browser_local_url_package_session['playground']['blueprint']['steps'][1]['pluginData']['url'] ?? '' ) ); +$assert( 'browser Playground session installs loopback package URLs through PHP instead of URL resources', ! is_wp_error( $browser_local_url_package_session ) && str_starts_with( (string) ( $browser_local_url_package_session['plugins'][0]['url'] ?? '' ), 'http://localhost:63498/uploads/wp-codebox/browser-runtime-plugins/generic-caller-plugin-' ) && ! str_starts_with( (string) ( $browser_local_url_package_session['plugins'][0]['url'] ?? '' ), 'data:application/zip;base64,' ) && 'runPHP' === ( $browser_local_url_package_session['playground']['blueprint']['steps'][1]['step'] ?? '' ) && str_contains( (string) ( $browser_local_url_package_session['playground']['blueprint']['steps'][1]['code'] ?? '' ), 'http://localhost:63498/uploads/wp-codebox/browser-runtime-plugins/generic-caller-plugin-' ) && ! isset( $browser_local_url_package_session['playground']['blueprint']['steps'][1]['pluginData'] ) ); $browser_site_blueprint_session = call_user_func( $browser_session_ability['execute_callback'],