22
33use WP_CLI \ParsePluginNameInput ;
44use 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