Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions features/plugin-list-wporg-status.feature
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,72 @@ Feature: Check the status of plugins on WordPress.org
| no-longer-in-directory | closed | 2017-11-13 |
| never-wporg | | |

@require-wp-5.2
Scenario: The wp.org last updated date for an active plugin does not depend on a second, rate-limited request
Given a WP install
And I run `wp plugin install wordpress-importer --version=0.5 --force`
And that HTTP requests to https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Blocale%5D=en_US&request%5Bslug%5D=wordpress-importer will respond with:
"""
HTTP/1.1 200
Content-Type: application/json

{
"name": "WordPress Importer",
"slug": "wordpress-importer",
"last_updated": "2025-09-26 9:07pm GMT"
}
"""
# plugins.trac.wordpress.org is known to rate-limit this scrape (HTTP 429), with no
# pubDate in the response body. wporg_last_updated must still resolve correctly for an
# active plugin, because the date is meant to come from the plugin-info API response
# above, not from a second request to trac.
And that HTTP requests to https://plugins.trac.wordpress.org/log/wordpress-importer/?limit=1&mode=stop_on_copy&format=rss will respond with:
"""
HTTP/1.1 429
Content-Type: text/html

<html><body>429 Too Many Requests</body></html>
"""

When I run `wp plugin list --fields=name,wporg_status,wporg_last_updated`
Then STDOUT should be a table containing rows:
| name | wporg_status | wporg_last_updated |
| wordpress-importer | active | 2025-09-26 |

@require-wp-5.2
Scenario: The wp.org last updated date falls back to the trac log when the plugin-info API omits it
Given a WP install
And I run `wp plugin install wordpress-importer --version=0.5 --force`
And that HTTP requests to https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Blocale%5D=en_US&request%5Bslug%5D=wordpress-importer will respond with:
"""
HTTP/1.1 200
Content-Type: application/json

{
"name": "WordPress Importer",
"slug": "wordpress-importer"
}
"""
And that HTTP requests to https://plugins.trac.wordpress.org/log/wordpress-importer/?limit=1&mode=stop_on_copy&format=rss will respond with:
"""
HTTP/1.1 200
Content-Type: application/rss+xml;charset=utf-8

<?xml version="1.0"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<item>
<pubDate>Fri, 26 Sep 2025 21:07:26 GMT</pubDate>
</item>
</channel>
</rss>
"""

When I run `wp plugin list --fields=name,wporg_status,wporg_last_updated`
Then STDOUT should be a table containing rows:
| name | wporg_status | wporg_last_updated |
| wordpress-importer | active | 2025-09-26 |

@less-than-wp-5.3
Scenario: The wp.org last updated date is still rendered on WordPress < 5.3
Given a WP install
Expand Down
47 changes: 35 additions & 12 deletions src/Plugin_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,17 @@ protected function get_wporg_data( $plugin_name ) {
if ( ! $this->check_wporg['last_updated'] ) {
return $data; // The plugin is active on .org, but we don't need the date.
}
// The plugins API already reports when the plugin was last updated, so use
// that instead of also scraping the trac log, which is rate-limited and
// otherwise unnecessary here. If the value can't be parsed, fall through
// to the trac log below rather than defaulting to today's date.
if ( ! empty( $plugin_data['last_updated'] ) && is_string( $plugin_data['last_updated'] ) ) {
$pub_date = strtotime( $plugin_data['last_updated'] );
if ( false !== $pub_date ) {
$data['last_updated'] = $this->format_wporg_last_updated( $pub_date );
return $data;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
// Just because the plugin is not in the api, does not mean it was never on .org.
}
Expand All @@ -1152,18 +1163,9 @@ protected function get_wporg_data( $plugin_name ) {
if ( false !== $xml ) {
$xml_pub_date = $xml->xpath( '//pubDate' );
if ( $xml_pub_date ) {
$pub_date = strtotime( $xml_pub_date[0] ) ?: null;

if ( function_exists( 'wp_date' ) ) {
$data['last_updated'] = wp_date( 'Y-m-d', $pub_date );
} else {
// wp_date() is WordPress 5.3+. get_date_from_gmt() renders in the site
// timezone the same way, without date_i18n()'s pre-5.3 contract of
// expecting a timestamp that already has the offset added to it.
$data['last_updated'] = get_date_from_gmt(
gmdate( 'Y-m-d H:i:s', $pub_date ?? time() ),
'Y-m-d'
);
$pub_date = strtotime( $xml_pub_date[0] );
if ( false !== $pub_date ) {
$data['last_updated'] = $this->format_wporg_last_updated( $pub_date );
}
}
}
Expand All @@ -1172,6 +1174,27 @@ protected function get_wporg_data( $plugin_name ) {
return $data;
}

/**
* Formats a wp.org publish date as a `Y-m-d` string in the site's configured timezone.
*
* @param int $pub_date Unix timestamp.
*
* @return string|false
*/
private function format_wporg_last_updated( $pub_date ) {
if ( function_exists( 'wp_date' ) ) {
return wp_date( 'Y-m-d', $pub_date );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// wp_date() is WordPress 5.3+. get_date_from_gmt() renders in the site
// timezone the same way, without date_i18n()'s pre-5.3 contract of
// expecting a timestamp that already has the offset added to it.
return get_date_from_gmt(
gmdate( 'Y-m-d H:i:s', $pub_date ),
'Y-m-d'
);
}

protected function filter_item_list( $items, $args ) {
$basenames = wp_list_pluck( $this->fetcher->get_many( $args ), 'file' );
return Utils\pick_fields( $items, $basenames );
Expand Down