44use WP_CLI \Utils ;
55use WP_CLI \WpOrgApi ;
66
7+ use function WP_CLI \Utils \normalize_path ;
8+
79/**
810 * Manages plugins, including installs, activations, and updates.
911 *
@@ -51,6 +53,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
5153 'status ' => false ,
5254 'last_updated ' => false ,
5355 ];
56+ protected $ check_headers = [
57+ 'tested_up_to ' => false ,
58+ ];
5459
5560 protected $ obj_fields = array (
5661 'name ' ,
@@ -265,6 +270,7 @@ protected function get_all_items() {
265270 'description ' => $ mu_description ,
266271 'file ' => $ file ,
267272 'auto_update ' => false ,
273+ 'tested_up_to ' => '' ,
268274 'wporg_status ' => $ wporg_info ['status ' ],
269275 'wporg_last_updated ' => $ wporg_info ['last_updated ' ],
270276 );
@@ -286,6 +292,7 @@ protected function get_all_items() {
286292 'file ' => $ name ,
287293 'auto_update ' => false ,
288294 'author ' => $ item_data ['Author ' ],
295+ 'tested_up_to ' => '' ,
289296 'wporg_status ' => '' ,
290297 'wporg_last_updated ' => '' ,
291298 ];
@@ -735,10 +742,39 @@ protected function get_item_list() {
735742 'file ' => $ file ,
736743 'auto_update ' => in_array ( $ file , $ auto_updates , true ),
737744 'author ' => $ details ['Author ' ],
745+ 'tested_up_to ' => '' ,
738746 'wporg_status ' => $ wporg_info ['status ' ],
739747 'wporg_last_updated ' => $ wporg_info ['last_updated ' ],
740748 ];
741749
750+ if ( $ this ->check_headers ['tested_up_to ' ] ) {
751+ $ plugin_readme = normalize_path ( dirname ( WP_PLUGIN_DIR . '/ ' . $ file ) . '/readme.txt ' );
752+
753+ if ( file_exists ( $ plugin_readme ) && is_readable ( $ plugin_readme ) ) {
754+ $ readme_obj = new SplFileObject ( $ plugin_readme );
755+ $ readme_obj ->setFlags ( SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY );
756+ $ readme_line = 0 ;
757+
758+ // Reading the whole file can exhaust the memory, so only read the first 100 lines of the file,
759+ // as the "Tested up to" header should be near the top.
760+ while ( $ readme_line < 100 && ! $ readme_obj ->eof () ) {
761+ $ line = $ readme_obj ->fgets ();
762+
763+ // Similar to WP.org, it matches for both "Tested up to" and "Tested" header in the readme file.
764+ preg_match ( '/^tested(:| up to:) (.*)$/i ' , strtolower ( $ line ), $ matches );
765+
766+ if ( isset ( $ matches [2 ] ) && ! empty ( $ matches [2 ] ) ) {
767+ $ items [ $ file ]['tested_up_to ' ] = $ matches [2 ];
768+ break ;
769+ }
770+
771+ ++$ readme_line ;
772+ }
773+
774+ $ file_obj = null ;
775+ }
776+ }
777+
742778 if ( null === $ update_info ) {
743779 // Get info for all plugins that don't have an update.
744780 $ plugin_update_info = isset ( $ all_update_info ->no_update [ $ file ] ) ? $ all_update_info ->no_update [ $ file ] : null ;
@@ -1289,6 +1325,7 @@ public function delete( $args, $assoc_args = array() ) {
12891325 * * description
12901326 * * file
12911327 * * author
1328+ * * tested_up_to
12921329 * * wporg_status
12931330 * * wporg_last_updated
12941331 *
@@ -1332,6 +1369,8 @@ public function list_( $_, $assoc_args ) {
13321369 $ fields = explode ( ', ' , $ fields );
13331370 $ this ->check_wporg ['status ' ] = in_array ( 'wporg_status ' , $ fields , true );
13341371 $ this ->check_wporg ['last_updated ' ] = in_array ( 'wporg_last_updated ' , $ fields , true );
1372+
1373+ $ this ->check_headers ['tested_up_to ' ] = in_array ( 'tested_up_to ' , $ fields , true );
13351374 }
13361375
13371376 $ field = Utils \get_flag_value ( $ assoc_args , 'field ' );
@@ -1341,6 +1380,8 @@ public function list_( $_, $assoc_args ) {
13411380 $ this ->check_wporg ['last_updated ' ] = true ;
13421381 }
13431382
1383+ $ this ->check_headers ['tested_up_to ' ] = 'tested_up_to ' === $ field || $ this ->check_headers ['tested_up_to ' ];
1384+
13441385 parent ::_list ( $ _ , $ assoc_args );
13451386 }
13461387
0 commit comments