Skip to content

update_unavailable_reason from one plugin or theme leaks into later list rows #551

Description

@ekamran

Bug summary

wp plugin list and wp theme list expose an update_unavailable_reason field. The reason string is set inside the per-item loop when an update fails the PHP or WordPress requirement, but the variable is never reset for the next item. After one incompatible item, later plugins or themes that do not set their own reason can show the first item's reason, even when their own update column says none or available. Anyone consuming --format=json or --format=csv gets contradictory rows.

Both list builders have the same pattern from the same commit:

  • Plugin_Command::get_item_list() (plugin side, PHP requirement branch)
  • the ParseThemeNameInput trait used by wp theme list (theme side, both the PHP and the WordPress requirement branches)

Introduced in 730da5d (Support requires and requires_php in plugin/theme list and update commands), so the leak exists since v2.1.24 and is still in v3.0.0 and main.

Steps to reproduce

On an install with two plugins (akismet and hello here), add a temporary mu-plugin that gives only akismet an update requiring an impossible PHP version:

<?php
add_filter( 'site_transient_update_plugins', function ( $transient ) {
	if ( ! is_object( $transient ) ) {
		$transient = new stdClass();
	}
	if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
		$transient->response = array();
	}
	$transient->response['akismet/akismet.php'] = (object) array(
		'slug'         => 'akismet',
		'plugin'       => 'akismet/akismet.php',
		'new_version'  => '99.0',
		'package'      => 'https://downloads.wordpress.org/plugin/akismet.99.0.zip',
		'requires'     => '5.0',
		'requires_php' => '99.0',
	);
	return $transient;
} );

Then:

$ wp plugin list --fields=name,update,update_unavailable_reason --format=csv
name,update,update_unavailable_reason
akismet,unavailable,"This update requires PHP version 99.0, but the version installed is 8.5.10."
hello,none,"This update requires PHP version 99.0, but the version installed is 8.5.10."

hello has no update at all, but reports akismet's reason. Expected: an empty reason for hello.

The theme side behaves the same with the equivalent site_transient_update_themes filter: injecting an incompatible update for twentytwentyfive makes twentytwentyfour and twentytwentythree both carry its reason. The leak is strictly forward: injecting the update on the last item of the list leaves all earlier rows clean.

The real world trigger is any site whose PHP is older than what one installed plugin's or theme's update requires, which is exactly the situation this field exists to report.

Environment

Reproduced against extension-command main (8b288e9) on WordPress 7.1 with PHP 8.5.10. Code inspected at the v2.1.24 and v3.0.0 tags, same pattern at both.

I have a fix ready (reset the variable per item in both loops) with Behat scenarios for both sides, PR follows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions