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: |
|
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:
- Send an authenticated
POSTrequest to/wp-json/wp/v2/pluginswith aslugfor a plugin whoseplugins_apiresponse does not contain a populatedlanguage_packsarray. - Observe that the plugin is correctly installed to
wp-content/plugins. - Observe that the REST request returns HTTP 500.
- 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 FatalTypeErroron PHP 8.x after a successful plugin install, causing the REST endpointPOST /wp/v2/pluginsto returnHTTP 500even though the plugin files are already on disk. This patch adds a one-line defensive guard to prevent the crash.