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 requires and requires_php in plugin/theme list and update command #440

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions features/plugin.feature
Original file line number Diff line number Diff line change
Expand Up @@ -810,3 +810,58 @@ Feature: Manage WordPress plugins
"""
5.5
"""

@require-php-7
Scenario: Show plugin update as unavailable if it doesn't meet WordPress requirements
Given a WP install

When I run `wp core download --version=6.4 --force`
And I run `rm -r wp-content/themes/*`
And I run `wp plugin install wp-super-cache --version=1.9.4`

When I run `wp plugin list --name=wp-super-cache --field=update_version`
And save STDOUT as {UPDATE_VERSION}

When I run `wp plugin list --name=wp-super-cache --field=requires`
And save STDOUT as {REQUIRES}

When I run `wp plugin list --name=wp-super-cache --field=requires_php`
And save STDOUT as {REQUIRES_PHP}

And I run `wp plugin list`
Then STDOUT should be a table containing rows:
| name | status | update | version | update_version | auto_update | requires | requires_php |
| wp-super-cache | inactive | unavailable | 1.9.4 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |

When I try `wp plugin update wp-super-cache`
Then STDERR should contain:
"""
Warning: wp-super-cache: Requires a newer version of WordPress
"""

@less-than-php-8.0 @require-wp-5.6
Scenario: Show plugin update as unavailable if it doesn't meet PHP requirements
Given a WP install

And I run `wp plugin install edit-flow --version=0.9.8`

When I run `wp plugin list --name=edit-flow --field=update_version`
And save STDOUT as {UPDATE_VERSION}

When I run `wp plugin list --name=edit-flow --field=requires`
And save STDOUT as {REQUIRES}

When I run `wp plugin list --name=edit-flow --field=requires_php`
And save STDOUT as {REQUIRES_PHP}

And I run `wp plugin list`
Then STDOUT should be a table containing rows:
| name | status | update | version | update_version | auto_update | requires | requires_php |
| edit-flow | inactive | unavailable | 0.9.8 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |

When I try `wp plugin update edit-flow`
Then STDERR should contain:
"""
Warning: edit-flow: Requires a newer version of PHP
"""

28 changes: 28 additions & 0 deletions features/theme.feature
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,31 @@ Feature: Manage WordPress themes
Then STDOUT should be a table containing rows:
| auto_update |
| on |

@require-php-7
Scenario: Show theme update as unavailable if it doesn't meet WordPress requirements
Given a WP install

When I run `wp core download --version=6.2 --force`
And I run `rm -r wp-content/themes/*`
And I run `wp theme install kadence --version=1.1.1`

When I run `wp theme list --name=kadence --field=update_version`
And save STDOUT as {UPDATE_VERSION}

When I run `wp theme list --name=kadence --field=requires`
And save STDOUT as {REQUIRES}

When I run `wp theme list --name=kadence --field=requires_php`
And save STDOUT as {REQUIRES_PHP}

And I run `wp theme list`
Then STDOUT should be a table containing rows:
| name | status | update | version | update_version | auto_update | requires | requires_php |
| kadence | inactive | unavailable | 1.1.1 | {UPDATE_VERSION} | off | {REQUIRES} | {REQUIRES_PHP} |

When I try `wp theme update kadence`
Then STDERR should contain:
"""
Warning: kadence: Requires a newer version of WordPress
"""
94 changes: 75 additions & 19 deletions src/Plugin_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ protected function get_all_items() {
'file' => $file,
'auto_update' => false,
'tested_up_to' => '',
'requires' => '',
'requires_php' => '',
'wporg_status' => $wporg_info['status'],
'wporg_last_updated' => $wporg_info['last_updated'],
);
Expand All @@ -293,6 +295,8 @@ protected function get_all_items() {
'auto_update' => false,
'author' => $item_data['Author'],
'tested_up_to' => '',
'requires' => '',
'requires_php' => '',
'wporg_status' => '',
'wporg_last_updated' => '',
];
Expand Down Expand Up @@ -740,6 +744,8 @@ public function update( $args, $assoc_args ) {
}

protected function get_item_list() {
global $wp_version;

$items = [];
$duplicate_names = [];

Expand All @@ -760,29 +766,62 @@ protected function get_item_list() {
$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 );
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false );

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

$php_version = PHP_VERSION;

$requires = isset( $update_info ) && isset( $update_info['requires'] ) ? $update_info['requires'] : null;
$requires_php = isset( $update_info ) && isset( $update_info['requires_php'] ) ? $update_info['requires_php'] : null;

// If an update has requires_php set, check to see if the local version of PHP meets that requirement
// The plugins update API already filters out plugins that don't meet WordPress requirements, but does not
// filter out plugins based on PHP requirements -- so we must do that here
$compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );

if ( ! $compatible_php ) {
$update = 'unavailable';
$update_unavailable_reason = "Requires a newer version of PHP [$requires_php] than available [$php_version]";
mrsdizzie marked this conversation as resolved.
Show resolved Hide resolved
} else {
$update = $update_info ? 'available' : 'none';
}

// requires and requires_php are only provided by the plugins update API in the case of an update available.
// For display consistency, get these values from the current plugin file if they aren't in this response
if ( null === $requires ) {
$requires = ! empty( $plugin_data['RequiresWP'] ) ? $plugin_data['RequiresWP'] : '';
}

if ( null === $requires_php ) {
$requires_php = ! empty( $plugin_data['RequiresPHP'] ) ? $plugin_data['RequiresPHP'] : '';
}

$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'],
'tested_up_to' => '',
'wporg_status' => $wporg_info['status'],
'wporg_last_updated' => $wporg_info['last_updated'],
'recently_active' => in_array( $file, array_keys( $recently_active ), true ),
'name' => $name,
'status' => $this->get_status( $file ),
'update' => $update,
'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'],
'tested_up_to' => '',
'requires' => $requires,
'requires_php' => $requires_php,
'wporg_status' => $wporg_info['status'],
'wporg_last_updated' => $wporg_info['last_updated'],

'recently_active' => in_array( $file, array_keys( $recently_active ), true ),

'update_unavailable_reason' => isset( $update_unavailable_reason ) ? $update_unavailable_reason : '',
];

if ( $this->check_headers['tested_up_to'] ) {
Expand Down Expand Up @@ -817,9 +856,25 @@ protected function get_item_list() {
// 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;

// Compare version and update information in plugin list.
// Check if local version is newer than what is listed upstream.
if ( null !== $plugin_update_info && version_compare( $details['Version'], $plugin_update_info->new_version, '>' ) ) {
$items[ $file ]['update'] = static::INVALID_VERSION_MESSAGE;
$items[ $file ]['update'] = static::INVALID_VERSION_MESSAGE;
$items[ $file ]['requires'] = isset( $plugin_update_info->requires ) ? $plugin_update_info->requires : null;
$items[ $file ]['requires_php'] = isset( $plugin_update_info->requires_php ) ? $plugin_update_info->requires_php : null;
}

// If there is a plugin in no_update with a newer version than the local copy, it is because the plugins update api
// has already filtered it because the local WordPress version is too low
if ( null !== $plugin_update_info && version_compare( $details['Version'], $plugin_update_info->new_version, '<' ) ) {
$items[ $file ]['update'] = 'unavailable';
$items[ $file ]['update_version'] = $plugin_update_info->new_version;
$items[ $file ]['requires'] = isset( $plugin_update_info->requires ) ? $plugin_update_info->requires : null;
$items[ $file ]['requires_php'] = isset( $plugin_update_info->requires_php ) ? $plugin_update_info->requires_php : null;

$reason = "Requires a newer version of WordPress [$plugin_update_info->requires] than installed [$wp_version]";

$items[ $file ]['update_unavailable_reason'] = $reason;

}
}
}
Expand Down Expand Up @@ -1397,6 +1452,8 @@ public function delete( $args, $assoc_args = array() ) {
* * file
* * author
* * tested_up_to
* * requires
* * requires_php
* * wporg_status
* * wporg_last_updated
*
Expand Down Expand Up @@ -1488,7 +1545,6 @@ protected function get_status( $file ) {
if ( is_plugin_active_for_network( $file ) ) {
return 'active-network';
}

if ( is_plugin_active( $file ) ) {
return 'active';
}
Expand Down
29 changes: 21 additions & 8 deletions src/WP_CLI/CommandWithUpgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private function status_all() {
$padding = $this->get_padding( $items );

foreach ( $items as $file => $details ) {
if ( $details['update'] ) {
if ( 'available' === $details['update'] ) {
$line = ' %yU%n';
} else {
$line = ' ';
Expand Down Expand Up @@ -150,8 +150,7 @@ private function show_legend( $items ) {
$this->map['long'][ $status ]
);
}

if ( in_array( true, wp_list_pluck( $items, 'update' ), true ) ) {
if ( in_array( 'available', wp_list_pluck( $items, 'update' ), true ) ) {
$legend_line[] = '%yU = Update Available%n';
}

Expand Down Expand Up @@ -375,7 +374,12 @@ protected function update_many( $args, $assoc_args ) {
$errors = count( $args ) - count( $items );
}

$items_to_update = wp_list_filter( $items, [ 'update' => true ] );
$items_to_update = array_filter(
$items,
function ( $item ) {
return isset( $item['update'] ) && 'none' !== $item['update'];
}
);

$minor = (bool) Utils\get_flag_value( $assoc_args, 'minor', false );
$patch = (bool) Utils\get_flag_value( $assoc_args, 'patch', false );
Expand Down Expand Up @@ -417,6 +421,11 @@ protected function update_many( $args, $assoc_args ) {
++$skipped;
unset( $items_to_update[ $item_key ] );
}
if ( 'unavailable' === $item_info['update'] ) {
WP_CLI::warning( "{$item_info['name']}: {$item_info['update_unavailable_reason']}" );
++$skipped;
unset( $items_to_update[ $item_key ] );
}
}

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

foreach ( $item as $field => &$value ) {
if ( 'update' === $field ) {
if ( true === $value ) {
$value = 'available';
} elseif ( false === $value ) {
$value = 'none';
// If an update is unavailable, make sure to also show these fields which will explain why
if ( 'unavailable' === $value ) {
if ( ! in_array( 'requires', $this->obj_fields, true ) ) {
array_push( $this->obj_fields, 'requires' );
}
if ( ! in_array( 'requires_php', $this->obj_fields, true ) ) {
array_push( $this->obj_fields, 'requires_php' );
}
}
} elseif ( 'auto_update' === $field ) {
if ( true === $value ) {
Expand Down
69 changes: 56 additions & 13 deletions src/WP_CLI/ParseThemeNameInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ protected function check_optional_args_and_all( $args, $all, $verb = 'install' )
* @return array
*/
private function get_all_themes() {
global $wp_version;
// Extract the major WordPress version (e.g., "6.3") from the full version string
list($wp_core_version) = explode( '-', $wp_version );
$wp_core_version = implode( '.', array_slice( explode( '.', $wp_core_version ), 0, 2 ) );

$items = array();
$theme_version_info = array();

Expand Down Expand Up @@ -76,22 +81,60 @@ private function get_all_themes() {
}

foreach ( wp_get_themes() as $key => $theme ) {
$stylesheet = $theme->get_stylesheet();

$stylesheet = $theme->get_stylesheet();
$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;

// Unlike plugin update responses, the wordpress.org API does not seem to check and filter themes that don't meet
// WordPress version requirements into a separate no_updates array
// Also unlike plugin update responses, the wordpress.org API seems to always include requires AND requires_php
$requires = isset( $update_info ) && isset( $update_info['requires'] ) ? $update_info['requires'] : null;
$requires_php = isset( $update_info ) && isset( $update_info['requires_php'] ) ? $update_info['requires_php'] : null;

$compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );
$compatible_wp = empty( $requires ) || version_compare( $wp_version, $requires, '>=' );

if ( ! $compatible_php ) {
$update = 'unavailable';
$update_unavailable_reason = sprintf(
'Requires a newer version of PHP [%s] than installed [%s]',
$requires_php,
PHP_VERSION
);
} else {
$update = $update_info ? 'available' : 'none';
}

if ( ! $compatible_wp ) {
$update = 'unavailable';
$update_unavailable_reason = "Requires a newer version of WordPress [$requires] than installed [$wp_version]";
} else {
$update = $update_info ? 'available' : 'none';
}

// For display consistency, get these values from the current plugin file if they aren't in this response
if ( null === $requires ) {
$requires = ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '';
}

if ( null === $requires_php ) {
$requires_php = ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '';
}

$items[ $stylesheet ] = [
'name' => $key,
'status' => $this->get_status( $theme ),
'update' => (bool) $update_info,
'update_version' => isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
'update_package' => isset( $update_info['package'] ) ? $update_info['package'] : null,
'version' => $theme->get( 'Version' ),
'update_id' => $stylesheet,
'title' => $theme->get( 'Name' ),
'description' => wordwrap( $theme->get( 'Description' ) ),
'author' => $theme->get( 'Author' ),
'auto_update' => in_array( $stylesheet, $auto_updates, true ),
'name' => $key,
'status' => $this->get_status( $theme ),
'update' => $update,
'update_version' => isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
'update_package' => isset( $update_info['package'] ) ? $update_info['package'] : null,
'version' => $theme->get( 'Version' ),
'update_id' => $stylesheet,
'title' => $theme->get( 'Name' ),
'description' => wordwrap( $theme->get( 'Description' ) ),
'author' => $theme->get( 'Author' ),
'auto_update' => in_array( $stylesheet, $auto_updates, true ),
'requires' => $requires,
'requires_php' => $requires_php,
'update_unavailable_reason' => isset( $update_unavailable_reason ) ? $update_unavailable_reason : '',
];

// Compare version and update information in theme list.
Expand Down
Loading