WP_Recovery_Mode::is_network_plugin() is meant to make WP_Recovery_Mode::handle_error() ignore errors from network-activated plugins, but it never returns true.
It compares each entry from wp_get_active_network_plugins() against the extension slug followed by a slash:
<?php
$network_plugins = wp_get_active_network_plugins();
foreach ( $network_plugins as $plugin ) {
if ( str_starts_with( $plugin, $extension['slug'] . '/' ) ) {
return true;
}
}
wp_get_active_network_plugins() returns full paths, such as /var/www/wp-content/plugins/my-plugin/my-plugin.php, while the slug from WP_Recovery_Mode::get_extension_for_error() is relative to the plugins directory, such as my-plugin. The prefix never matches, so an error from a network-activated plugin is not rejected with invalid_source.
Single-file plugins such as hello.php could not match even with relative paths, because their slug is the file name and the check requires a trailing slash.
This has been the case since recovery mode was introduced in [44973]. The impact is limited today, because WP_Fatal_Error_Handler::handle() only calls handle_error() when ! is_multisite(), but the check is wrong wherever it is used.
Steps to reproduce
On a multisite install, network-activate a plugin in a directory, for example my-plugin/my-plugin.php.
Call ( new WP_Recovery_Mode() )->handle_error() with an error whose file is inside WP_PLUGIN_DIR . '/my-plugin/'.
Expected: a WP_Error with the code invalid_source. Actual: the error is treated as coming from a pausable plugin (for example non_protected_endpoint).
Proposed fix
Convert each path with plugin_basename() before comparing, and also match a single-file plugin by its basename:
<?php
foreach ( $network_plugins as $plugin ) {
// wp_get_active_network_plugins() returns full paths, while the slug is relative to the plugins directory.
$plugin = plugin_basename( $plugin );
if ( $plugin === $extension['slug'] || str_starts_with( $plugin, $extension['slug'] . '/' ) ) {
return true;
}
}
Found while adding test coverage for WP_Recovery_Mode in PR 14160, where @lancewillett suggested a multisite case confirming that handle_error() rejects an error from a network-activated plugin.
Javascript required