Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/js/_enqueues/admin/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -2354,3 +2354,59 @@ $( function( $ ) {
// Expose public methods.
return pub;
})();

/**
* Disable the submit button until all users radio buttons are checked.
*
* @since 7.0.0
*/
(function(){
const usersForm = document.querySelector( '.users-php .delete-and-reassign-users-form' );

// Check if the form exists and contains any radio buttons.
if ( ! usersForm || ! usersForm.querySelector( 'input[type="radio"]' ) ) {
return;
}

const submitBtn = usersForm.querySelector( 'input[type="submit"]' );

// Disable the submit button until all users radio buttons are checked.
submitBtn.disabled = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For accessibility, it would be preferable to throw an error if not all radio decisions have been resolved instead of disabling the submit button. Disabled buttons aren't discoverable by assistive technology, so it becomes difficult for a screen reader user to identify what's missing.

The error would need to notify the user with useful information, e.g. how many items still need to be completed, and ideally would also provide easy access to reach the incomplete fields. (Which can be a pain if you have three missing decisions but 100 users to sort through.)


// Listen for changes on any radio input in the form.
usersForm.addEventListener('change', function() {
if ( ! usersForm.checkValidity() ) {
submitBtn.disabled = true;
return;
}

// Check all radio groups for validity.
let allValid = true;
const radioGroups = usersForm.querySelectorAll( 'fieldset ul' );
radioGroups.forEach( function( radioGroup ) {
const radios = radioGroup.querySelectorAll( 'input[type="radio"]' );
let checkedRadio = null;
radios.forEach( function( radio ) {
if ( radio.checked ) {
checkedRadio = radio;
}
});

if ( checkedRadio && checkedRadio.value === 'reassign' ) {
const select = radioGroup.querySelector( 'select' );
if ( select && select.value === '-1' ) {
allValid = false;
}
}
});

submitBtn.disabled = !allValid;
});

usersForm.querySelectorAll( 'select' ).forEach( function( selectElement ) {
selectElement.addEventListener( 'change', function( e ) {
const radio = e.target.closest( 'li' ).querySelector( 'input[type="radio"]' );
radio.checked = e.target.value !== '-1';
});
});
})();
11 changes: 11 additions & 0 deletions src/wp-admin/includes/deprecated.php
Original file line number Diff line number Diff line change
Expand Up @@ -1589,3 +1589,14 @@ function image_attachment_fields_to_save( $post, $attachment ) {

return $post;
}

/**
* Was used to add JavaScript to the delete users form.
*
* @since 3.5.0
* @deprecated 6.9.0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @deprecated 6.9.0
* @deprecated 7.0.0

* @access private
*/
function delete_users_add_js() {
_deprecated_function( __FUNCTION__, '6.9.0' );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_deprecated_function( __FUNCTION__, '6.9.0' );
_deprecated_function( __FUNCTION__, '7.0.0' );

}
119 changes: 83 additions & 36 deletions src/wp-admin/includes/ms.php
Original file line number Diff line number Diff line change
Expand Up @@ -860,30 +860,31 @@ function _thickbox_path_admin_subfolder() {
* @return bool
*/
function confirm_delete_users( $users ) {
global $wpdb;

$current_user = wp_get_current_user();
if ( ! is_array( $users ) || empty( $users ) ) {
return false;
}

?>
<h1><?php esc_html_e( 'Users' ); ?></h1>
<h1><?php esc_html_e( 'Delete Users' ); ?></h1>

<?php if ( 1 === count( $users ) ) : ?>
<p><?php _e( 'You have chosen to delete the user from all networks and sites.' ); ?></p>
<?php else : ?>
<p><?php _e( 'You have chosen to delete the following users from all networks and sites.' ); ?></p>
<?php endif; ?>

<form action="users.php?action=dodelete" method="post">
<form action="users.php?action=dodelete" method="post" class="delete-and-reassign-users-form">
<input type="hidden" name="dodelete" />
<?php
wp_nonce_field( 'ms-users-delete' );
$site_admins = get_super_admins();
$admin_out = '<option value="' . esc_attr( $current_user->ID ) . '">' . $current_user->user_login . '</option>';
?>
<table class="form-table" role="presentation">
<?php
$allusers = (array) $_POST['allusers'];
foreach ( $allusers as $user_id ) {
foreach ( $users as $user_id ) {
if ( '' !== $user_id && '0' !== $user_id ) {
$delete_user = get_userdata( $user_id );

Expand All @@ -906,6 +907,7 @@ function confirm_delete_users( $users ) {
)
);
}

?>
<tr>
<th scope="row"><?php echo $delete_user->user_login; ?>
Expand All @@ -930,46 +932,91 @@ function confirm_delete_users( $users ) {
$blog_users = get_users(
array(
'blog_id' => $details->userblog_id,
'fields' => array( 'ID', 'user_login' ),
'fields' => array( 'ID' ),
'exclude' => $users,
)
);

$blog_users = wp_list_pluck( $blog_users, 'ID' );

if ( is_array( $blog_users ) && ! empty( $blog_users ) ) {
$user_site = "<a href='" . esc_url( get_home_url( $details->userblog_id ) ) . "'>{$details->blogname}</a>";
$user_dropdown = '<label for="reassign_user" class="screen-reader-text">' .
/* translators: Hidden accessibility text. */
__( 'Select a user' ) .
'</label>';
$user_dropdown .= "<select name='blog[$user_id][$key]' id='reassign_user'>";
$user_list = '';

foreach ( $blog_users as $user ) {
if ( ! in_array( (int) $user->ID, $allusers, true ) ) {
$user_list .= "<option value='{$user->ID}'>{$user->user_login}</option>";
$user_site = "<a href='" . esc_url( get_home_url( $details->userblog_id ) ) . "'>{$details->blogname}</a>";
switch_to_blog( $details->userblog_id );
/* This filter is documented in wp-admin/users.php */
$user_has_content = (bool) apply_filters( 'users_have_additional_content', false, array( $delete_user->ID ) );

if ( ! $user_has_content ) {
if ( $wpdb->get_var(
$wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_author = %d
LIMIT 1",
$delete_user->ID
)
) ) {
$user_has_content = true;
} elseif ( $wpdb->get_var(
$wpdb->prepare(
"SELECT link_id FROM {$wpdb->links}
WHERE link_owner = %d
LIMIT 1",
$delete_user->ID
)
) ) {
$user_has_content = true;
}
}

if ( '' === $user_list ) {
$user_list = $admin_out;
}

$user_dropdown .= $user_list;
$user_dropdown .= "</select>\n";
?>
<ul style="list-style:none;">
<li>
restore_current_blog();

if ( ! $user_has_content ) {
?>
<p>
<?php
/* translators: %s: Link to user's site. */
printf( __( 'Site: %s' ), $user_site );
?>
</p>
<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 />
<p><?php _e( 'This user does not have any content.' ); ?></p>
<?php
} else {
?>
<p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a fieldset with a legend so that screen reader users get the appropriate context for the associated radio inputs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fieldset is already present, but:

  1. There is no legend and
  2. The paragraph here does not include the user's username or any other differentiating information

Change the p to a legend and add the user name into the text, e.g. "{username}: What should be done with the content owned by this user?"

It's preferable to place the username first, as that minimizes the discovery and makes the list easier to scan.

<?php
/* translators: %s: Link to user's site. */
printf( __( 'Site: %s' ), $user_site );
?>
</p>
<ul>
<li>
<label>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using implicit label elements; they can cause problems for voice control users. Instead use an explicit for/id relationship and don't wrap the input inside the label.

<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 />
<?php _e( 'Delete all content.' ); ?>
</label>
</li>
<li>
<label>
<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 />
<?php _e( 'Attribute all content to:' ); ?>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is combining the radio for reassigning & the label for the dropdown into a single field. This is confusing, and leaves the dropdown unlabeled. Separate these into two explicitly separate fields with independent labels.

Avoid using implicit labels, and use an explicit for/id relationship, intead.

</label>

<?php
wp_dropdown_users(
array(
'show_option_none' => __( 'Select a user' ),
'name' => "blog[$user_id][$key]",
'include' => $blog_users,
'show' => 'display_name_with_login',
'id' => "reassign_user_{$details->userblog_id}_{$delete_user->ID}",
)
);
?>

</li>
</li>
<li><label><input type="radio" id="delete_option0" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="delete" checked="checked" />
<?php _e( 'Delete all content.' ); ?></label></li>
<li><label><input type="radio" id="delete_option1" name="delete[<?php echo $details->userblog_id . '][' . $delete_user->ID; ?>]" value="reassign" />
<?php _e( 'Attribute all content to:' ); ?></label>
<?php echo $user_dropdown; ?></li>
</ul>
<?php
</ul>
<?php
}
}
}
echo '</fieldset></td></tr>';
Expand All @@ -986,7 +1033,7 @@ function confirm_delete_users( $users ) {
</table>
<?php
/** This action is documented in wp-admin/users.php */
do_action( 'delete_user_form', $current_user, $allusers );
do_action( 'delete_user_form', $current_user, $users );

if ( 1 === count( $users ) ) :
?>
Expand All @@ -996,7 +1043,7 @@ function confirm_delete_users( $users ) {
<?php
endif;

submit_button( __( 'Confirm Deletion' ), 'primary' );
submit_button( __( 'Confirm Deletion' ), 'primary', 'submit', false, array( 'id' => 'confirm-users-deletion' ) );
?>
</form>
<?php
Expand Down
20 changes: 0 additions & 20 deletions src/wp-admin/includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,26 +561,6 @@ function default_password_nag() {
);
}

/**
* @since 3.5.0
* @access private
*/
function delete_users_add_js() {
?>
<script>
jQuery( function($) {
var submit = $('#submit').prop('disabled', true);
$('input[name="delete_option"]').one('change', function() {
submit.prop('disabled', false);
});
$('#reassign_user').focus( function() {
$('#delete_option1').prop('checked', true).trigger('change');
});
} );
</script>
<?php
}

/**
* Optional SSL preference that can be turned on by hooking to the 'personal_options' action.
*
Expand Down
5 changes: 3 additions & 2 deletions src/wp-admin/network/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@

$doaction = $_POST['action'];
$userfunction = '';
$allusers = (array) $_POST['allusers'];

foreach ( (array) $_POST['allusers'] as $user_id ) {
foreach ( $allusers as $user_id ) {
if ( ! empty( $user_id ) ) {
switch ( $doaction ) {
case 'delete':
Expand All @@ -72,7 +73,7 @@
require_once ABSPATH . 'wp-admin/admin-header.php';

echo '<div class="wrap">';
confirm_delete_users( $_POST['allusers'] );
confirm_delete_users( $allusers );
echo '</div>';

require_once ABSPATH . 'wp-admin/admin-footer.php';
Expand Down
Loading
Loading