Skip to content

Plugins window: real updates, not just a phantom badge#185

Merged
AllTerrainDeveloper merged 1 commit into
trunkfrom
fix-update-plugins-issue
May 12, 2026
Merged

Plugins window: real updates, not just a phantom badge#185
AllTerrainDeveloper merged 1 commit into
trunkfrom
fix-update-plugins-issue

Conversation

@AllTerrainDeveloper
Copy link
Copy Markdown
Collaborator

@AllTerrainDeveloper AllTerrainDeveloper commented May 12, 2026

Fixes the "Update available" tab showing zero plugins while the dock badge reported pending updates, and adds the per-row + bulk "Update now" UX Core has on wp-admin/plugins.php.

Update.plugins.mov

Bugs fixed

1. Stale update_plugins transient on REST requests

Core only refreshes update_plugins on load-plugins.php / load-update-core.php / cron. REST is not on that list, so a freshly loaded Plugins window saw an empty/stale snapshot while the dock badge (which is rebuilt off $menu during the desktop-shell page load) reported real numbers.

Fix: desktop_mode_plugins_window_maybe_refresh_update_transient() — lazy refresh on the first row of every REST plugins collection, inheriting Core's own 12h throttle (_maybe_update_plugins() posture). Guarded by a static so it runs once per request, and by a new desktop_mode_plugins_window_refresh_updates filter for hosts that orchestrate updates themselves.

2. .php-strip mismatch (the real culprit)

Core's WP_REST_Plugins_Controller emits the plugin field with the .php stripped (elementor/elementor), but every internal WP data structure — the update_plugins transient, Plugin_Upgrader::bulk_upgrade(), wp_ajax_update_plugin — keys by the full filename (elementor/elementor.php). The REST field callback was looking up the stripped key against a fully-keyed transient → miss on every row → all rows reported available: false.

Fix: desktop_mode_plugins_window_row_plugin_file() re-appends .php when missing. Routed desktop_mode_update_available and desktop_mode_size_kb through it (the size callback had the same latent bug for single-file plugins like Hello Dolly). Mirrored on the JS side in updateInstalledPlugin() so wp_ajax_update_plugin receives the form it actually keys against (otherwise the upgrader fell through to "Plugin is at the latest version").

Update-now UX (matches wp-admin/plugins.php)

  • Per-row Update button — primary Update to X.Y.Z button in the actions cell when the user has update_plugins AND the transient entry ships a package URL. When available && ! package (premium / no-wp.org-zip plugins), surfaces the same "Auto-update unavailable" hint Core renders.
  • Bulk UpdateUpdate N action in the bulk bar; fans through the same single-flight queue.
  • Single-flight queue (update-queue.ts) — mirrors Core's wp.updates.ajaxLocked + FIFO wp.updates.queue semantics. Concurrent Plugin_Upgrader runs corrupt the update_plugins transient (documented in Core's own AJAX handler); enforcing single-flight is non-optional, not a polish item.
  • Reuses Core's AJAX endpointwp_ajax_update_plugin with the existing 'updates' nonce. No reimplementation of Plugin_Upgrader, which keeps us out of wp-admin/includes/ and keeps Plugin Check green.
  • Count badge on the "Update available" segment<wpd-badge tone="warning">N</wpd-badge> that recounts on every row update; hidden when zero.
  • Live cross-view sync — success broadcasts on desktop-mode.plugin.changed with action: 'update', refreshes the framework menu (dock badge), bumps the row version inline.

Data shape changes

  • desktop_mode_update_available REST field now { available, new_version, package, slug } (was { available, new_version }). package is the gate the JS uses to decide Update-button vs Auto-update-unavailable hint — matches Core's own gating in wp_plugin_update_row().
  • caps config blob gains update (boolean, mirrors current_user_can( 'update_plugins' )). Server still re-validates every mutation in the AJAX handler.

New filter

desktop_mode_plugins_window_refresh_updates (bool, default true) — opt-out of the lazy transient refresh. For managed hosts that run their own update orchestration.

Tests

tests/phpunit/tests/pluginsWindowRegistration.php: +6 tests covering hit case with REST-stripped row shape (regression test for the .php strip), missing-package fallback, the 12h throttle, the opt-out filter, and caps.update for admin + editor. 27/27 in the class green, 660/660 in the full plugin suite green.

Files

  • includes/plugins-window/rest-fields.php — normalization helper + lazy refresh + extended field shape.
  • includes/plugins-window/permissions.phpcaps.update.
  • src/plugins-window/rest.tsupdateInstalledPlugin() (re-appends .php before hitting wp_ajax_update_plugin).
  • src/plugins-window/update-queue.ts (new) — single-flight queue.
  • src/plugins-window/installed-view.ts — Update button (row + bulk), count badge, optimistic row repaint, broadcast on success.
  • src/plugins-window/types.ts — extended desktop_mode_update_available + caps.update.
  • tests/phpunit/tests/pluginsWindowRegistration.php — new coverage.
  • docs/hooks-reference.md — new filter + extended field shape.
Open WordPress Playground Preview

- Introduced `desktop_mode_plugins_window_refresh_updates` filter to control lazy refresh of plugin updates in REST API.
- Updated REST fields to include `package` and `slug` in `desktop_mode_update_available` for better update handling.
- Implemented `runUpdate` and `runBulk` functions for updating plugins, ensuring proper state management during updates.
- Added `enqueueUpdateJob` to manage concurrent plugin updates, preventing transient corruption.
- Enhanced session recovery mechanisms to handle stale nonces and improve user experience during session expiry.
- Implemented cross-tab synchronization for plugin state changes between Installed and Browse tabs.
- Added comprehensive unit tests to validate new functionality and ensure reliability.
@AllTerrainDeveloper AllTerrainDeveloper enabled auto-merge (squash) May 12, 2026 15:26
@github-actions
Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@AllTerrainDeveloper AllTerrainDeveloper merged commit 4a122da into trunk May 12, 2026
5 checks passed
@AllTerrainDeveloper AllTerrainDeveloper deleted the fix-update-plugins-issue branch May 12, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant