-
Notifications
You must be signed in to change notification settings - Fork 135
Show plugin cards when external requests are disabled #2190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from 4 commits
d1dae91
d40dca2
727be0c
d49280c
9db2772
2c2777f
661fa92
0e26be7
19f15f0
e79cc1c
e4e34af
2270f60
9598ba3
9ae95ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
/** | ||
* Integration test simulating the real issue scenario. | ||
* | ||
* This test simulates what happens when: | ||
* 1. External requests are disabled in WordPress (WP_HTTP_BLOCK_EXTERNAL = true) | ||
* 2. Performance Lab plugin tries to fetch plugin information | ||
* 3. Our fallback should kick in and return local plugin data | ||
*/ | ||
|
||
// Set up the WordPress mock environment | ||
define( 'WP_HTTP_BLOCK_EXTERNAL', true ); | ||
define( 'WP_ACCESSIBLE_HOSTS', '' ); // No allowed hosts | ||
define( 'ABSPATH', __DIR__ . '/mock-wp/' ); | ||
define( 'MINUTE_IN_SECONDS', 60 ); | ||
define( 'HOUR_IN_SECONDS', 3600 ); | ||
|
||
// Mock WordPress functions | ||
function wp_strip_all_tags( $string ) { | ||
return strip_tags( $string ); | ||
} | ||
|
||
function get_transient( $key ) { | ||
// Simulate no cached data | ||
return false; | ||
} | ||
|
||
function set_transient( $key, $data, $expiration ) { | ||
// Mock - just return true | ||
return true; | ||
} | ||
|
||
function is_wp_error( $thing ) { | ||
return $thing instanceof WP_Error; | ||
} | ||
|
||
function __( $text, $domain = 'default' ) { | ||
return $text; | ||
} | ||
|
||
function plugins_api( $action, $args ) { | ||
// Simulate API being blocked/unavailable | ||
return new WP_Error( 'http_request_failed', 'A valid URL was not provided.' ); | ||
} | ||
|
||
function get_plugins( $plugin_folder = '' ) { | ||
// Simulate locally installed Performance Lab plugins | ||
return array( | ||
'webp-uploads/load.php' => array( | ||
'Name' => 'Modern Image Formats', | ||
'Description' => 'Converts images to modern formats like WebP and AVIF during upload and delivery to improve site performance.', | ||
'Version' => '2.0.0', | ||
'Author' => 'WordPress Performance Team', | ||
'RequiresWP' => '6.0', | ||
'RequiresPHP' => '7.4', | ||
), | ||
'optimization-detective/load.php' => array( | ||
'Name' => 'Optimization Detective', | ||
'Description' => 'Provides infrastructure for gathering optimization insights to improve site performance.', | ||
'Version' => '0.7.0', | ||
'Author' => 'WordPress Performance Team', | ||
'RequiresWP' => '6.5', | ||
'RequiresPHP' => '7.2', | ||
), | ||
'embed-optimizer/load.php' => array( | ||
'Name' => 'Embed Optimizer', | ||
'Description' => 'Optimizes the performance of embeds by lazy-loading iframes and scripts.', | ||
'Version' => '0.3.0', | ||
'Author' => 'WordPress Performance Team', | ||
'RequiresWP' => '6.5', | ||
'RequiresPHP' => '7.2', | ||
), | ||
); | ||
} | ||
|
||
function is_plugin_active( $plugin_file ) { | ||
// Simulate some plugins being active | ||
$active = array( | ||
'webp-uploads/load.php', | ||
'optimization-detective/load.php', | ||
); | ||
return in_array( $plugin_file, $active, true ); | ||
} | ||
|
||
function wp_array_slice_assoc( $array, $keys ) { | ||
return array_intersect_key( $array, array_flip( $keys ) ); | ||
} | ||
|
||
class WP_Error { | ||
private $error_code; | ||
private $error_message; | ||
|
||
public function __construct( $code, $message ) { | ||
$this->error_code = $code; | ||
$this->error_message = $message; | ||
} | ||
|
||
public function get_error_code() { | ||
return $this->error_code; | ||
} | ||
|
||
public function get_error_message() { | ||
return $this->error_message; | ||
} | ||
} | ||
|
||
// Include the functions we need to test | ||
require_once __DIR__ . '/plugins/performance-lab/includes/admin/local-plugin-fallback.php'; | ||
|
||
// Mock the standalone plugin data function | ||
function perflab_get_standalone_plugins() { | ||
return array( 'webp-uploads', 'optimization-detective', 'embed-optimizer' ); | ||
} | ||
|
||
// Include the modified plugins.php file (with our fallback integration) | ||
// We'll mock just the specific function we modified | ||
function perflab_query_plugin_info_with_fallback( $plugin_slug ) { | ||
// This simulates the modified perflab_query_plugin_info function | ||
|
||
// Check if external requests are blocked and if we should use local fallback. | ||
$should_use_fallback = perflab_are_external_requests_blocked(); | ||
|
||
if ( $should_use_fallback ) { | ||
// Try to get local plugin data instead. | ||
$local_data = perflab_get_local_plugin_fallback_data( array( $plugin_slug ) ); | ||
if ( isset( $local_data[ $plugin_slug ] ) ) { | ||
return $local_data[ $plugin_slug ]; | ||
} | ||
} | ||
|
||
// If fallback didn't work, proceed with API (which will fail) | ||
$response = plugins_api( 'query_plugins', array( | ||
'author' => 'wordpressdotorg', | ||
'tag' => 'performance', | ||
)); | ||
|
||
if ( is_wp_error( $response ) ) { | ||
// Try local fallback as backup | ||
$local_fallback_data = perflab_get_local_plugin_fallback_data( perflab_get_standalone_plugins() ); | ||
|
||
if ( ! empty( $local_fallback_data ) && isset( $local_fallback_data[ $plugin_slug ] ) ) { | ||
return $local_fallback_data[ $plugin_slug ]; | ||
} | ||
|
||
return $response; // Return the error | ||
} | ||
|
||
return array(); // Should not reach here in our test | ||
} | ||
|
||
echo "=== Integration Test: Issue #2189 Scenario ===\n\n"; | ||
|
||
echo "Scenario: External requests are disabled, Performance Lab plugins are installed locally.\n"; | ||
echo "Expected: Plugin cards should still be shown using local plugin data.\n\n"; | ||
|
||
echo "1. External requests blocked: " . (perflab_are_external_requests_blocked() ? 'YES' : 'NO') . "\n"; | ||
echo "2. Testing plugin info retrieval for 'webp-uploads'...\n"; | ||
|
||
$result = perflab_query_plugin_info_with_fallback( 'webp-uploads' ); | ||
|
||
if ( is_wp_error( $result ) ) { | ||
echo " ✗ FAIL: Got WP_Error: " . $result->get_error_message() . "\n"; | ||
echo " This means the fallback did not work!\n"; | ||
} else { | ||
echo " ✓ SUCCESS: Got plugin data from local fallback!\n"; | ||
echo " Plugin Name: " . $result['name'] . "\n"; | ||
echo " Plugin Version: " . $result['version'] . "\n"; | ||
echo " Is Active: " . ($result['is_active'] ? 'Yes' : 'No') . "\n"; | ||
echo " Fallback Used: " . ($result['fallback_local'] ? 'Yes' : 'No') . "\n"; | ||
echo " Description: " . substr( $result['short_description'], 0, 60 ) . "...\n"; | ||
} | ||
|
||
echo "\n3. Testing for non-installed plugin...\n"; | ||
$result_missing = perflab_query_plugin_info_with_fallback( 'nonexistent-plugin' ); | ||
|
||
if ( is_wp_error( $result_missing ) ) { | ||
echo " ✓ CORRECT: Non-installed plugin correctly returns error\n"; | ||
} else { | ||
echo " ✗ UNEXPECTED: Non-installed plugin returned data somehow\n"; | ||
} | ||
|
||
echo "\n=== Test Conclusion ===\n"; | ||
echo "If the above shows SUCCESS for locally installed plugins and CORRECT for non-installed,\n"; | ||
echo "then our fix for issue #2189 is working properly!\n"; | ||
echo "\nThis means users with WP_HTTP_BLOCK_EXTERNAL=true will still see their\n"; | ||
echo "locally installed Performance Lab plugins in the Settings > Performance screen.\n"; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these functions can be put directly into |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,195 @@ | ||||||
<?php | ||||||
/** | ||||||
* Local Plugin Fallback functionality for Performance Lab. | ||||||
* | ||||||
* Provides fallback functionality to show plugin cards for locally installed | ||||||
* performance-related plugins when external API requests are disabled or fail. | ||||||
* | ||||||
* @package performance-lab | ||||||
* @since 4.0.1 | ||||||
|
||||||
*/ | ||||||
|
||||||
// @codeCoverageIgnoreStart | ||||||
if ( ! defined( 'ABSPATH' ) ) { | ||||||
exit; // Exit if accessed directly. | ||||||
} | ||||||
// @codeCoverageIgnoreEnd | ||||||
|
||||||
/** | ||||||
* Gets local plugin information for Performance Lab standalone plugins. | ||||||
* | ||||||
* This function provides fallback data when external API requests to WordPress.org | ||||||
* are disabled or fail, allowing the Performance Lab interface to still show | ||||||
* cards for locally installed performance plugins. | ||||||
* | ||||||
* @since 4.0.1 | ||||||
* | ||||||
* @param string[] $plugin_slugs Array of plugin slugs to get local info for. | ||||||
* @return array<string, array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], version: string, fallback_local: bool, is_installed: bool, is_active: bool}> Local plugin data keyed by slug. | ||||||
*/ | ||||||
function perflab_get_local_plugin_fallback_data( array $plugin_slugs ): array { | ||||||
// Ensure we have access to plugin functions. | ||||||
if ( ! function_exists( 'get_plugins' ) ) { | ||||||
require_once ABSPATH . 'wp-admin/includes/plugin.php'; | ||||||
} | ||||||
|
||||||
$local_plugins = get_plugins(); | ||||||
$fallback_data = array(); | ||||||
|
||||||
foreach ( $plugin_slugs as $plugin_slug ) { | ||||||
// Look for plugin files that match this slug. | ||||||
$plugin_file = perflab_find_local_plugin_file( $local_plugins, $plugin_slug ); | ||||||
|
||||||
if ( ! $plugin_file ) { | ||||||
continue; // Plugin not installed locally. | ||||||
} | ||||||
|
||||||
$plugin_headers = $local_plugins[ $plugin_file ]; | ||||||
$is_active = is_plugin_active( $plugin_file ); | ||||||
|
||||||
// Build normalized plugin data similar to WordPress.org API response. | ||||||
$fallback_data[ $plugin_slug ] = array( | ||||||
'name' => $plugin_headers['Name'] ?? $plugin_slug, | ||||||
'slug' => $plugin_slug, | ||||||
'short_description' => perflab_sanitize_plugin_description( $plugin_headers['Description'] ?? '' ), | ||||||
|
||||||
'requires' => $plugin_headers['RequiresWP'] ?? false, | ||||||
'requires_php' => $plugin_headers['RequiresPHP'] ?? false, | ||||||
'requires_plugins' => perflab_parse_requires_plugins( $plugin_headers ), | ||||||
'version' => $plugin_headers['Version'] ?? '0.0.0', | ||||||
'fallback_local' => true, // Flag to identify this as local fallback data. | ||||||
|
||||||
'is_installed' => true, | ||||||
'is_active' => $is_active, | ||||||
); | ||||||
} | ||||||
|
||||||
return $fallback_data; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Finds the plugin file for a given slug among installed plugins. | ||||||
* | ||||||
* @since 4.0.1 | ||||||
* | ||||||
* @param array<string, array<string, string>> $local_plugins Array from get_plugins(). | ||||||
* @param string $plugin_slug Plugin slug to find. | ||||||
* @return string|false Plugin file path relative to plugins directory, or false if not found. | ||||||
*/ | ||||||
function perflab_find_local_plugin_file( array $local_plugins, string $plugin_slug ) { | ||||||
foreach ( $local_plugins as $plugin_file => $plugin_data ) { | ||||||
// Extract directory name from plugin file path. | ||||||
$plugin_dir = strtok( $plugin_file, '/' ); | ||||||
|
||||||
if ( $plugin_dir === $plugin_slug ) { | ||||||
return $plugin_file; | ||||||
} | ||||||
} | ||||||
|
||||||
return false; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Sanitizes and truncates plugin description for display. | ||||||
* | ||||||
* @since 4.0.1 | ||||||
* | ||||||
* @param string $description Raw plugin description. | ||||||
* @return string Sanitized and truncated description. | ||||||
*/ | ||||||
function perflab_sanitize_plugin_description( string $description ): string { | ||||||
|
||||||
if ( empty( $description ) ) { | ||||||
return ''; | ||||||
} | ||||||
|
||||||
// Strip all HTML tags and decode entities. | ||||||
$description = wp_strip_all_tags( $description ); | ||||||
$description = html_entity_decode( $description, ENT_QUOTES, 'UTF-8' ); | ||||||
|
||||||
// Truncate to reasonable length for short description. | ||||||
if ( mb_strlen( $description ) > 200 ) { | ||||||
$description = mb_substr( $description, 0, 200 ) . '...'; | ||||||
|
||||||
} | ||||||
|
||||||
return trim( $description ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Parses the requires_plugins from plugin headers. | ||||||
* | ||||||
* This attempts to extract required plugins from various possible header formats. | ||||||
* | ||||||
* @since 4.0.1 | ||||||
* | ||||||
* @param array<string, string> $plugin_headers Plugin headers array. | ||||||
* @return string[] Array of required plugin slugs. | ||||||
*/ | ||||||
function perflab_parse_requires_plugins( array $plugin_headers ): array { | ||||||
$requires_plugins = array(); | ||||||
|
||||||
// Check for RequiresPlugins header (WordPress 6.5+). | ||||||
if ( ! empty( $plugin_headers['RequiresPlugins'] ) ) { | ||||||
$plugins = array_map( 'trim', explode( ',', $plugin_headers['RequiresPlugins'] ) ); | ||||||
$requires_plugins = array_merge( $requires_plugins, $plugins ); | ||||||
} | ||||||
|
||||||
// For known Performance Lab plugins, add their specific dependencies. | ||||||
$plugin_name = $plugin_headers['Name'] ?? ''; | ||||||
|
||||||
// Embed Optimizer has a soft dependency on Optimization Detective. | ||||||
if ( false !== strpos( $plugin_name, 'Embed Optimizer' ) ) { | ||||||
|
||||||
$requires_plugins[] = 'optimization-detective'; | ||||||
} | ||||||
|
||||||
// Image Prioritizer has a soft dependency on Optimization Detective. | ||||||
|
// Image Prioritizer has a soft dependency on Optimization Detective. | |
// Image Prioritizer has a hard dependency on Optimization Detective. |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? Couldn't we just go ahead and try to make the request? And if it's blocked or if it's failed due to wordpress.org being down, we can then fall back to the locally installed plugins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the function is useful because when it returns true we can have an admin notice or some message on the screen that explains why the plugin information is not available.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to make this into an array and then use the in_array()
function.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? The function is defined in this same plugin, correct?
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test file shouldn't be located here. It should be part of the
performance-lab
test suite.