Skip to content

Commit 730da5d

Browse files
committed
Support requires and requires_php in plugin/theme list and update commands
This introduces a new update sate of 'unavailable' which is when there is a new version of a theme/plugin upstream but the local WordPress / PHP requirements do not meet the requirements set by the authors.
1 parent f0cebef commit 730da5d

File tree

5 files changed

+235
-40
lines changed

5 files changed

+235
-40
lines changed

features/plugin.feature

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,3 +810,58 @@ Feature: Manage WordPress plugins
810810
"""
811811
5.5
812812
"""
813+
814+
@require-php-7
815+
Scenario: Show plugin update as unavailable if it doesn't meet WordPress requirements
816+
Given a WP install
817+
818+
When I run `wp core download --version=6.4 --force`
819+
And I run `rm -r wp-content/themes/*`
820+
And I run `wp plugin install wp-super-cache --version=1.9.4`
821+
822+
When I run `wp plugin list --name=wp-super-cache --field=update_version`
823+
And save STDOUT as {UPDATE_VERSION}
824+
825+
When I run `wp plugin list --name=wp-super-cache --field=requires`
826+
And save STDOUT as {REQUIRES}
827+
828+
When I run `wp plugin list --name=wp-super-cache --field=requires_php`
829+
And save STDOUT as {REQUIRES_PHP}
830+
831+
And I run `wp plugin list`
832+
Then STDOUT should be a table containing rows:
833+
| name | status | update | version | update_version | auto_update | requires | requires_php |
834+
| wp-super-cache | inactive | unavailable | 1.9.4 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |
835+
836+
When I try `wp plugin update wp-super-cache`
837+
Then STDERR should contain:
838+
"""
839+
Warning: wp-super-cache: Requires a newer version of WordPress
840+
"""
841+
842+
@less-than-php-8.0 @require-wp-5.6
843+
Scenario: Show plugin update as unavailable if it doesn't meet PHP requirements
844+
Given a WP install
845+
846+
And I run `wp plugin install edit-flow --version=0.9.8`
847+
848+
When I run `wp plugin list --name=edit-flow --field=update_version`
849+
And save STDOUT as {UPDATE_VERSION}
850+
851+
When I run `wp plugin list --name=edit-flow --field=requires`
852+
And save STDOUT as {REQUIRES}
853+
854+
When I run `wp plugin list --name=edit-flow --field=requires_php`
855+
And save STDOUT as {REQUIRES_PHP}
856+
857+
And I run `wp plugin list`
858+
Then STDOUT should be a table containing rows:
859+
| name | status | update | version | update_version | auto_update | requires | requires_php |
860+
| edit-flow | inactive | unavailable | 0.9.8 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |
861+
862+
When I try `wp plugin update edit-flow`
863+
Then STDERR should contain:
864+
"""
865+
Warning: edit-flow: Requires a newer version of PHP
866+
"""
867+

features/theme.feature

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,31 @@ Feature: Manage WordPress themes
620620
Then STDOUT should be a table containing rows:
621621
| auto_update |
622622
| on |
623+
624+
@require-php-7
625+
Scenario: Show theme update as unavailable if it doesn't meet WordPress requirements
626+
Given a WP install
627+
628+
When I run `wp core download --version=6.2 --force`
629+
And I run `rm -r wp-content/themes/*`
630+
And I run `wp theme install kadence --version=1.1.1`
631+
632+
When I run `wp theme list --name=kadence --field=update_version`
633+
And save STDOUT as {UPDATE_VERSION}
634+
635+
When I run `wp theme list --name=kadence --field=requires`
636+
And save STDOUT as {REQUIRES}
637+
638+
When I run `wp theme list --name=kadence --field=requires_php`
639+
And save STDOUT as {REQUIRES_PHP}
640+
641+
And I run `wp theme list`
642+
Then STDOUT should be a table containing rows:
643+
| name | status | update | version | update_version | auto_update | requires | requires_php |
644+
| kadence | inactive | unavailable | 1.1.1 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |
645+
646+
When I try `wp theme update kadence`
647+
Then STDERR should contain:
648+
"""
649+
Warning: kadence: Requires a newer version of WordPress
650+
"""

src/Plugin_Command.php

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ protected function get_all_items() {
271271
'file' => $file,
272272
'auto_update' => false,
273273
'tested_up_to' => '',
274+
'requires' => '',
275+
'requires_php' => '',
274276
'wporg_status' => $wporg_info['status'],
275277
'wporg_last_updated' => $wporg_info['last_updated'],
276278
);
@@ -293,6 +295,8 @@ protected function get_all_items() {
293295
'auto_update' => false,
294296
'author' => $item_data['Author'],
295297
'tested_up_to' => '',
298+
'requires' => '',
299+
'requires_php' => '',
296300
'wporg_status' => '',
297301
'wporg_last_updated' => '',
298302
];
@@ -740,6 +744,8 @@ public function update( $args, $assoc_args ) {
740744
}
741745

742746
protected function get_item_list() {
747+
global $wp_version;
748+
743749
$items = [];
744750
$duplicate_names = [];
745751

@@ -760,29 +766,62 @@ protected function get_item_list() {
760766
$update_info = ( isset( $all_update_info->response[ $file ] ) && null !== $all_update_info->response[ $file ] ) ? (array) $all_update_info->response[ $file ] : null;
761767
$name = Utils\get_plugin_name( $file );
762768
$wporg_info = $this->get_wporg_data( $name );
769+
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false );
763770

764771
if ( ! isset( $duplicate_names[ $name ] ) ) {
765772
$duplicate_names[ $name ] = array();
766773
}
767774

775+
$php_version = PHP_VERSION;
776+
777+
$requires = isset( $update_info ) && isset( $update_info['requires'] ) ? $update_info['requires'] : null;
778+
$requires_php = isset( $update_info ) && isset( $update_info['requires_php'] ) ? $update_info['requires_php'] : null;
779+
780+
// If an update has requires_php set, check to see if the local version of PHP meets that requirement
781+
// The plugins update API already filters out plugins that don't meet WordPress requirements, but does not
782+
// filter out plugins based on PHP requirements -- so we must do that here
783+
$compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );
784+
785+
if ( ! $compatible_php ) {
786+
$update = 'unavailable';
787+
$update_unavailable_reason = "Requires a newer version of PHP [$requires_php] than available [$php_version]";
788+
} else {
789+
$update = $update_info ? 'available' : 'none';
790+
}
791+
792+
// requires and requires_php are only provided by the plugins update API in the case of an update available.
793+
// For display consistency, get these values from the current plugin file if they aren't in this response
794+
if ( null === $requires ) {
795+
$requires = ! empty( $plugin_data['RequiresWP'] ) ? $plugin_data['RequiresWP'] : '';
796+
}
797+
798+
if ( null === $requires_php ) {
799+
$requires_php = ! empty( $plugin_data['RequiresPHP'] ) ? $plugin_data['RequiresPHP'] : '';
800+
}
801+
768802
$duplicate_names[ $name ][] = $file;
769803
$items[ $file ] = [
770-
'name' => $name,
771-
'status' => $this->get_status( $file ),
772-
'update' => (bool) $update_info,
773-
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
774-
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
775-
'version' => $details['Version'],
776-
'update_id' => $file,
777-
'title' => $details['Name'],
778-
'description' => wordwrap( $details['Description'] ),
779-
'file' => $file,
780-
'auto_update' => in_array( $file, $auto_updates, true ),
781-
'author' => $details['Author'],
782-
'tested_up_to' => '',
783-
'wporg_status' => $wporg_info['status'],
784-
'wporg_last_updated' => $wporg_info['last_updated'],
785-
'recently_active' => in_array( $file, array_keys( $recently_active ), true ),
804+
'name' => $name,
805+
'status' => $this->get_status( $file ),
806+
'update' => $update,
807+
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
808+
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
809+
'version' => $details['Version'],
810+
'update_id' => $file,
811+
'title' => $details['Name'],
812+
'description' => wordwrap( $details['Description'] ),
813+
'file' => $file,
814+
'auto_update' => in_array( $file, $auto_updates, true ),
815+
'author' => $details['Author'],
816+
'tested_up_to' => '',
817+
'requires' => $requires,
818+
'requires_php' => $requires_php,
819+
'wporg_status' => $wporg_info['status'],
820+
'wporg_last_updated' => $wporg_info['last_updated'],
821+
822+
'recently_active' => in_array( $file, array_keys( $recently_active ), true ),
823+
824+
'update_unavailable_reason' => isset( $update_unavailable_reason ) ? $update_unavailable_reason : '',
786825
];
787826

788827
if ( $this->check_headers['tested_up_to'] ) {
@@ -817,9 +856,25 @@ protected function get_item_list() {
817856
// Get info for all plugins that don't have an update.
818857
$plugin_update_info = isset( $all_update_info->no_update[ $file ] ) ? $all_update_info->no_update[ $file ] : null;
819858

820-
// Compare version and update information in plugin list.
859+
// Check if local version is newer than what is listed upstream.
821860
if ( null !== $plugin_update_info && version_compare( $details['Version'], $plugin_update_info->new_version, '>' ) ) {
822-
$items[ $file ]['update'] = static::INVALID_VERSION_MESSAGE;
861+
$items[ $file ]['update'] = static::INVALID_VERSION_MESSAGE;
862+
$items[ $file ]['requires'] = isset( $plugin_update_info->requires ) ? $plugin_update_info->requires : null;
863+
$items[ $file ]['requires_php'] = isset( $plugin_update_info->requires_php ) ? $plugin_update_info->requires_php : null;
864+
}
865+
866+
// If there is a plugin in no_update with a newer version than the local copy, it is because the plugins update api
867+
// has already filtered it because the local WordPress version is too low
868+
if ( null !== $plugin_update_info && version_compare( $details['Version'], $plugin_update_info->new_version, '<' ) ) {
869+
$items[ $file ]['update'] = 'unavailable';
870+
$items[ $file ]['update_version'] = $plugin_update_info->new_version;
871+
$items[ $file ]['requires'] = isset( $plugin_update_info->requires ) ? $plugin_update_info->requires : null;
872+
$items[ $file ]['requires_php'] = isset( $plugin_update_info->requires_php ) ? $plugin_update_info->requires_php : null;
873+
874+
$reason = "Requires a newer version of WordPress [$plugin_update_info->requires] than installed [$wp_version]";
875+
876+
$items[ $file ]['update_unavailable_reason'] = $reason;
877+
823878
}
824879
}
825880
}
@@ -1397,6 +1452,8 @@ public function delete( $args, $assoc_args = array() ) {
13971452
* * file
13981453
* * author
13991454
* * tested_up_to
1455+
* * requires
1456+
* * requires_php
14001457
* * wporg_status
14011458
* * wporg_last_updated
14021459
*
@@ -1488,7 +1545,6 @@ protected function get_status( $file ) {
14881545
if ( is_plugin_active_for_network( $file ) ) {
14891546
return 'active-network';
14901547
}
1491-
14921548
if ( is_plugin_active( $file ) ) {
14931549
return 'active';
14941550
}

src/WP_CLI/CommandWithUpgrade.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private function status_all() {
103103
$padding = $this->get_padding( $items );
104104

105105
foreach ( $items as $file => $details ) {
106-
if ( $details['update'] ) {
106+
if ( 'available' === $details['update'] ) {
107107
$line = ' %yU%n';
108108
} else {
109109
$line = ' ';
@@ -150,8 +150,7 @@ private function show_legend( $items ) {
150150
$this->map['long'][ $status ]
151151
);
152152
}
153-
154-
if ( in_array( true, wp_list_pluck( $items, 'update' ), true ) ) {
153+
if ( in_array( 'available', wp_list_pluck( $items, 'update' ), true ) ) {
155154
$legend_line[] = '%yU = Update Available%n';
156155
}
157156

@@ -375,7 +374,12 @@ protected function update_many( $args, $assoc_args ) {
375374
$errors = count( $args ) - count( $items );
376375
}
377376

378-
$items_to_update = wp_list_filter( $items, [ 'update' => true ] );
377+
$items_to_update = array_filter(
378+
$items,
379+
function ( $item ) {
380+
return isset( $item['update'] ) && $item['update'] !== 'none';
381+
}
382+
);
379383

380384
$minor = (bool) Utils\get_flag_value( $assoc_args, 'minor', false );
381385
$patch = (bool) Utils\get_flag_value( $assoc_args, 'patch', false );
@@ -417,6 +421,11 @@ protected function update_many( $args, $assoc_args ) {
417421
++$skipped;
418422
unset( $items_to_update[ $item_key ] );
419423
}
424+
if ( 'unavailable' === $item_info['update'] ) {
425+
WP_CLI::warning( "{$item_info['name']}: {$item_info['update_unavailable_reason']}" );
426+
++$skipped;
427+
unset( $items_to_update[ $item_key ] );
428+
}
420429
}
421430

422431
if ( Utils\get_flag_value( $assoc_args, 'dry-run' ) ) {
@@ -564,10 +573,14 @@ function ( $value ) {
564573

565574
foreach ( $item as $field => &$value ) {
566575
if ( 'update' === $field ) {
567-
if ( true === $value ) {
568-
$value = 'available';
569-
} elseif ( false === $value ) {
570-
$value = 'none';
576+
// If an update is unavailable, make sure to also show these fields which will explain why
577+
if ( 'unavailable' === $value ) {
578+
if ( ! in_array( 'requires', $this->obj_fields, true ) ) {
579+
array_push( $this->obj_fields, 'requires' );
580+
}
581+
if ( ! in_array( 'requires_php', $this->obj_fields, true ) ) {
582+
array_push( $this->obj_fields, 'requires_php' );
583+
}
571584
}
572585
} elseif ( 'auto_update' === $field ) {
573586
if ( true === $value ) {

src/WP_CLI/ParseThemeNameInput.php

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ protected function check_optional_args_and_all( $args, $all, $verb = 'install' )
4545
* @return array
4646
*/
4747
private function get_all_themes() {
48+
global $wp_version;
49+
// Extract the major WordPress version (e.g., "6.3") from the full version string
50+
list($wp_core_version) = explode( '-', $wp_version );
51+
$wp_core_version = implode( '.', array_slice( explode( '.', $wp_core_version ), 0, 2 ) );
52+
4853
$items = array();
4954
$theme_version_info = array();
5055

@@ -76,22 +81,60 @@ private function get_all_themes() {
7681
}
7782

7883
foreach ( wp_get_themes() as $key => $theme ) {
79-
$stylesheet = $theme->get_stylesheet();
80-
84+
$stylesheet = $theme->get_stylesheet();
8185
$update_info = ( isset( $all_update_info->response[ $stylesheet ] ) && null !== $all_update_info->response[ $theme->get_stylesheet() ] ) ? (array) $all_update_info->response[ $theme->get_stylesheet() ] : null;
8286

87+
// Unlike plugin update responses, the wordpress.org API does not seem to check and filter themes that don't meet
88+
// WordPress version requirements into a separate no_updates array
89+
// Also unlike plugin update responses, the wordpress.org API seems to always include requires AND requires_php
90+
$requires = isset( $update_info ) && isset( $update_info['requires'] ) ? $update_info['requires'] : null;
91+
$requires_php = isset( $update_info ) && isset( $update_info['requires_php'] ) ? $update_info['requires_php'] : null;
92+
93+
$compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );
94+
$compatible_wp = empty( $requires ) || version_compare( $wp_version, $requires, '>=' );
95+
96+
if ( ! $compatible_php ) {
97+
$update = 'unavailable';
98+
$update_unavailable_reason = sprintf(
99+
'Requires a newer version of PHP [%s] than installed [%s]',
100+
$requires_php,
101+
PHP_VERSION
102+
);
103+
} else {
104+
$update = $update_info ? 'available' : 'none';
105+
}
106+
107+
if ( ! $compatible_wp ) {
108+
$update = 'unavailable';
109+
$update_unavailable_reason = "Requires a newer version of WordPress [$requires] than installed [$wp_version]";
110+
} else {
111+
$update = $update_info ? 'available' : 'none';
112+
}
113+
114+
// For display consistency, get these values from the current plugin file if they aren't in this response
115+
if ( null === $requires ) {
116+
$requires = ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '';
117+
}
118+
119+
if ( null === $requires_php ) {
120+
$requires_php = ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '';
121+
}
122+
83123
$items[ $stylesheet ] = [
84-
'name' => $key,
85-
'status' => $this->get_status( $theme ),
86-
'update' => (bool) $update_info,
87-
'update_version' => isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
88-
'update_package' => isset( $update_info['package'] ) ? $update_info['package'] : null,
89-
'version' => $theme->get( 'Version' ),
90-
'update_id' => $stylesheet,
91-
'title' => $theme->get( 'Name' ),
92-
'description' => wordwrap( $theme->get( 'Description' ) ),
93-
'author' => $theme->get( 'Author' ),
94-
'auto_update' => in_array( $stylesheet, $auto_updates, true ),
124+
'name' => $key,
125+
'status' => $this->get_status( $theme ),
126+
'update' => $update,
127+
'update_version' => isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
128+
'update_package' => isset( $update_info['package'] ) ? $update_info['package'] : null,
129+
'version' => $theme->get( 'Version' ),
130+
'update_id' => $stylesheet,
131+
'title' => $theme->get( 'Name' ),
132+
'description' => wordwrap( $theme->get( 'Description' ) ),
133+
'author' => $theme->get( 'Author' ),
134+
'auto_update' => in_array( $stylesheet, $auto_updates, true ),
135+
'requires' => $requires,
136+
'requires_php' => $requires_php,
137+
'update_unavailable_reason' => isset( $update_unavailable_reason ) ? $update_unavailable_reason : '',
95138
];
96139

97140
// Compare version and update information in theme list.

0 commit comments

Comments
 (0)