Make WordPress Core

Opened 5 weeks ago

Last modified 3 weeks ago

#65191 reviewing defect (bug)

deletePluginSuccess does not update counts for custom plugin_status groups

Reported by: andrewp's profile andrew.p Owned by: sergeybiryukov's profile SergeyBiryukov
Milestone: 7.1 Priority: normal
Severity: minor Version:
Component: Plugins Keywords: has-patch
Focuses: Cc:

Description

#60495 added the plugins_list_status_text filter, so plugins can register custom group tabs on plugins.php by adding entries to the array passed through plugins_list and supplying a label through the new filter.

The server side works. The count badge is right on initial load, and ?plugin_status=<custom> filters the list as expected.

The AJAX delete flow does not keep up. wp.updates.deletePluginSuccess in wp-admin/js/updates.js only decrements counts for the hardcoded buckets (upgrade, inactive, active, recently_activated). Anything added through plugins_list is skipped, so the badge on the custom tab stays stale until the page reloads.

Steps to reproduce

  1. Drop the snippet below into a must-use plugin to register a sample group.
<?php
add_filter( 'plugins_list', function( $plugins ) {
        if ( empty( $plugins['all'] ) ) {
                return $plugins;
        }

        $plugins['demo_group'] = array_filter(
                $plugins['all'],
                static function ( $data, $file ) {
                        return str_starts_with( $file, 'a' );
                },
                ARRAY_FILTER_USE_BOTH
        );

        return $plugins;
} );

add_filter( 'plugins_list_status_text', function( $text, $count, $type ) {
        return $type === 'demo_group' ? 'Demo Group' : $text;
}, 10, 3 );
  1. Install two plugins whose folder starts with a, plus one that does not.
  2. Visit wp-admin/plugins.php. The "Demo Group (2)" tab shows the right count.
  3. Click the tab. Delete one of the listed plugins from the row actions.
  4. The row fades out, but the tab still reads "(2)".
  5. Reload the page. The tab now reads "(1)", matching what the server returns.

The hardcoded tabs (Active, Inactive) decrement correctly in the same flow.

Cause

In wp-admin/js/updates.js, deletePluginSuccess checks four buckets and stops:

if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) { ... }
if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) { ... }
if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) { ... }
if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) { ... }

Other keys present in plugins are not iterated.

Suggested approach

After the existing blocks, walk any remaining keys and apply the same pattern. Hardcoded keys keep their explicit path, so existing sites see no behavior change.

var knownKeys = [ 'all', 'search', 'upgrade', 'active', 'inactive',
        'recently_activated', 'mustuse', 'dropins', 'paused',
        'auto-update-enabled', 'auto-update-disabled' ];

_.each( _.keys( plugins ), function( key ) {
        if ( -1 !== _.indexOf( knownKeys, key ) ) {
                return;
        }
        if ( ! _.isArray( plugins[ key ] ) ) {
                return;
        }
        if ( -1 === _.indexOf( plugins[ key ], response.plugin ) ) {
                return;
        }

        plugins[ key ] = _.without( plugins[ key ], response.plugin );

        var $tab = $views.find( '.' + key );

        if ( plugins[ key ].length ) {
                $tab.find( '.count' ).text( '(' + plugins[ key ].length + ')' );
        } else {
                $tab.remove();
        }
} );

The activate, deactivate, and update success callbacks likely have the same gap. Happy to roll those into one patch if reviewers want a single sweep, otherwise this ticket can stay scoped to delete.

Notes

wp-plugin-delete-success is already triggered as a jQuery event, so individual plugins can shim this on the client. With #60495 turning custom groups into a documented extension point, asking every consumer to ship the same workaround does not feel right.

Test plan

  • Hardcoded tabs: delete still decrements Active, Inactive, and Update available exactly as before.
  • Custom group registered via plugins_list: the count drops on delete, and the tab disappears when it reaches zero.
  • Sites with no custom group: no change, no extra DOM lookups.

Attachments (1)

wp-core-fix-deletePluginSuccess.diff (1.6 KB) - added by andrew.p 5 weeks ago.

Download all attachments as: .zip

Change History (6)

This ticket was mentioned in PR #11752 on WordPress/wordpress-develop by andreas-pa.


5 weeks ago
#1

  • Keywords has-patch added; needs-patch removed

Trac ticket: https://core.trac.wordpress.org/ticket/65191

Decrements counts for groups registered via the plugins_list filter when a plugin is deleted via AJAX, so tabs whose label is supplied through plugins_list_status_text (introduced in #60495) stay in sync without requiring a page reload. The four hardcoded buckets (upgrade, inactive, active, recently_activated) plus auto-update-enabled / auto-update-disabled keep their explicit fast paths; the new loop only runs for keys outside that set.

## Reproduction

See the steps in Trac #65191. TL;DR: register a group via plugins_list with a label supplied through plugins_list_status_text, delete a plugin in that group via the row action, and observe the count badge stay stale until reload.

## Test plan

  • [x] Hardcoded tabs (Active, Inactive, Update available) still decrement on delete.
  • [x] Custom group registered via plugins_list: count drops on delete; tab disappears at zero.
  • [x] Sites with no custom groups: no behavior change.

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


5 weeks ago
#2

Improvement to PR: https://github.com/WordPress/wordpress-develop/pull/11752.
This PR refactors the rendering of the | separator between filter links in WP_List_Table::views() (the .subsubsub navigation — e.g. "All | Active | Inactive" on the Plugins screen).

Before: the separator was hardcoded in the PHP output via implode( " |</li>\n", $views ) in wp-admin/includes/class-wp-list-table.php.

After: PHP emits clean </li> markup, and the separator is drawn in CSS via a ::after pseudo-element.

Trac ticket: https://core.trac.wordpress.org/ticket/65191

## Use of AI Tools

#3 @alessioarzentondev
5 weeks ago

Worked on this during WordPress Contributor Day, Torino 2026.

While testing the previous PR, we noticed that when a plugin is deleted and the corresponding <li> is removed from the WP_List_Table filters via Ajax, the | separator on the preceding <li> was left behind, because it was injected via PHP and therefore not aware of subsequent DOM changes.

Since it's purely a visual element, we think it belongs in CSS instead.

Moving it to a stylesheet keeps structure and presentation separated, makes the separator easier to restyle or remove, and handles the last item automatically without any special PHP logic. Nothing changes visually for the user, and the markup ends up a bit cleaner — including for screen readers and copy/paste, since the | is no longer part of the text content.

Props andrew.p, alessioarzentondev, gabrieleburgarella, enzo2018, lopo, realloc, f94leonardo.

#4 @SergeyBiryukov
4 weeks ago

  • Milestone changed from Awaiting Review to 7.1
  • Owner set to SergeyBiryukov
  • Status changed from new to reviewing

#5 @ozgursar
3 weeks ago

  • Keywords needs-testing removed

Test Report

Patch tested: https://github.com/WordPress/wordpress-develop/pull/11777

Environment

  • WordPress: 7.1-alpha-62161-src
  • Subdirectory: No
  • PHP: 8.2.29
  • Server: nginx/1.29.4
  • Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
  • Browser: Chrome 148.0.0.0
  • OS: macOS
  • Theme: Twenty Twenty-Five 1.5
  • MU Plugins: None activated
  • Plugins:
    • Code Snippets 3.9.6
    • Test Reports 1.3.0

Steps taken

  1. Add the snippet provided in the ticket description
  2. Install 2 plugins start with letter a
  3. Observe the Demo Group count increase to 2
  4. Delete one of them and observe that the count stays the same (as 2)
  5. Apply patch and repeat steps
  6. Observe that Demo Group count decreases to 1 as expected
  7. ✅ Patch is solving the problem

Expected result

  • After patch, we expect the dynamic tab's count update correctly same as the hardcoded tabs.

Screenshots/Screencast with results

Before: https://files.catbox.moe/qbq8df.mp4
After: https://files.catbox.moe/177r7i.mp4

Note: See TracTickets for help on using tickets.