Make WordPress Core

Changeset 62688


Ignore:
Timestamp:
07/10/2026 05:01:47 PM (2 months ago)
Author:
joedolson
Message:

Users: Improve interfaces for deleting users.

Provide clearer guidance for reassigning or deleting content when deleting users in both single and multisite instances. Content assignment is handled on a per-user basis, rather than forcing all content to be assigned to a single user. Add validation to enforce making a selection. Add fieldsets and legends to improve form structure and accessibility. Move inline JS to common.js. Improve backend logic to process per-user content handling. Exclude users being deleted from dropdown forms.

Developed in https://github.com/WordPress/wordpress-develop/pull/10502, https://github.com/WordPress/wordpress-develop/pull/12467

Props stefan.velthuys, ocean90, hellofromTonya, akshat2802, hubersen, fakhriaz, ozgursar, sirlouen, realloc, audrasjb, joedolson, afercia, krokodok.
Fixes #56914.

Location:
trunk/src
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/admin/common.js

    r61771 r62688  
    23572357        return pub;
    23582358})();
     2359
     2360/**
     2361 * Validate the delete-and-reassign users form and surface an accessible
     2362 * error summary instead of disabling the submit button.
     2363 *
     2364 * Disabled buttons can't be discovered by assistive technology, so rather
     2365 * than blocking submission we let the form submit, intercept it when content
     2366 * decisions are still missing, and present a focusable error summary that
     2367 * lists how many decisions remain and links straight to each one.
     2368 *
     2369 * Shared by both the single-site (wp-admin/users.php) and multisite/network
     2370 * (confirm_delete_users() in wp-admin/includes/ms.php) deletion forms. The two
     2371 * differ in markup: single site has one content decision per user, multisite
     2372 * has one decision per site a user belongs to (several radio groups per
     2373 * fieldset), and their reassign dropdowns use different "no selection" values.
     2374 * The logic below works per radio group so it covers both.
     2375 *
     2376 * @since 7.1.0
     2377 */
     2378(function(){
     2379        const { _n, sprintf } = wp.i18n;
     2380        const usersForm = document.querySelector( '.delete-and-reassign-users-form' );
     2381
     2382        // Check if the form exists and contains any radio buttons.
     2383        if ( ! usersForm || ! usersForm.querySelector( 'input[type="radio"]' ) ) {
     2384                return;
     2385        }
     2386
     2387        const summaryId = 'delete-users-error-summary';
     2388
     2389        /**
     2390         * Whether a reassign dropdown has no user selected.
     2391         *
     2392         * The "Select a user" placeholder value differs between the forms: the
     2393         * single-site dropdown uses an empty string, the multisite one uses the
     2394         * wp_dropdown_users() default of '-1'.
     2395         *
     2396         * @param {HTMLSelectElement} select The reassign dropdown.
     2397         * @return {boolean} True when no real user is selected.
     2398         */
     2399        function hasNoSelectedUser( select ) {
     2400                return '' === select.value || '-1' === select.value;
     2401        }
     2402
     2403        /**
     2404         * Builds a human-readable label for a radio group's decision.
     2405         *
     2406         * Combines the fieldset legend (the user) with the site context that
     2407         * precedes the group on multisite, so each summary entry is identifiable.
     2408         *
     2409         * @param {HTMLElement} group The radio group (<ul>) element.
     2410         * @return {string} The composed label.
     2411         */
     2412        function getDecisionLabel( group ) {
     2413                const fieldset = group.closest( 'fieldset' );
     2414                const legend   = fieldset ? fieldset.querySelector( 'legend' ) : null;
     2415                const parts    = [];
     2416
     2417                if ( legend ) {
     2418                        parts.push( legend.textContent.trim() );
     2419                }
     2420
     2421                // On multisite each radio group is preceded by a "Site: …" paragraph.
     2422                const previous = group.previousElementSibling;
     2423                if ( previous && previous !== legend && previous.textContent.trim() ) {
     2424                        parts.push( previous.textContent.trim() );
     2425                }
     2426
     2427                return parts.join( ' – ' );
     2428        }
     2429
     2430        // Keep the radio selection in sync with the reassign dropdown.
     2431        usersForm.querySelectorAll( 'select' ).forEach( function( selectElement ) {
     2432                selectElement.addEventListener( 'change', function( e ) {
     2433                        const item  = e.target.closest( 'li' );
     2434                        const radio = item ? item.querySelector( 'input[type="radio"]' ) : null;
     2435                        if ( radio ) {
     2436                                radio.checked = ! hasNoSelectedUser( e.target );
     2437                        }
     2438                });
     2439        });
     2440
     2441        /**
     2442         * Returns the radio groups whose content decision is still incomplete.
     2443         *
     2444         * A decision unit is a single radio group (<ul>), which maps to one user on
     2445         * single site and one site-per-user on multisite.
     2446         *
     2447         * @return {Array} Objects describing each incomplete decision.
     2448         */
     2449        function getIncompleteDecisions() {
     2450                const incomplete = [];
     2451
     2452                usersForm.querySelectorAll( 'fieldset ul' ).forEach( function( group ) {
     2453                        const radios = group.querySelectorAll( 'input[type="radio"]' );
     2454                        if ( ! radios.length ) {
     2455                                return;
     2456                        }
     2457
     2458                        const checked = group.querySelector( 'input[type="radio"]:checked' );
     2459
     2460                        // No option chosen yet.
     2461                        if ( ! checked ) {
     2462                                incomplete.push( { target: radios[ 0 ], label: getDecisionLabel( group ) } );
     2463                                return;
     2464                        }
     2465
     2466                        // "Attribute to another user" chosen, but no user selected.
     2467                        if ( 'reassign' === checked.value ) {
     2468                                const select = group.querySelector( 'select' );
     2469                                if ( select && hasNoSelectedUser( select ) ) {
     2470                                        incomplete.push( { target: select, label: getDecisionLabel( group ) } );
     2471                                }
     2472                        }
     2473                });
     2474
     2475                return incomplete;
     2476        }
     2477
     2478        /**
     2479         * Builds or refreshes the error summary markup.
     2480         *
     2481         * @param {Array} incomplete Incomplete decisions from getIncompleteDecisions().
     2482         * @return {string} The summary title, for announcing to assistive technology.
     2483         */
     2484        function renderErrorSummary( incomplete ) {
     2485                let summary = document.getElementById( summaryId );
     2486
     2487                if ( ! summary ) {
     2488                        summary = document.createElement( 'div' );
     2489                        summary.id = summaryId;
     2490                        summary.className = 'notice notice-error';
     2491                        summary.setAttribute( 'tabindex', '-1' );
     2492
     2493                        // The wrapper contains the form on single site and wraps it on
     2494                        // multisite; insert the summary right after the page heading.
     2495                        const wrap    = usersForm.querySelector( '.wrap' ) || usersForm.closest( '.wrap' ) || usersForm;
     2496                        const heading = wrap.querySelector( 'h1' );
     2497                        wrap.insertBefore( summary, heading ? heading.nextSibling : wrap.firstChild );
     2498                }
     2499
     2500                const count = incomplete.length;
     2501                const title = sprintf(
     2502                        /* translators: %s: Number of content decisions still required. */
     2503                        _n(
     2504                                '%s content decision is still required before you can delete.',
     2505                                '%s content decisions are still required before you can delete.',
     2506                                count
     2507                        ),
     2508                        count
     2509                );
     2510
     2511                // Clear any previous markup and invalid states before rebuilding,
     2512                // so decisions resolved since the last render are no longer flagged.
     2513                summary.textContent = '';
     2514                usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
     2515                        el.removeAttribute( 'aria-invalid' );
     2516                });
     2517
     2518                const titleEl = document.createElement( 'p' );
     2519                const strong  = document.createElement( 'strong' );
     2520                strong.textContent = title;
     2521                titleEl.appendChild( strong );
     2522                summary.appendChild( titleEl );
     2523
     2524                const list = document.createElement( 'ul' );
     2525                incomplete.forEach( function( item ) {
     2526                        const li   = document.createElement( 'li' );
     2527                        const link = document.createElement( 'a' );
     2528                        link.href        = '#' + item.target.id;
     2529                        link.textContent = item.label;
     2530                        link.addEventListener( 'click', function( e ) {
     2531                                e.preventDefault();
     2532                                item.target.focus();
     2533                        });
     2534                        li.appendChild( link );
     2535                        list.appendChild( li );
     2536
     2537                        item.target.setAttribute( 'aria-invalid', 'true' );
     2538                });
     2539                summary.appendChild( list );
     2540
     2541                return title;
     2542        }
     2543
     2544        /**
     2545         * Removes the error summary and clears invalid states.
     2546         */
     2547        function clearErrorState() {
     2548                const summary = document.getElementById( summaryId );
     2549                if ( summary ) {
     2550                        summary.remove();
     2551                }
     2552                usersForm.querySelectorAll( '[aria-invalid]' ).forEach( function( el ) {
     2553                        el.removeAttribute( 'aria-invalid' );
     2554                });
     2555        }
     2556
     2557        /**
     2558         * Refreshes the error summary to match the current form state.
     2559         *
     2560         * @param {boolean} moveFocus Whether to move focus to the summary and
     2561         *                            announce it (used on a failed submit).
     2562         * @return {number} The number of incomplete decisions.
     2563         */
     2564        function updateSummary( moveFocus ) {
     2565                const incomplete = getIncompleteDecisions();
     2566
     2567                if ( ! incomplete.length ) {
     2568                        clearErrorState();
     2569                        return 0;
     2570                }
     2571
     2572                const title = renderErrorSummary( incomplete );
     2573
     2574                if ( moveFocus ) {
     2575                        document.getElementById( summaryId ).focus();
     2576                        if ( window.wp && window.wp.a11y ) {
     2577                                window.wp.a11y.speak( title, 'assertive' );
     2578                        }
     2579                }
     2580
     2581                return incomplete.length;
     2582        }
     2583
     2584        usersForm.addEventListener( 'submit', function( e ) {
     2585                if ( updateSummary( true ) > 0 ) {
     2586                        e.preventDefault();
     2587                }
     2588        });
     2589
     2590        // Keep an existing summary current as decisions are resolved, without
     2591        // stealing focus on every interaction.
     2592        usersForm.addEventListener( 'change', function() {
     2593                if ( document.getElementById( summaryId ) ) {
     2594                        updateSummary( false );
     2595                }
     2596        });
     2597})();
  • trunk/src/wp-admin/css/common.css

    r62526 r62688  
    26062606}
    26072607
     2608.delete-and-reassign-users-form legend {
     2609        margin: 1em 0;
     2610        line-height: 1.5;
     2611        float: inline-start;
     2612}
     2613
     2614.delete-and-reassign-users-form fieldset > fieldset,
     2615.delete-and-reassign-users-form fieldset > ul {
     2616        clear: both;
     2617}
     2618
     2619.delete-and-reassign-users-form fieldset:has(select[aria-invalid="true"]):not(fieldset:has(fieldset)) {
     2620        border-left: 4px solid #cc1818;
     2621        background: #fcf0f0;
     2622        padding-left: 12px;
     2623}
     2624
    26082625.importers {
    26092626        font-size: 16px;
  • trunk/src/wp-admin/includes/deprecated.php

    r61440 r62688  
    15901590        return $post;
    15911591}
     1592
     1593/**
     1594 * Was used to add JavaScript to the delete users form.
     1595 *
     1596 * @since 3.5.0
     1597 * @deprecated 7.1.0
     1598 * @access private
     1599 */
     1600function delete_users_add_js() {
     1601        _deprecated_function( __FUNCTION__, '7.1.0' );
     1602}
  • trunk/src/wp-admin/includes/ms.php

    r62086 r62688  
    857857 */
    858858function confirm_delete_users( $users ) {
     859        global $wpdb;
     860
    859861        $current_user = wp_get_current_user();
    860862        if ( ! is_array( $users ) || empty( $users ) ) {
    861863                return false;
    862864        }
     865
    863866        ?>
    864         <h1><?php esc_html_e( 'Users' ); ?></h1>
     867        <h1><?php esc_html_e( 'Delete Users' ); ?></h1>
    865868
    866869        <?php if ( 1 === count( $users ) ) : ?>
     
    870873        <?php endif; ?>
    871874
    872         <form action="users.php?action=dodelete" method="post">
     875        <form action="users.php?action=dodelete" method="post" class="delete-and-reassign-users-form">
    873876        <input type="hidden" name="dodelete" />
    874877        <?php
    875878        wp_nonce_field( 'ms-users-delete' );
    876879        $site_admins = get_super_admins();
    877         $admin_out   = '<option value="' . esc_attr( $current_user->ID ) . '">' . $current_user->user_login . '</option>';
    878880        ?>
    879881        <table class="form-table" role="presentation">
    880882        <?php
    881         $allusers = (array) $_POST['allusers'];
    882         foreach ( $allusers as $user_id ) {
     883        foreach ( $users as $user_id ) {
    883884                if ( '' !== $user_id && '0' !== $user_id ) {
    884885                        $delete_user = get_userdata( $user_id );
     
    903904                                );
    904905                        }
     906
    905907                        ?>
    906908                        <tr>
     
    913915                        if ( ! empty( $blogs ) ) {
    914916                                ?>
    915                                 <td><fieldset><p><legend>
     917                                <td><fieldset><legend>
    916918                                <?php
    917919                                printf(
    918                                         /* translators: %s: User login. */
    919                                         __( 'What should be done with content owned by %s?' ),
    920                                         '<em>' . $delete_user->user_login . '</em>'
     920                                        /* translators: 1: User login, 2: User ID. */
     921                                        __( '%1$s (ID #%2$s): What should be done with the content owned by this user?' ),
     922                                        '<strong>' . $delete_user->user_login . '</strong>',
     923                                        $user_id
    921924                                );
    922925                                ?>
    923                                 </legend></p>
     926                                </legend>
    924927                                <?php
    925928                                foreach ( (array) $blogs as $key => $details ) {
     
    927930                                                array(
    928931                                                        'blog_id' => $details->userblog_id,
    929                                                         'fields'  => array( 'ID', 'user_login' ),
     932                                                        'fields'  => array( 'ID' ),
     933                                                        'exclude' => $users,
    930934                                                )
    931935                                        );
    932936
     937                                        $blog_users = wp_list_pluck( $blog_users, 'ID' );
     938
    933939                                        if ( is_array( $blog_users ) && ! empty( $blog_users ) ) {
    934                                                 $user_site     = "<a href='" . esc_url( get_home_url( $details->userblog_id ) ) . "'>{$details->blogname}</a>";
    935                                                 $user_dropdown = '<label for="reassign_user" class="screen-reader-text">' .
    936                                                                 /* translators: Hidden accessibility text. */
    937                                                                 __( 'Select a user' ) .
    938                                                         '</label>';
    939                                                 $user_dropdown .= "<select name='blog[$user_id][$key]' id='reassign_user'>";
    940                                                 $user_list      = '';
    941 
    942                                                 foreach ( $blog_users as $user ) {
    943                                                         if ( ! in_array( (int) $user->ID, $allusers, true ) ) {
    944                                                                 $user_list .= "<option value='{$user->ID}'>{$user->user_login}</option>";
     940                                                $user_site = "<a href='" . esc_url( get_home_url( $details->userblog_id ) ) . "'>{$details->blogname}</a>";
     941                                                switch_to_blog( $details->userblog_id );
     942                                                /* This filter is documented in wp-admin/users.php */
     943                                                $user_has_content = (bool) apply_filters( 'users_have_additional_content', false, array( $delete_user->ID ) );
     944
     945                                                if ( ! $user_has_content ) {
     946                                                        if ( $wpdb->get_var(
     947                                                                $wpdb->prepare(
     948                                                                        "SELECT ID FROM {$wpdb->posts}
     949                                                                        WHERE post_author = %d
     950                                                                        LIMIT 1",
     951                                                                        $delete_user->ID
     952                                                                )
     953                                                        ) ) {
     954                                                                $user_has_content = true;
     955                                                        } elseif ( $wpdb->get_var(
     956                                                                $wpdb->prepare(
     957                                                                        "SELECT link_id FROM {$wpdb->links}
     958                                                                        WHERE link_owner = %d
     959                                                                        LIMIT 1",
     960                                                                        $delete_user->ID
     961                                                                )
     962                                                        ) ) {
     963                                                                $user_has_content = true;
    945964                                                        }
    946965                                                }
    947 
    948                                                 if ( '' === $user_list ) {
    949                                                         $user_list = $admin_out;
    950                                                 }
    951 
    952                                                 $user_dropdown .= $user_list;
    953                                                 $user_dropdown .= "</select>\n";
    954                                                 ?>
    955                                                 <ul style="list-style:none;">
    956                                                         <li>
     966                                                restore_current_blog();
     967
     968                                                if ( ! $user_has_content ) {
     969                                                        ?>
     970                                                        <p>
     971                                                        <?php
     972                                                        /* translators: %s: Link to user's site. */
     973                                                        printf( __( 'Site: %s' ), $user_site );
     974                                                        ?>
     975                                                        </p>
     976                                                        <input type="hidden" id="delete_option_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="delete" required />
     977                                                        <p><?php _e( 'This user does not have any content.' ); ?></p>
     978                                                        <?php
     979                                                } else {
     980                                                        ?>
     981                                                        <fieldset>
     982                                                                <legend>
    957983                                                                <?php
    958984                                                                /* translators: %s: Link to user's site. */
    959985                                                                printf( __( 'Site: %s' ), $user_site );
    960986                                                                ?>
    961                                                         </li>
    962                                                         <li><label><input type="radio" id="delete_option0" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="delete" checked="checked" />
    963                                                         <?php _e( 'Delete all content.' ); ?></label></li>
    964                                                         <li><label><input type="radio" id="delete_option1" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="reassign" />
    965                                                         <?php _e( 'Attribute all content to:' ); ?></label>
    966                                                         <?php echo $user_dropdown; ?></li>
    967                                                 </ul>
    968                                                 <?php
     987                                                                </legend>
     988                                                                <ul>
     989                                                                        <li>
     990                                                                                <input type="radio" id="delete_option_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="delete" required />
     991                                                                                <label for="delete_option_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>"><?php _e( 'Delete all content.' ); ?></label>
     992                                                                        </li>
     993                                                                        <li>
     994                                                                                <input type="radio" id="reassign_option_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="reassign" required />
     995                                                                                <label for="reassign_option_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>"><?php _e( 'Attribute all content to another user.' ); ?></label>
     996
     997                                                                                <label for="reassign_user_<?php echo esc_attr( $details->userblog_id . '_' . $delete_user->ID ); ?>" class="screen-reader-text"><?php _e( 'Select a user to attribute the content to.' ); ?></label>
     998                                                                                <?php
     999                                                                                wp_dropdown_users(
     1000                                                                                        array(
     1001                                                                                                'show_option_none' => __( 'Select a user' ),
     1002                                                                                                'name'    => "blog[$user_id][$key]",
     1003                                                                                                'include' => $blog_users,
     1004                                                                                                'show'    => 'display_name_with_login',
     1005                                                                                                'id'      => "reassign_user_{$details->userblog_id}_{$delete_user->ID}",
     1006                                                                                        )
     1007                                                                                );
     1008                                                                                ?>
     1009
     1010                                                                        </li>
     1011                                                                </ul>
     1012                                                        </fieldset>
     1013                                                        <?php
     1014                                                }
    9691015                                        }
    9701016                                }
     
    9831029        <?php
    9841030        /** This action is documented in wp-admin/users.php */
    985         do_action( 'delete_user_form', $current_user, $allusers );
     1031        do_action( 'delete_user_form', $current_user, $users );
    9861032
    9871033        if ( 1 === count( $users ) ) :
     
    9931039        endif;
    9941040
    995         submit_button( __( 'Confirm Deletion' ), 'primary' );
     1041        submit_button( __( 'Confirm Deletion' ), 'primary', 'submit', false, array( 'id' => 'confirm-users-deletion' ) );
    9961042        ?>
    9971043        </form>
  • trunk/src/wp-admin/includes/user.php

    r62632 r62688  
    564564
    565565/**
    566  * @since 3.5.0
    567  * @access private
    568  */
    569 function delete_users_add_js() {
    570         ?>
    571 <script>
    572 jQuery( function($) {
    573         var submit = $('#submit').prop('disabled', true);
    574         $('input[name="delete_option"]').one('change', function() {
    575                 submit.prop('disabled', false);
    576         });
    577         $('#reassign_user').focus( function() {
    578                 $('#delete_option1').prop('checked', true).trigger('change');
    579         });
    580 } );
    581 </script>
    582         <?php
    583 }
    584 
    585 /**
    586566 * Optional SSL preference that can be turned on by hooking to the 'personal_options' action.
    587567 *
  • trunk/src/wp-admin/network/users.php

    r61660 r62688  
    5757                                $doaction     = $_POST['action'];
    5858                                $userfunction = '';
    59 
    60                                 foreach ( (array) $_POST['allusers'] as $user_id ) {
     59                                $allusers     = (array) $_POST['allusers'];
     60
     61                                foreach ( $allusers as $user_id ) {
    6162                                        if ( ! empty( $user_id ) ) {
    6263                                                switch ( $doaction ) {
     
    7374
    7475                                                                echo '<div class="wrap">';
    75                                                                 confirm_delete_users( $_POST['allusers'] );
     76                                                                confirm_delete_users( $allusers );
    7677                                                                echo '</div>';
    7778
     
    189190                        }
    190191
     192                        /*
     193                         * Validate that every "reassign" choice has a real target before making
     194                         * any changes. The confirmation form blocks this client-side, so this is
     195                         * a defense-in-depth guard against an invalid reassignment (e.g. content
     196                         * reassigned to the non-existent user "-1") when JavaScript is bypassed.
     197                         * Bail out entirely so the deletion is all-or-nothing.
     198                         */
     199                        if ( ! empty( $_POST['blog'] ) && is_array( $_POST['blog'] ) && ! empty( $_POST['delete'] ) ) {
     200                                foreach ( $_POST['blog'] as $id => $blogs ) {
     201                                        foreach ( $blogs as $blogid => $reassign_user_id ) {
     202                                                if ( isset( $_POST['delete'][ $blogid ][ $id ] )
     203                                                        && 'reassign' === $_POST['delete'][ $blogid ][ $id ]
     204                                                        && (int) $reassign_user_id < 1
     205                                                ) {
     206                                                        wp_redirect(
     207                                                                add_query_arg(
     208                                                                        array( 'error' => 'missing_reassign' ),
     209                                                                        network_admin_url( 'users.php' )
     210                                                                )
     211                                                        );
     212                                                        exit;
     213                                                }
     214                                        }
     215                                }
     216                        }
     217
    191218                        if ( ! empty( $_POST['blog'] ) && is_array( $_POST['blog'] ) ) {
    192219                                foreach ( $_POST['blog'] as $id => $users ) {
     
    311338        );
    312339}
     340
     341if ( isset( $_REQUEST['error'] ) && 'missing_reassign' === $_REQUEST['error'] ) {
     342        wp_admin_notice(
     343                __( 'No users were deleted because no user was selected for content reassignment.' ),
     344                array(
     345                        'type'        => 'error',
     346                        'dismissible' => true,
     347                        'id'          => 'message',
     348                )
     349        );
     350}
    313351?>
    314352<div class="wrap">
  • trunk/src/wp-admin/users.php

    r62026 r62688  
    214214                        }
    215215
    216                         switch ( $_REQUEST['delete_option'] ) {
     216                        if ( 'reassign' === $_REQUEST['delete_option'][ $id ] && empty( $_REQUEST['reassign_user'][ $id ] ) ) {
     217                                $update = 'err_missing_reassign';
     218                                continue;
     219                        }
     220
     221                        switch ( $_REQUEST['delete_option'][ $id ] ) {
    217222                                case 'delete':
    218223                                        wp_delete_user( $id );
    219224                                        break;
    220225                                case 'reassign':
    221                                         wp_delete_user( $id, $_REQUEST['reassign_user'] );
     226                                        wp_delete_user( $id, $_REQUEST['reassign_user'][ $id ] );
    222227                                        break;
    223228                        }
     
    307312                }
    308313
    309                 /**
    310                  * Filters whether the users being deleted have additional content
    311                  * associated with them outside of the `post_author` and `link_owner` relationships.
    312                  *
    313                  * @since 5.2.0
    314                  *
    315                  * @param bool  $users_have_additional_content Whether the users have additional content. Default false.
    316                  * @param int[] $user_ids                      Array of IDs for users being deleted.
    317                  */
    318                 $users_have_content = (bool) apply_filters( 'users_have_additional_content', false, $user_ids );
    319 
    320                 if ( $user_ids && ! $users_have_content ) {
    321                         if ( $wpdb->get_var(
    322                                 "SELECT ID FROM {$wpdb->posts}
    323                                 WHERE post_author IN( " . implode( ',', $user_ids ) . ' )
    324                                 LIMIT 1'
    325                         ) ) {
    326                                 $users_have_content = true;
    327                         } elseif ( $wpdb->get_var(
    328                                 "SELECT link_id FROM {$wpdb->links}
    329                                 WHERE link_owner IN( " . implode( ',', $user_ids ) . ' )
    330                                 LIMIT 1'
    331                         ) ) {
    332                                 $users_have_content = true;
    333                         }
    334                 }
    335 
    336                 if ( $users_have_content ) {
    337                         add_action( 'admin_head', 'delete_users_add_js' );
    338                 }
    339 
    340314                require_once ABSPATH . 'wp-admin/admin-header.php';
    341315                ?>
    342                 <form method="post" name="updateusers" id="updateusers">
     316                <form method="post" name="updateusers" id="updateusers" class="delete-and-reassign-users-form">
    343317                <?php wp_nonce_field( 'delete-users' ); ?>
    344318                <?php echo $referer; ?>
     
    366340                <ul>
    367341                <?php
    368                 $go_delete = 0;
     342                $go_delete          = 0;
     343                $users_have_content = false;
    369344
    370345                foreach ( $all_user_ids as $id ) {
     
    386361                                        esc_attr( $id )
    387362                                );
    388                                 printf(
    389                                         /* translators: 1: User ID, 2: User login. */
    390                                         __( 'ID #%1$s: %2$s' ),
    391                                         $id,
    392                                         $user->user_login
    393                                 );
     363
     364                                /**
     365                                 * Filters whether the users being deleted have additional content
     366                                 * associated with them outside of the `post_author` and `link_owner` relationships.
     367                                 *
     368                                 * @since 5.2.0
     369                                 *
     370                                 * @param bool  $users_have_additional_content Whether the users have additional content. Default false.
     371                                 * @param int[] $user_ids                      Array of IDs for users being deleted.
     372                                 */
     373                                $user_has_content = (bool) apply_filters( 'users_have_additional_content', false, array( $id ) );
     374
     375                                if ( ! $user_has_content ) {
     376                                        if ( $wpdb->get_var(
     377                                                $wpdb->prepare(
     378                                                        "SELECT ID FROM {$wpdb->posts}
     379                                                        WHERE post_author = %d
     380                                                        LIMIT 1",
     381                                                        $id
     382                                                )
     383                                        ) ) {
     384                                                $user_has_content = true;
     385                                        } elseif ( $wpdb->get_var(
     386                                                $wpdb->prepare(
     387                                                        "SELECT link_id FROM {$wpdb->links}
     388                                                        WHERE link_owner = %d
     389                                                        LIMIT 1",
     390                                                        $id
     391                                                )
     392                                        ) ) {
     393                                                $user_has_content = true;
     394                                        }
     395                                }
     396
     397                                if ( ! $user_has_content ) {
     398                                        ?>
     399                                        <input type="hidden" name="delete_option[<?php echo esc_attr( $id ); ?>]" value="delete" required />
     400                                        <p>
     401                                        <?php
     402                                        printf(
     403                                                /* translators: 1: User login. 2: User ID */
     404                                                __( '%1$s (ID #%2$s): This user does not have any content.' ),
     405                                                '<strong>' . $user->user_login . '</strong>',
     406                                                $id
     407                                        );
     408                                        ?>
     409                                        </p>
     410                                        <?php
     411                                } else {
     412                                        ?>
     413                                        <fieldset>
     414                                        <legend>
     415                                        <?php
     416                                        printf(
     417                                                /* translators: 1: User login, 2: User ID. */
     418                                                __( '%1$s (ID #%2$s): What should be done with the content owned by this user?' ),
     419                                                '<strong>' . $user->user_login . '</strong>',
     420                                                $id
     421                                        );
     422                                        ?>
     423                                        </legend>
     424
     425                                        <ul>
     426                                                <li>
     427                                                        <input type="radio" id="delete_option_<?php echo esc_attr( $id ); ?>" name="delete_option[<?php echo esc_attr( $id ); ?>]" value="delete" required />
     428                                                        <label for="delete_option_<?php echo esc_attr( $id ); ?>"><?php _e( 'Delete all content.' ); ?></label>
     429                                                </li>
     430                                                <li>
     431                                                        <input type="radio" id="reassign_option_<?php echo esc_attr( $id ); ?>" name="delete_option[<?php echo esc_attr( $id ); ?>]" value="reassign" required />
     432                                                        <label for="reassign_option_<?php echo esc_attr( $id ); ?>"><?php _e( 'Attribute all content to another user.' ); ?></label>
     433
     434                                                        <label for="reassign_user_<?php echo esc_attr( $id ); ?>" class="screen-reader-text"><?php _e( 'Select a user to attribute the content to.' ); ?></label>
     435                                                        <?php
     436                                                        wp_dropdown_users(
     437                                                                array(
     438                                                                        'show_option_none'  => __( 'Select a user' ),
     439                                                                        'option_none_value' => '',
     440                                                                        'name'              => 'reassign_user[' . $id . ']',
     441                                                                        'id'                => 'reassign_user_' . $id,
     442                                                                        'exclude'           => $user_ids,
     443                                                                        'show'              => 'display_name_with_login',
     444                                                                )
     445                                                        );
     446                                                        ?>
     447                                                </li>
     448                                        </ul>
     449                                        </fieldset>
     450                                        <?php
     451                                        $users_have_content = true;
     452                                }
     453
    394454                                echo "</li>\n";
    395455
     
    402462                <?php
    403463                if ( $go_delete ) :
    404 
    405                         if ( ! $users_have_content ) :
    406                                 ?>
    407                                 <input type="hidden" name="delete_option" value="delete" />
    408                         <?php else : ?>
    409                                 <fieldset>
    410                                 <?php if ( 1 === $go_delete ) : ?>
    411                                         <p><legend><?php _e( 'What should be done with content owned by this user?' ); ?></legend></p>
    412                                 <?php else : ?>
    413                                         <p><legend><?php _e( 'What should be done with content owned by these users?' ); ?></legend></p>
    414                                 <?php endif; ?>
    415 
    416                                 <ul style="list-style:none;">
    417                                         <li>
    418                                                 <input type="radio" id="delete_option0" name="delete_option" value="delete" />
    419                                                 <label for="delete_option0"><?php _e( 'Delete all content.' ); ?></label>
    420                                         </li>
    421                                         <li>
    422                                                 <input type="radio" id="delete_option1" name="delete_option" value="reassign" />
    423                                                 <label for="delete_option1"><?php _e( 'Attribute all content to:' ); ?></label>
    424                                                 <?php
    425                                                 wp_dropdown_users(
    426                                                         array(
    427                                                                 'name'    => 'reassign_user',
    428                                                                 'exclude' => $user_ids,
    429                                                                 'show'    => 'display_name_with_login',
    430                                                         )
    431                                                 );
    432                                                 ?>
    433                                         </li>
    434                                 </ul>
    435                                 </fieldset>
    436                                 <?php
    437                         endif;
    438 
    439464                        /**
    440465                         * Fires at the end of the delete users form prior to the confirm button.
     
    613638                $messages = array();
    614639                if ( isset( $_GET['update'] ) ) :
     640                        $delete_count = isset( $_GET['delete_count'] ) ? (int) $_GET['delete_count'] : 0;
     641
    615642                        switch ( $_GET['update'] ) {
    616643                                case 'del':
    617644                                case 'del_many':
    618                                         $delete_count = isset( $_GET['delete_count'] ) ? (int) $_GET['delete_count'] : 0;
    619645                                        if ( 1 === $delete_count ) {
    620646                                                $message = __( 'User deleted.' );
     
    696722                                                )
    697723                                        );
    698                                         $messages[] = wp_get_admin_notice(
    699                                                 __( 'Other user roles have been changed.' ),
    700                                                 array(
    701                                                         'id'                 => 'message',
    702                                                         'additional_classes' => array( 'updated' ),
    703                                                         'dismissible'        => true,
    704                                                 )
    705                                         );
     724                                        if ( $delete_count > 0 ) {
     725                                                $messages[] = wp_get_admin_notice(
     726                                                        __( 'Other user roles have been changed.' ),
     727                                                        array(
     728                                                                'id'                 => 'message',
     729                                                                'additional_classes' => array( 'updated' ),
     730                                                                'dismissible'        => true,
     731                                                        )
     732                                                );
     733                                        }
    706734                                        break;
    707735                                case 'err_admin_del':
     
    714742                                                )
    715743                                        );
    716                                         $messages[] = wp_get_admin_notice(
    717                                                 __( 'Other users have been deleted.' ),
    718                                                 array(
    719                                                         'id'                 => 'message',
    720                                                         'additional_classes' => array( 'updated' ),
    721                                                         'dismissible'        => true,
    722                                                 )
    723                                         );
     744                                        if ( $delete_count > 0 ) {
     745                                                $messages[] = wp_get_admin_notice(
     746                                                        __( 'Other users have been deleted.' ),
     747                                                        array(
     748                                                                'id'                 => 'message',
     749                                                                'additional_classes' => array( 'updated' ),
     750                                                                'dismissible'        => true,
     751                                                        )
     752                                                );
     753                                        }
     754                                        break;
     755                                case 'err_missing_reassign':
     756                                        $messages[] = wp_get_admin_notice(
     757                                                __( 'Users could not be deleted because no user was selected for content reassignment.' ),
     758                                                array(
     759                                                        'id'                 => 'message',
     760                                                        'additional_classes' => array( 'error' ),
     761                                                        'dismissible'        => true,
     762                                                )
     763                                        );
     764                                        if ( $delete_count > 0 ) {
     765                                                $messages[] = wp_get_admin_notice(
     766                                                        __( 'Other users have been deleted.' ),
     767                                                        array(
     768                                                                'id'                 => 'message',
     769                                                                'additional_classes' => array( 'updated' ),
     770                                                                'dismissible'        => true,
     771                                                        )
     772                                                );
     773                                        }
    724774                                        break;
    725775                                case 'remove':
Note: See TracChangeset for help on using the changeset viewer.