@@ -16,7 +16,7 @@ export interface PhpunitRunCodeOptions {
1616 env : Record < string , unknown >
1717 wpConfigDefines : Record < string , unknown >
1818 dependencyMounts : string [ ]
19- dependencyPlugins ?: Array < { path : string ; activate : boolean } >
19+ dependencyPlugins ?: Array < { path : string ; pluginFile : string ; activate : boolean ; loadAs : "plugin" | "mu-plugin" } >
2020 bootstrapFiles : string [ ]
2121 preloadFiles ?: string [ ]
2222 bootstrapMode : string
@@ -386,9 +386,10 @@ CONFIG;
386386
387387export function phpunitMultisitePreinstallCode ( options : PhpunitMultisitePreinstallCodeOptions ) : string {
388388 const wpConfigDefines = { ...options . wpConfigDefines }
389- for ( const name of [ "MULTISITE" , "SUBDOMAIN_INSTALL" , "DOMAIN_CURRENT_SITE" , "PATH_CURRENT_SITE" , "SITE_ID_CURRENT_SITE" , "BLOG_ID_CURRENT_SITE" ] ) {
389+ for ( const name of [ "MULTISITE" , "SUBDOMAIN_INSTALL" , "DOMAIN_CURRENT_SITE" , "PATH_CURRENT_SITE" , "SITE_ID_CURRENT_SITE" , "BLOG_ID_CURRENT_SITE" , "WPMU_PLUGIN_DIR" ] ) {
390390 delete wpConfigDefines [ name ]
391391 }
392+ wpConfigDefines . WPMU_PLUGIN_DIR = "/tmp/wp-codebox-preinstall-mu-plugins"
392393 return `error_reporting(E_ALL);
393394ini_set('display_errors', '1');
394395ini_set('display_startup_errors', '1');
@@ -416,6 +417,9 @@ ${phpWpConfigDefineAppenderFunction("pg_append_wp_config_defines", "error_log('S
416417${ managedPhpunitConfigWriterPhp ( ) }
417418
418419pg_apply_env($bench_env);
420+ if (!is_dir('/tmp/wp-codebox-preinstall-mu-plugins') && !mkdir('/tmp/wp-codebox-preinstall-mu-plugins', 0700, true) && !is_dir('/tmp/wp-codebox-preinstall-mu-plugins')) {
421+ throw new RuntimeException('Could not create isolated multisite preinstall mu-plugin directory.');
422+ }
419423$config_path = pg_write_managed_test_config($wp_config_defines, 'wptests_', $database_type);
420424if (!defined('WP_TESTS_MULTISITE')) {
421425 define('WP_TESTS_MULTISITE', true);
@@ -1036,42 +1040,48 @@ function pg_run_load_deps_stage(array $cfg): array {
10361040 if (empty($plugins)) {
10371041 foreach (explode("\n", (string) ($cfg['dep_mounts'] ?? '')) as $path) {
10381042 if (trim($path) !== '') {
1039- $plugins[] = array('path' => $path, 'activate' => true);
1043+ $slug = basename(rtrim(str_replace('\\\\', '/', trim($path)), '/'));
1044+ $plugins[] = array('path' => $path, 'pluginFile' => $slug . '/' . $slug . '.php', 'activate' => true, 'loadAs' => 'plugin');
10401045 }
10411046 }
10421047 }
1043- $plugin_root = realpath(WP_PLUGIN_DIR);
1044- if ($plugin_root === false || !is_dir($plugin_root)) {
1045- throw new RuntimeException('WordPress plugin directory is unavailable while loading managed PHPUnit dependencies');
1046- }
10471048 foreach ($plugins as $plugin) {
1048- if (!is_array($plugin) || !is_string($plugin['path'] ?? null)) {
1049- throw new RuntimeException('managed PHPUnit dependency metadata must contain a plugin path');
1049+ if (!is_array($plugin) || !is_string($plugin['path'] ?? null) || !is_string($plugin['pluginFile'] ?? null)) {
1050+ throw new RuntimeException('managed PHPUnit dependency metadata must contain a plugin path and entrypoint');
1051+ }
1052+ $load_as = ($plugin['loadAs'] ?? 'plugin') === 'mu-plugin' ? 'mu-plugin' : 'plugin';
1053+ $declared_plugin_root = $load_as === 'mu-plugin' ? WPMU_PLUGIN_DIR . '/contained-runtime' : WP_PLUGIN_DIR;
1054+ $plugin_root = realpath($declared_plugin_root);
1055+ if ($plugin_root === false || !is_dir($plugin_root)) {
1056+ throw new RuntimeException('WordPress dependency root is unavailable while loading managed PHPUnit dependencies');
10501057 }
10511058 $dep_mount = rtrim(str_replace('\\\\', '/', trim($plugin['path'])), '/');
10521059 $dep_real = realpath($dep_mount);
10531060 if ($dep_real === false || !is_dir($dep_real) || dirname($dep_real) !== $plugin_root) {
1054- throw new RuntimeException('managed PHPUnit dependency must be a direct child directory of WP_PLUGIN_DIR : ' . $dep_mount);
1061+ throw new RuntimeException('managed PHPUnit dependency must be a direct child of its declared WordPress plugin root : ' . $dep_mount);
10551062 }
1056- if ($dep_mount !== WP_PLUGIN_DIR . '/' . basename($dep_mount)) {
1063+ if ($dep_mount !== $declared_plugin_root . '/' . basename($dep_mount)) {
10571064 throw new RuntimeException('managed PHPUnit dependency path is not canonical: ' . $dep_mount);
10581065 }
1059- $loaded_file = null;
1060- foreach (glob($dep_real . '/*.php') ?: array() as $dep_file) {
1061- if (basename($dep_file) === 'db.php') {
1062- continue;
1063- }
1064- if (strpos(file_get_contents($dep_file), 'Plugin Name:') !== false) {
1065- require_once $dep_file;
1066- $loaded_file = $dep_file;
1066+ $plugin_file = trim(str_replace('\\\\', '/', $plugin['pluginFile']));
1067+ $slug = basename($dep_mount);
1068+ if ($plugin_file === '' || str_starts_with($plugin_file, '/') || str_contains($plugin_file, '..') || !str_starts_with($plugin_file, $slug . '/') || !str_ends_with($plugin_file, '.php')) {
1069+ throw new RuntimeException('managed PHPUnit dependency entrypoint is unsafe or outside its plugin slug: ' . $plugin_file);
1070+ }
1071+ $entrypoint = $dep_real . '/' . substr($plugin_file, strlen($slug) + 1);
1072+ $loaded_file = realpath($entrypoint);
1073+ if ($loaded_file === false || !is_file($loaded_file) || !is_readable($loaded_file) || strpos($loaded_file, rtrim($dep_real, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR) !== 0) {
1074+ throw new RuntimeException('managed PHPUnit dependency entrypoint is not a readable contained file: ' . $plugin_file);
1075+ }
1076+ foreach (array($dep_real . '/vendor/autoload_packages.php', $dep_real . '/vendor/autoload.php') as $composer_autoload) {
1077+ if (is_file($composer_autoload) && is_readable($composer_autoload)) {
1078+ require_once $composer_autoload;
10671079 break;
10681080 }
10691081 }
1070- if ($loaded_file === null) {
1071- throw new RuntimeException('managed PHPUnit dependency has no readable plugin entrypoint: ' . $dep_mount);
1072- }
1082+ require_once $loaded_file;
10731083 $result['loaded'][] = $loaded_file;
1074- if (!empty($plugin['activate'])) {
1084+ if ($load_as === 'plugin' && !empty($plugin['activate'])) {
10751085 $result['activate'][] = $loaded_file;
10761086 }
10771087 }
@@ -1146,7 +1156,7 @@ function pg_activate_plugin_file(string $plugin_file, bool $network_wide): void
11461156 pg_log('PLUGIN_ACTIVATE_BEGIN ' . $plugin_basename . ' ' . pg_diagnostic_context());
11471157 do_action('activate_' . $plugin_basename, $network_wide);
11481158 pg_mark_plugin_active($plugin_basename, $network_wide);
1149- do_action('activated_plugin', $plugin_basename, false, $network_wide);
1159+ do_action('activated_plugin', $plugin_basename, $network_wide);
11501160 pg_log('PLUGIN_ACTIVATE_OK ' . $plugin_basename . ' ' . pg_diagnostic_context());
11511161}
11521162
0 commit comments