Skip to content
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

Use a single WordPress.org API request to get information for all plugins #1562

Merged
Merged
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 46 additions & 38 deletions plugins/performance-lab/includes/admin/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,66 @@
* @return array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], download_link: string, version: string}|WP_Error Array of plugin data or WP_Error if failed.
*/
function perflab_query_plugin_info( string $plugin_slug ) {
$plugin = get_transient( 'perflab_plugin_info_' . $plugin_slug );

if ( is_array( $plugin ) ) {
/**
* Validated (mostly) plugin data.
*
* @var array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], download_link: string, version: string} $plugin
*/
return $plugin;
$transient_key = 'perflab_plugins_info';
$plugins = get_transient( $transient_key );

if ( is_array( $plugins ) && isset( $plugins[ $plugin_slug ] ) ) {
Copy link
Member

@felixarntz felixarntz Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second check here is problematic. We can't check that in the same clause, otherwise a missing plugin will lead to endless re-issuing of the API request and bypassing the cache.

If the cache data is an array, we know it's a cache hit. So then we need to check on that data if the $plugin_slug is not set, and if not return the same error as below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixarntz I have implemented this, could you please take a look.

return $plugins[ $plugin_slug ];
}

$fields = array(
'name',
'slug',
'short_description',
'requires',
'requires_php',
'requires_plugins',
'download_link',
'version', // Needed by install_plugin_install_status().
);
$request = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[author]=wordpressdotorg&request[tag]=performance&request[per_page]=100' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use plugins_api() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter Thanks for reviewing this PR. I have not used plugins_api() because this does not support passing multiple specific slugs (which would be ideal) . Please let me know if I am missing anything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plugins_api() is a wrapper for any API calls to https://api.wordpress.org/plugins/, so you can use it here too. It's only the API itself that doesn't support passing multiple specific slugs, but we're not doing that here anyway.

You can call the plugins_api() function with the query_plugins action and provide the relevant request arguments.

Copy link
Member

@westonruter westonruter Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work fine:

$response = plugins_api(
	'query_plugins',
	array(
		'author'   => 'wordpressdotorg',
		'tag'      => 'performance',
		'per_page' => 100,
	)
);

I can see that $response->plugins is populated as expected.

Copy link
Contributor Author

@narenin narenin Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixarntz @westonruter Thanks for the feedback, I have implemented the same.


$plugin = plugins_api(
'plugin_information',
array(
'slug' => $plugin_slug,
'fields' => array_fill_keys( $fields, true ),
)
);

if ( is_wp_error( $plugin ) ) {
return $plugin;
if ( is_wp_error( $request ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better variable name would be $response, right? But I think plugins_api() should be used instead in any case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter I have corrected the same please check.

return new WP_Error( 'api_error', __( 'Failed to retrieve plugins data from WordPress.org API.', 'default' ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the error being returned would be passed through.

This string isn't actually part of core, right? So default can't be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have corrected the same, please check.

}

if ( is_object( $plugin ) ) {
$plugin = (array) $plugin;
$body = wp_remote_retrieve_body( $request );
$data = json_decode( $body, true );

if ( ! isset( $data['plugins'] ) || ! is_array( $data['plugins'] ) ) {
return new WP_Error( 'no_plugins', __( 'No plugins found in the API response.', 'default' ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, is this string actually found in core? If not, then default cannot be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter This has been fixed.

}

// Only store what we need.
$plugin = wp_array_slice_assoc( $plugin, $fields );
$plugins = array();
foreach ( $data['plugins'] as $plugin_data ) {
$plugin_info = wp_array_slice_assoc(
$plugin_data,
array(
'name',
'slug',
'short_description',
'requires',
'requires_php',
'requires_plugins',
'download_link',
'version',
)
);

// Ensure the 'requires_plugins' is always an array.
if ( ! isset( $plugin_info['requires_plugins'] ) || ! is_array( $plugin_info['requires_plugins'] ) ) {
$plugin_info['requires_plugins'] = array();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this code needed? Right below the if $plugin_info['requires_plugins'] is not set, then it is set to false, which will then never occur. Apparently this if statement could be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// Make sure all fields default to false in case another plugin is modifying the response from WordPress.org via the plugins_api filter.
$plugin = array_merge( array_fill_keys( $fields, false ), $plugin );
// Ensure 'requires' and 'requires_php' are either strings or false.
$plugin_info['requires'] = isset( $plugin_info['requires'] ) ? $plugin_info['requires'] : false;
$plugin_info['requires_php'] = isset( $plugin_info['requires_php'] ) ? $plugin_info['requires_php'] : false;
narenin marked this conversation as resolved.
Show resolved Hide resolved

set_transient( 'perflab_plugin_info_' . $plugin_slug, $plugin, HOUR_IN_SECONDS );
$plugins[ $plugin_data['slug'] ] = $plugin_info;
}

set_transient( $transient_key, $plugins, HOUR_IN_SECONDS );

if ( ! isset( $plugins[ $plugin_slug ] ) ) {
return new WP_Error( 'plugin_not_found', __( 'Plugin not found.', 'default' ) );
}

/**
* Validated (mostly) plugin data.
*
* @var array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], download_link: string, version: string} $plugin
* @var array<string, array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], download_link: string, version: string}> $plugins
*/
return $plugin;
return $plugins[ $plugin_slug ];
}

/**
Expand Down
Loading