Make WordPress Core

Opened 29 hours ago

Last modified 22 hours ago

#65240 new defect (bug)

REST API: WP_REST_Plugins_Controller::create_item() fatals when plugins_api response lacks language_packs property

Reported by: flipcreative's profile flipcreative Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 6.9.4
Component: REST API Keywords: has-patch
Focuses: rest-api, php-compatibility Cc:

Description

When installing a plugin via the REST endpoint POST /wp/v2/plugins, WP_REST_Plugins_Controller::create_item() calls plugins_api( 'plugin_information', ... ) requesting language_packs => true, then immediately runs array_map() over $api->language_packs without checking whether the property is set or is an array.

If the API response does not include a populated language_packs key — which can happen when the plugin has no translation packs on translate.wordpress.org for any installed locale, when wp.org returns a partial response, or when the result is filtered by a third party — accessing $api->language_packs produces a PHP Warning, then immediately triggers a Fatal TypeError on the array_map call. Both originate from the same request.

The install itself has already succeeded by the time the fatal fires — $upgrader->install( $api->download_link ) runs and completes before the language pack section is reached, so the plugin files are on disk. The REST response returns a 500 even though the install succeeded. This causes spurious error UI in any caller that uses this endpoint and produces noisy logs in production. We are seeing this consistently in the wild from at least one widely deployed third-party caller (Elementor One's app installer) and the volume is enough that one major managed WordPress host has flagged the resulting 500 spike to customers.

File: wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php

Offending code (around lines 380–385):

`php
$language_packs = array_map(

static function ( $item ) {

return (object) $item;

},
$api->language_packs

);
`

Error output:

`
PHP Warning: Undefined property: stdClass::$language_packs in

/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 387

PHP Fatal error: Uncaught TypeError: array_map(): Argument #2 ($array) must be of type

array, null given in /wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:383

`

Steps to reproduce:

  1. Send an authenticated POST request to /wp-json/wp/v2/plugins with a slug for a plugin whose plugins_api response does not contain a populated language_packs array.
  2. Observe that the plugin is correctly installed to wp-content/plugins.
  3. Observe that the REST request returns HTTP 500.
  4. Observe the warning and fatal in the PHP error log.

Suggested fix:

Guard the array_map call:

`php
$language_packs = array();
if ( ! empty( $api->language_packs ) && is_array( $api->language_packs ) ) {

$language_packs = array_map(

static function ( $item ) {

return (object) $item;

},
$api->language_packs

);

}
`

Alternatively, normalize the plugins_api response so that language_packs is always present as an array (even empty) when requested via the fields parameter.

Environment:

  • WordPress 6.9.4
  • PHP 8.x (the strict TypeError behavior requires PHP 8.0+)
  • Reproduced on WP Engine hosting

Related tickets (same defensive-coding pattern):

  • #44582 — Undefined property stdClass::$plugin
  • #59413 — Undefined property stdClass::$plugin in class-wp-automatic-updater.php
  • #46382 — Undefined property stdClass::$current in class-walker-nav-menu.php

This is part of a broader pattern of WP core code paths that trust stdClass properties on API responses without guards. The fix here is one-line and low-risk.

Change History (1)

This ticket was mentioned in PR #11835 on WordPress/wordpress-develop by @malaytiwari.


22 hours ago
#1

  • Keywords has-patch added; needs-patch removed

WP_REST_Plugins_Controller::create_item() can throw a Fatal TypeError on PHP 8.x after a successful plugin install, causing the REST endpoint POST /wp/v2/plugins to return HTTP 500 even though the plugin files are already on disk. This patch adds a one-line defensive guard to prevent the crash.

Note: See TracTickets for help on using tickets.