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

Support 'wporg_status' and 'wporg_last_updated' as optional wp plugin list fields #382

Merged
merged 21 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ These fields are optionally available:
* file
* auto_update
* author
* wporg_status
* wporg_last_updated

**EXAMPLES**

Expand All @@ -408,6 +410,17 @@ These fields are optionally available:
| hello | inactive | none | 1.6 | 1.7.2 |
+---------+----------------+--------+---------+----------------+

# Check whether plugins are still active on WordPress.org
$ wp plugin list --format=csv --fields=name,wporg_status,wporg_last_updated
+--------------------+--------------+--------------------+
| name | wporg_status | wporg_last_updated |
+--------------------+--------------+--------------------+
| akismet | active | 2023-12-11 |
| user-switching | active | 2023-11-17 |
| wordpress-importer | active | 2023-04-28 |
| local | | |
+--------------------+--------------+--------------------+



### wp plugin path
Expand Down
41 changes: 41 additions & 0 deletions features/plugin-list-wporg-status.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Feature: Check the status of plugins on WordPress.org

@require-wp-5.2
Scenario: Install plugins and check the status on wp.org.
Given a WP install

When I run `wp plugin install wordpress-importer --version=0.5 --force`
And I run `wp plugin install https://downloads.wordpress.org/plugin/no-longer-in-directory.1.0.62.zip`
And a wp-content/plugins/never-wporg/never-wporg.php file:
"""
<?php
/**
* Plugin Name: This plugin was never in the WordPress.org plugin directory
* Version: 2.0.2
*/
"""

When I run `wp plugin list --name=wordpress-importer --field=wporg_last_updated`
Then STDOUT should not be empty
And save STDOUT as {COMMIT_DATE}

When I run `wp plugin list --fields=name,wporg_status`
Then STDOUT should be a table containing rows:
| name | wporg_status |
| wordpress-importer | active |
| no-longer-in-directory | closed |
| never-wporg | |

When I run `wp plugin list --fields=name,wporg_last_updated`
Then STDOUT should be a table containing rows:
| name | wporg_last_updated |
| wordpress-importer | {COMMIT_DATE} |
| no-longer-in-directory | 2017-11-13 |
| never-wporg | |

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 | {COMMIT_DATE} |
| no-longer-in-directory | closed | 2017-11-13 |
| never-wporg | | |
168 changes: 133 additions & 35 deletions src/Plugin_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use WP_CLI\ParsePluginNameInput;
use WP_CLI\Utils;
use WP_CLI\WpOrgApi;

/**
* Manages plugins, including installs, activations, and updates.
Expand Down Expand Up @@ -46,6 +47,10 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
protected $item_type = 'plugin';
protected $upgrade_refresh = 'wp_update_plugins';
protected $upgrade_transient = 'update_plugins';
protected $check_wporg = [
'status' => false,
'last_updated' => false,
];

protected $obj_fields = array(
'name',
Expand Down Expand Up @@ -245,19 +250,23 @@ protected function get_all_items() {
if ( ! empty( $mu_plugin['Description'] ) ) {
$mu_description = $mu_plugin['Description'];
}
$mu_name = Utils\get_plugin_name( $file );
$wporg_info = $this->get_wporg_data( $mu_name );

$items[ $file ] = array(
'name' => Utils\get_plugin_name( $file ),
'status' => 'must-use',
'update' => false,
'update_version' => null,
'update_package' => null,
'version' => $mu_version,
'update_id' => '',
'title' => $mu_title,
'description' => $mu_description,
'file' => $file,
'auto_update' => false,
'name' => $mu_name,
'status' => 'must-use',
'update' => false,
'update_version' => null,
'update_package' => null,
'version' => $mu_version,
'update_id' => '',
'title' => $mu_title,
'description' => $mu_description,
'file' => $file,
'auto_update' => false,
'wporg_status' => $wporg_info['status'],
'wporg_last_updated' => $wporg_info['last_updated'],
);
}

Expand All @@ -266,17 +275,19 @@ protected function get_all_items() {
foreach ( $raw_items as $name => $item_data ) {
$description = ! empty( $raw_data[ $name ][0] ) ? $raw_data[ $name ][0] : '';
$items[ $name ] = [
'name' => $name,
'title' => $item_data['Title'],
'description' => $description,
'status' => 'dropin',
'update' => false,
'update_version' => null,
'update_package' => null,
'update_id' => '',
'file' => $name,
'auto_update' => false,
'author' => $item_data['Author'],
'name' => $name,
'title' => $item_data['Title'],
'description' => $description,
'status' => 'dropin',
'update' => false,
'update_version' => null,
'update_package' => null,
'update_id' => '',
'file' => $name,
'auto_update' => false,
'author' => $item_data['Author'],
'wporg_status' => '',
'wporg_last_updated' => '',
];
}

Expand Down Expand Up @@ -703,29 +714,31 @@ protected function get_item_list() {
$all_update_info = $this->get_update_info();
$update_info = ( isset( $all_update_info->response[ $file ] ) && null !== $all_update_info->response[ $file ] ) ? (array) $all_update_info->response[ $file ] : null;
$name = Utils\get_plugin_name( $file );
$wporg_info = $this->get_wporg_data( $name );

if ( ! isset( $duplicate_names[ $name ] ) ) {
$duplicate_names[ $name ] = array();
}

$duplicate_names[ $name ][] = $file;
$items[ $file ] = [
'name' => $name,
'status' => $this->get_status( $file ),
'update' => (bool) $update_info,
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
'version' => $details['Version'],
'update_id' => $file,
'title' => $details['Name'],
'description' => wordwrap( $details['Description'] ),
'file' => $file,
'auto_update' => in_array( $file, $auto_updates, true ),
'author' => $details['Author'],
'name' => $name,
'status' => $this->get_status( $file ),
'update' => (bool) $update_info,
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
'version' => $details['Version'],
'update_id' => $file,
'title' => $details['Name'],
'description' => wordwrap( $details['Description'] ),
'file' => $file,
'auto_update' => in_array( $file, $auto_updates, true ),
'author' => $details['Author'],
'wporg_status' => $wporg_info['status'],
'wporg_last_updated' => $wporg_info['last_updated'],
];

if ( null === $update_info ) {

// Get info for all plugins that don't have an update.
$plugin_update_info = isset( $all_update_info->no_update[ $file ] ) ? $all_update_info->no_update[ $file ] : null;

Expand All @@ -748,6 +761,64 @@ protected function get_item_list() {
return $items;
}

/**
* Get the wordpress.org status of a plugin.
*
* @param string $plugin_name The plugin slug.
*
* @return string The status of the plugin, includes the last update date.
*/
protected function get_wporg_data( $plugin_name ) {
$data = [
'status' => '',
'last_updated' => '',
];
if ( ! $this->check_wporg['status'] && ! $this->check_wporg['last_updated'] ) {
return $data;
}

if ( $this->check_wporg ) {
try {
$plugin_data = ( new WpOrgApi() )->get_plugin_info( $plugin_name );
} catch ( Exception $e ) {
// Request failed. The plugin is not (active) on .org.
$plugin_data = false;
}
if ( $plugin_data ) {
$data['status'] = 'active';
if ( ! $this->check_wporg['last_updated'] ) {
return $data; // The plugin is active on .org, but we don't need the date.
}
}
// Just because the plugin is not in the api, does not mean it was never on .org.
}

$request = wp_remote_get( "https://plugins.trac.wordpress.org/log/{$plugin_name}/?limit=1&mode=stop_on_copy&format=rss" );
Copy link
Member

Choose a reason for hiding this comment

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

Should we only make this request if we're checking for the date?

Copy link
Member Author

Choose a reason for hiding this comment

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

I made a better check in d7ec274
The answer is, not always,

When the call to api.wp is not succesfull, we need to check the SVN to distinguish between closed and never on .org.

$response_code = wp_remote_retrieve_response_code( $request );
if ( 404 === $response_code ) {
return $data; // This plugin was never on .org, there is no date to check.
}
if ( 'active' !== $data['status'] ) {
$data['status'] = 'closed'; // This plugin was on .org at some point, but not anymore.
}
if ( ! class_exists( 'SimpleXMLElement' ) ) {
WP_CLI::error( "The PHP extension 'SimpleXMLElement' is not available but is required for XML-formatted output." );
}

// Check the last update date.
$r_body = wp_remote_retrieve_body( $request );
if ( str_contains( $r_body, 'pubDate' ) ) {
// Very raw check, not validating the format or anything else.
$xml = simplexml_load_string( $r_body );
$xml_pub_date = $xml->xpath( '//pubDate' );
if ( $xml_pub_date ) {
$data['last_updated'] = wp_date( 'Y-m-d', (string) strtotime( $xml_pub_date[0] ) );
}
}

return $data;
}

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 Expand Up @@ -1188,6 +1259,8 @@ public function delete( $args, $assoc_args = array() ) {
* * file
* * auto_update
* * author
* * wporg_status
* * wporg_last_updated
*
* ## EXAMPLES
*
Expand All @@ -1210,9 +1283,34 @@ public function delete( $args, $assoc_args = array() ) {
* | hello | inactive | none | 1.6 | 1.7.2 |
* +---------+----------------+--------+---------+----------------+
*
* # Check whether plugins are still active on WordPress.org
* $ wp plugin list --format=csv --fields=name,wporg_status,wporg_last_updated
* +--------------------+--------------+--------------------+
* | name | wporg_status | wporg_last_updated |
* +--------------------+--------------+--------------------+
* | akismet | active | 2023-12-11 |
* | user-switching | active | 2023-11-17 |
* | wordpress-importer | active | 2023-04-28 |
* | local | | |
* +--------------------+--------------+--------------------+
*
* @subcommand list
*/
public function list_( $_, $assoc_args ) {
$fields = Utils\get_flag_value( $assoc_args, 'fields' );
if ( ! empty( $fields ) ) {
$fields = explode( ',', $fields );
$this->check_wporg['status'] = in_array( 'wporg_status', $fields, true );
$this->check_wporg['last_updated'] = in_array( 'wporg_last_updated', $fields, true );
}

$field = Utils\get_flag_value( $assoc_args, 'field' );
if ( 'wporg_status' === $field ) {
$this->check_wporg['status'] = true;
} elseif ( 'wporg_last_updated' === $field ) {
$this->check_wporg['last_updated'] = true;
}

parent::_list( $_, $assoc_args );
}

Expand Down