Skip to content

Commit 12618c7

Browse files
Support 'wporg_status' and 'wporg_last_updated' as optional wp plugin list fields (#382)
* Plugins show their .org status. * Split the fields to check for wporg and date seperatatly. * Temp behat file to check * Added hints for behat that need to be implemented. * phpcs fixes. * Also add wp-org checks to mu-plugins. * Added behat tests. * Allow to select the status on wporg as a single field. * Added Behat tests. * Better check for active plugins. * Renamed wporg keys * Replaced the api.wp call with the appropriate class. * replaced test plugin Akismet, because it's failing php version tests. * Fixed a missed rename. * Dropins need a hardcoded exception for wp.org status. * Removed all no_wp_org key words. * Fix the feature name in Behat * Add an example to the command doc * Regenerate README --------- Co-authored-by: Daniel Bachhuber <daniel.bachhuber@automattic.com>
1 parent 506a20a commit 12618c7

File tree

3 files changed

+187
-35
lines changed

3 files changed

+187
-35
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ These fields are optionally available:
386386
* file
387387
* auto_update
388388
* author
389+
* wporg_status
390+
* wporg_last_updated
389391

390392
**EXAMPLES**
391393

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

413+
# Check whether plugins are still active on WordPress.org
414+
$ wp plugin list --format=csv --fields=name,wporg_status,wporg_last_updated
415+
+--------------------+--------------+--------------------+
416+
| name | wporg_status | wporg_last_updated |
417+
+--------------------+--------------+--------------------+
418+
| akismet | active | 2023-12-11 |
419+
| user-switching | active | 2023-11-17 |
420+
| wordpress-importer | active | 2023-04-28 |
421+
| local | | |
422+
+--------------------+--------------+--------------------+
423+
411424

412425

413426
### wp plugin path
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Feature: Check the status of plugins on WordPress.org
2+
3+
@require-wp-5.2
4+
Scenario: Install plugins and check the status on wp.org.
5+
Given a WP install
6+
7+
When I run `wp plugin install wordpress-importer --version=0.5 --force`
8+
And I run `wp plugin install https://downloads.wordpress.org/plugin/no-longer-in-directory.1.0.62.zip`
9+
And a wp-content/plugins/never-wporg/never-wporg.php file:
10+
"""
11+
<?php
12+
/**
13+
* Plugin Name: This plugin was never in the WordPress.org plugin directory
14+
* Version: 2.0.2
15+
*/
16+
"""
17+
18+
When I run `wp plugin list --name=wordpress-importer --field=wporg_last_updated`
19+
Then STDOUT should not be empty
20+
And save STDOUT as {COMMIT_DATE}
21+
22+
When I run `wp plugin list --fields=name,wporg_status`
23+
Then STDOUT should be a table containing rows:
24+
| name | wporg_status |
25+
| wordpress-importer | active |
26+
| no-longer-in-directory | closed |
27+
| never-wporg | |
28+
29+
When I run `wp plugin list --fields=name,wporg_last_updated`
30+
Then STDOUT should be a table containing rows:
31+
| name | wporg_last_updated |
32+
| wordpress-importer | {COMMIT_DATE} |
33+
| no-longer-in-directory | 2017-11-13 |
34+
| never-wporg | |
35+
36+
When I run `wp plugin list --fields=name,wporg_status,wporg_last_updated`
37+
Then STDOUT should be a table containing rows:
38+
| name | wporg_status | wporg_last_updated |
39+
| wordpress-importer | active | {COMMIT_DATE} |
40+
| no-longer-in-directory | closed | 2017-11-13 |
41+
| never-wporg | | |

src/Plugin_Command.php

Lines changed: 133 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use WP_CLI\ParsePluginNameInput;
44
use WP_CLI\Utils;
5+
use WP_CLI\WpOrgApi;
56

67
/**
78
* Manages plugins, including installs, activations, and updates.
@@ -46,6 +47,10 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
4647
protected $item_type = 'plugin';
4748
protected $upgrade_refresh = 'wp_update_plugins';
4849
protected $upgrade_transient = 'update_plugins';
50+
protected $check_wporg = [
51+
'status' => false,
52+
'last_updated' => false,
53+
];
4954

5055
protected $obj_fields = array(
5156
'name',
@@ -245,19 +250,23 @@ protected function get_all_items() {
245250
if ( ! empty( $mu_plugin['Description'] ) ) {
246251
$mu_description = $mu_plugin['Description'];
247252
}
253+
$mu_name = Utils\get_plugin_name( $file );
254+
$wporg_info = $this->get_wporg_data( $mu_name );
248255

249256
$items[ $file ] = array(
250-
'name' => Utils\get_plugin_name( $file ),
251-
'status' => 'must-use',
252-
'update' => false,
253-
'update_version' => null,
254-
'update_package' => null,
255-
'version' => $mu_version,
256-
'update_id' => '',
257-
'title' => $mu_title,
258-
'description' => $mu_description,
259-
'file' => $file,
260-
'auto_update' => false,
257+
'name' => $mu_name,
258+
'status' => 'must-use',
259+
'update' => false,
260+
'update_version' => null,
261+
'update_package' => null,
262+
'version' => $mu_version,
263+
'update_id' => '',
264+
'title' => $mu_title,
265+
'description' => $mu_description,
266+
'file' => $file,
267+
'auto_update' => false,
268+
'wporg_status' => $wporg_info['status'],
269+
'wporg_last_updated' => $wporg_info['last_updated'],
261270
);
262271
}
263272

@@ -266,17 +275,19 @@ protected function get_all_items() {
266275
foreach ( $raw_items as $name => $item_data ) {
267276
$description = ! empty( $raw_data[ $name ][0] ) ? $raw_data[ $name ][0] : '';
268277
$items[ $name ] = [
269-
'name' => $name,
270-
'title' => $item_data['Title'],
271-
'description' => $description,
272-
'status' => 'dropin',
273-
'update' => false,
274-
'update_version' => null,
275-
'update_package' => null,
276-
'update_id' => '',
277-
'file' => $name,
278-
'auto_update' => false,
279-
'author' => $item_data['Author'],
278+
'name' => $name,
279+
'title' => $item_data['Title'],
280+
'description' => $description,
281+
'status' => 'dropin',
282+
'update' => false,
283+
'update_version' => null,
284+
'update_package' => null,
285+
'update_id' => '',
286+
'file' => $name,
287+
'auto_update' => false,
288+
'author' => $item_data['Author'],
289+
'wporg_status' => '',
290+
'wporg_last_updated' => '',
280291
];
281292
}
282293

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

707719
if ( ! isset( $duplicate_names[ $name ] ) ) {
708720
$duplicate_names[ $name ] = array();
709721
}
710722

711723
$duplicate_names[ $name ][] = $file;
712724
$items[ $file ] = [
713-
'name' => $name,
714-
'status' => $this->get_status( $file ),
715-
'update' => (bool) $update_info,
716-
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
717-
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
718-
'version' => $details['Version'],
719-
'update_id' => $file,
720-
'title' => $details['Name'],
721-
'description' => wordwrap( $details['Description'] ),
722-
'file' => $file,
723-
'auto_update' => in_array( $file, $auto_updates, true ),
724-
'author' => $details['Author'],
725+
'name' => $name,
726+
'status' => $this->get_status( $file ),
727+
'update' => (bool) $update_info,
728+
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
729+
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
730+
'version' => $details['Version'],
731+
'update_id' => $file,
732+
'title' => $details['Name'],
733+
'description' => wordwrap( $details['Description'] ),
734+
'file' => $file,
735+
'auto_update' => in_array( $file, $auto_updates, true ),
736+
'author' => $details['Author'],
737+
'wporg_status' => $wporg_info['status'],
738+
'wporg_last_updated' => $wporg_info['last_updated'],
725739
];
726740

727741
if ( null === $update_info ) {
728-
729742
// Get info for all plugins that don't have an update.
730743
$plugin_update_info = isset( $all_update_info->no_update[ $file ] ) ? $all_update_info->no_update[ $file ] : null;
731744

@@ -748,6 +761,64 @@ protected function get_item_list() {
748761
return $items;
749762
}
750763

764+
/**
765+
* Get the wordpress.org status of a plugin.
766+
*
767+
* @param string $plugin_name The plugin slug.
768+
*
769+
* @return string The status of the plugin, includes the last update date.
770+
*/
771+
protected function get_wporg_data( $plugin_name ) {
772+
$data = [
773+
'status' => '',
774+
'last_updated' => '',
775+
];
776+
if ( ! $this->check_wporg['status'] && ! $this->check_wporg['last_updated'] ) {
777+
return $data;
778+
}
779+
780+
if ( $this->check_wporg ) {
781+
try {
782+
$plugin_data = ( new WpOrgApi() )->get_plugin_info( $plugin_name );
783+
} catch ( Exception $e ) {
784+
// Request failed. The plugin is not (active) on .org.
785+
$plugin_data = false;
786+
}
787+
if ( $plugin_data ) {
788+
$data['status'] = 'active';
789+
if ( ! $this->check_wporg['last_updated'] ) {
790+
return $data; // The plugin is active on .org, but we don't need the date.
791+
}
792+
}
793+
// Just because the plugin is not in the api, does not mean it was never on .org.
794+
}
795+
796+
$request = wp_remote_get( "https://plugins.trac.wordpress.org/log/{$plugin_name}/?limit=1&mode=stop_on_copy&format=rss" );
797+
$response_code = wp_remote_retrieve_response_code( $request );
798+
if ( 404 === $response_code ) {
799+
return $data; // This plugin was never on .org, there is no date to check.
800+
}
801+
if ( 'active' !== $data['status'] ) {
802+
$data['status'] = 'closed'; // This plugin was on .org at some point, but not anymore.
803+
}
804+
if ( ! class_exists( 'SimpleXMLElement' ) ) {
805+
WP_CLI::error( "The PHP extension 'SimpleXMLElement' is not available but is required for XML-formatted output." );
806+
}
807+
808+
// Check the last update date.
809+
$r_body = wp_remote_retrieve_body( $request );
810+
if ( str_contains( $r_body, 'pubDate' ) ) {
811+
// Very raw check, not validating the format or anything else.
812+
$xml = simplexml_load_string( $r_body );
813+
$xml_pub_date = $xml->xpath( '//pubDate' );
814+
if ( $xml_pub_date ) {
815+
$data['last_updated'] = wp_date( 'Y-m-d', (string) strtotime( $xml_pub_date[0] ) );
816+
}
817+
}
818+
819+
return $data;
820+
}
821+
751822
protected function filter_item_list( $items, $args ) {
752823
$basenames = wp_list_pluck( $this->fetcher->get_many( $args ), 'file' );
753824
return Utils\pick_fields( $items, $basenames );
@@ -1188,6 +1259,8 @@ public function delete( $args, $assoc_args = array() ) {
11881259
* * file
11891260
* * auto_update
11901261
* * author
1262+
* * wporg_status
1263+
* * wporg_last_updated
11911264
*
11921265
* ## EXAMPLES
11931266
*
@@ -1210,9 +1283,34 @@ public function delete( $args, $assoc_args = array() ) {
12101283
* | hello | inactive | none | 1.6 | 1.7.2 |
12111284
* +---------+----------------+--------+---------+----------------+
12121285
*
1286+
* # Check whether plugins are still active on WordPress.org
1287+
* $ wp plugin list --format=csv --fields=name,wporg_status,wporg_last_updated
1288+
* +--------------------+--------------+--------------------+
1289+
* | name | wporg_status | wporg_last_updated |
1290+
* +--------------------+--------------+--------------------+
1291+
* | akismet | active | 2023-12-11 |
1292+
* | user-switching | active | 2023-11-17 |
1293+
* | wordpress-importer | active | 2023-04-28 |
1294+
* | local | | |
1295+
* +--------------------+--------------+--------------------+
1296+
*
12131297
* @subcommand list
12141298
*/
12151299
public function list_( $_, $assoc_args ) {
1300+
$fields = Utils\get_flag_value( $assoc_args, 'fields' );
1301+
if ( ! empty( $fields ) ) {
1302+
$fields = explode( ',', $fields );
1303+
$this->check_wporg['status'] = in_array( 'wporg_status', $fields, true );
1304+
$this->check_wporg['last_updated'] = in_array( 'wporg_last_updated', $fields, true );
1305+
}
1306+
1307+
$field = Utils\get_flag_value( $assoc_args, 'field' );
1308+
if ( 'wporg_status' === $field ) {
1309+
$this->check_wporg['status'] = true;
1310+
} elseif ( 'wporg_last_updated' === $field ) {
1311+
$this->check_wporg['last_updated'] = true;
1312+
}
1313+
12161314
parent::_list( $_, $assoc_args );
12171315
}
12181316

0 commit comments

Comments
 (0)