| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /* global ajaxurl, pwsL10n, userProfileL10n, ClipboardJS */ 2 3 /** 4 * @output wp-admin/js/user-profile.js 5 */ 6 7 /** 8 * Handles the user profile functionality. 9 * 10 * @param {JQueryStatic} $ The jQuery object. 11 */ 12 (function($) { 13 var updateLock = false, 14 isSubmitting = false, 15 __ = wp.i18n.__, 16 clipboard = new ClipboardJS( '.application-password-display .copy-button' ), 17 $pass1Row, 18 $pass1, 19 $pass2, 20 $weakRow, 21 $weakCheckbox, 22 $toggleButton, 23 $submitButtons, 24 $submitButton, 25 currentPass, 26 $form, 27 originalFormContent, 28 $passwordWrapper, 29 successTimeout, 30 isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false, 31 ua = navigator.userAgent.toLowerCase(), 32 isSafari = window.safari !== 'undefined' && typeof window.safari === 'object', 33 isFirefox = ua.indexOf( 'firefox' ) !== -1; 34 35 /** 36 * Generates a password and updates the password input field. 37 * 38 * If zxcvbn is not yet loaded, it will wait and try again. If the password 39 * input field is empty or the password wrapper is open, it will set the 40 * password to the generated value. If the user has already entered a 41 * password, it will check the strength of that password. It also binds 42 * the toggle button to show or hide the password and sets the initial state 43 * of the toggle button based on whether the password should be masked or 44 * not. Finally, it updates the label for confirming weak passwords. 45 */ 46 function generatePassword() { 47 if ( typeof zxcvbn !== 'function' ) { 48 setTimeout( generatePassword, 50 ); 49 return; 50 } else if ( ! $pass1.val() || $passwordWrapper.hasClass( 'is-open' ) ) { 51 // zxcvbn loaded before user entered password, or generating new password. 52 $pass1.val( $pass1.data( 'pw' ) ); 53 $pass1.trigger( 'pwupdate' ); 54 showOrHideWeakPasswordCheckbox(); 55 } else { 56 // zxcvbn loaded after the user entered password, check strength. 57 check_pass_strength(); 58 showOrHideWeakPasswordCheckbox(); 59 } 60 61 /* 62 * This works around a race condition when zxcvbn loads quickly and 63 * causes `generatePassword()` to run prior to the toggle button being 64 * bound. 65 */ 66 bindToggleButton(); 67 68 // Install screen. 69 if ( 1 !== parseInt( $toggleButton.data( 'start-masked' ), 10 ) ) { 70 // Show the password not masked if admin_password hasn't been posted yet. 71 $pass1.attr( 'type', 'text' ); 72 } else { 73 // Otherwise, mask the password. 74 $toggleButton.trigger( 'click' ); 75 } 76 77 // Once zxcvbn loads, passwords strength is known. 78 $( '#pw-weak-text-label' ).text( __( 'Confirm use of weak password' ) ); 79 } 80 81 /** 82 * Binds the password input field to update the current password and refresh the password strength area when the user types in the field. 83 */ 84 function bindPass1() { 85 currentPass = $pass1.val(); 86 87 if ( 1 === parseInt( $pass1.data( 'reveal' ), 10 ) ) { 88 generatePassword(); 89 } 90 91 $pass1.on( 'input' + ' pwupdate', function () { 92 if ( $pass1.val() === currentPass ) { 93 return; 94 } 95 96 currentPass = $pass1.val(); 97 98 // Refresh password strength area. 99 $pass1.removeClass( 'short bad good strong' ); 100 showOrHideWeakPasswordCheckbox(); 101 } ); 102 103 bindCapsLockWarning( $pass1 ); 104 } 105 106 /** 107 * Resets the toggle button to show or hide the password. 108 * 109 * @param {boolean} show Whether to show the password or not. 110 */ 111 function resetToggle( show ) { 112 $toggleButton 113 .attr({ 114 'aria-label': show ? __( 'Show password' ) : __( 'Hide password' ) 115 }) 116 .find( '.text' ) 117 .text( show ? __( 'Show' ) : __( 'Hide' ) ) 118 .end() 119 .find( '.dashicons' ) 120 .removeClass( show ? 'dashicons-hidden' : 'dashicons-visibility' ) 121 .addClass( show ? 'dashicons-visibility' : 'dashicons-hidden' ); 122 } 123 124 /** 125 * Binds the toggle button to show or hide the password. Also ensures that the password input type is set to password when the form is submitted. 126 */ 127 function bindToggleButton() { 128 if ( !! $toggleButton ) { 129 // Do not rebind. 130 return; 131 } 132 $toggleButton = $pass1Row.find('.wp-hide-pw'); 133 134 // Toggle between showing and hiding the password. 135 $toggleButton.show().on( 'click', function () { 136 if ( 'password' === $pass1.attr( 'type' ) ) { 137 $pass1.attr( 'type', 'text' ); 138 resetToggle( false ); 139 } else { 140 $pass1.attr( 'type', 'password' ); 141 resetToggle( true ); 142 } 143 }); 144 145 // Ensure the password input type is set to password when the form is submitted. 146 $pass1Row.closest( 'form' ).on( 'submit', function() { 147 if ( $pass1.attr( 'type' ) === 'text' ) { 148 $pass1.attr( 'type', 'password' ); 149 resetToggle( true ); 150 } 151 } ); 152 } 153 154 /** 155 * Handle the password reset button. Sets up an ajax callback to trigger sending 156 * a password reset email. 157 */ 158 function bindPasswordResetLink() { 159 $( '#generate-reset-link' ).on( 'click', function() { 160 var $this = $(this), 161 data = { 162 'user_id': userProfileL10n.user_id, // The user to send a reset to. 163 'nonce': userProfileL10n.nonce // Nonce to validate the action. 164 }; 165 166 // Remove any previous error messages. 167 $this.parent().find( '.notice-error' ).remove(); 168 169 // Send the reset request. 170 var resetAction = wp.ajax.post( 'send-password-reset', data ); 171 172 // Handle reset success. 173 resetAction.done( function( response ) { 174 addInlineNotice( $this, true, response ); 175 } ); 176 177 // Handle reset failure. 178 resetAction.fail( function( response ) { 179 addInlineNotice( $this, false, response ); 180 } ); 181 182 }); 183 184 } 185 186 /** 187 * Helper function to insert an inline notice of success or failure. 188 * 189 * @param {jQuery} $this The button element: the message will be inserted 190 * above this button 191 * @param {boolean} success Whether the message is a success message. 192 * @param {string} message The message to insert. 193 */ 194 function addInlineNotice( $this, success, message ) { 195 var resultDiv = $( '<div />', { 196 role: 'alert' 197 } ); 198 199 // Set up the notice div. 200 resultDiv.addClass( 'notice inline' ); 201 202 // Add a class indicating success or failure. 203 resultDiv.addClass( 'notice-' + ( success ? 'success' : 'error' ) ); 204 205 // Add the message, wrapping in a p tag, with a fadein to highlight each message. 206 resultDiv.text( $( $.parseHTML( message ) ).text() ).wrapInner( '<p />'); 207 208 // Disable the button when the callback has succeeded. 209 $this.prop( 'disabled', success ); 210 211 // Remove any previous notices. 212 $this.siblings( '.notice' ).remove(); 213 214 // Insert the notice. 215 $this.before( resultDiv ); 216 } 217 218 /** 219 * Initializes the password form, including the password strength meter, weak password checkbox, and show/hide password toggle button. 220 */ 221 function bindPasswordForm() { 222 var $generateButton, 223 $cancelButton; 224 225 $pass1Row = $( '.user-pass1-wrap, .user-pass-wrap, .mailserver-pass-wrap, .reset-pass-submit' ); 226 227 // Hide the confirm password field when JavaScript support is enabled. 228 $('.user-pass2-wrap').hide(); 229 230 $submitButton = $( '#submit, #wp-submit' ).on( 'click', function () { 231 updateLock = false; 232 }); 233 234 $submitButtons = $submitButton.add( ' #createusersub' ); 235 236 $weakRow = $( '.pw-weak' ); 237 $weakCheckbox = $weakRow.find( '.pw-checkbox' ); 238 $weakCheckbox.on( 'change', function() { 239 $submitButtons.prop( 'disabled', ! $weakCheckbox.prop( 'checked' ) ); 240 } ); 241 242 $pass1 = $('#pass1, #mailserver_pass'); 243 if ( $pass1.length ) { 244 bindPass1(); 245 } else { 246 // Password field for the login form. 247 $pass1 = $( '#user_pass' ); 248 249 bindCapsLockWarning( $pass1 ); 250 } 251 252 /* 253 * Fix a LastPass mismatch issue, LastPass only changes pass2. 254 * 255 * This fixes the issue by copying any changes from the hidden 256 * pass2 field to the pass1 field, then running check_pass_strength. 257 */ 258 $pass2 = $( '#pass2' ).on( 'input', function () { 259 if ( $pass2.val().length > 0 ) { 260 $pass1.val( $pass2.val() ); 261 $pass2.val(''); 262 currentPass = ''; 263 $pass1.trigger( 'pwupdate' ); 264 } 265 } ); 266 267 // Disable hidden inputs to prevent autofill and submission. 268 if ( $pass1.is( ':hidden' ) ) { 269 $pass1.prop( 'disabled', true ); 270 $pass2.prop( 'disabled', true ); 271 } 272 273 $passwordWrapper = $pass1Row.find( '.wp-pwd' ); 274 $generateButton = $pass1Row.find( 'button.wp-generate-pw' ); 275 276 bindToggleButton(); 277 278 $generateButton.show(); 279 $generateButton.on( 'click', function () { 280 updateLock = true; 281 282 // Make sure the password fields are shown. 283 $generateButton.not( '.skip-aria-expanded' ).attr( 'aria-expanded', 'true' ); 284 $passwordWrapper 285 .show() 286 .addClass( 'is-open' ); 287 288 // Enable the inputs when showing. 289 $pass1.attr( 'disabled', false ); 290 $pass2.attr( 'disabled', false ); 291 292 // Set the password to the generated value. 293 generatePassword(); 294 295 // Show generated password in plaintext by default. 296 resetToggle ( false ); 297 298 // Generate the next password and cache. 299 wp.ajax.post( 'generate-password' ) 300 .done( function( data ) { 301 $pass1.data( 'pw', data ); 302 } ); 303 } ); 304 305 $cancelButton = $pass1Row.find( 'button.wp-cancel-pw' ); 306 $cancelButton.on( 'click', function () { 307 updateLock = false; 308 309 // Disable the inputs when hiding to prevent autofill and submission. 310 $pass1.prop( 'disabled', true ); 311 $pass2.prop( 'disabled', true ); 312 313 // Clear password field and update the UI. 314 $pass1.val( '' ).trigger( 'pwupdate' ); 315 resetToggle( false ); 316 317 // Hide password controls. 318 $passwordWrapper 319 .hide() 320 .removeClass( 'is-open' ); 321 322 // Stop an empty password from being submitted as a change. 323 $submitButtons.prop( 'disabled', false ); 324 325 $generateButton.attr( 'aria-expanded', 'false' ); 326 } ); 327 328 $pass1Row.closest( 'form' ).on( 'submit', function () { 329 updateLock = false; 330 331 $pass1.prop( 'disabled', false ); 332 $pass2.prop( 'disabled', false ); 333 $pass2.val( $pass1.val() ); 334 }); 335 } 336 337 /** 338 * Sets CSS classes to the password strength results based on the password strength. 339 */ 340 function check_pass_strength() { 341 var pass1 = $('#pass1').val(), strength; 342 343 $('#pass-strength-result').removeClass('short bad good strong empty'); 344 if ( ! pass1 || '' === pass1.trim() ) { 345 $( '#pass-strength-result' ).addClass( 'empty' ).html( ' ' ); 346 return; 347 } 348 349 strength = wp.passwordStrength.meter( pass1, wp.passwordStrength.userInputDisallowedList(), pass1 ); 350 351 switch ( strength ) { 352 case -1: 353 $( '#pass-strength-result' ).addClass( 'bad' ).html( pwsL10n.unknown ); 354 break; 355 case 2: 356 $('#pass-strength-result').addClass('bad').html( pwsL10n.bad ); 357 break; 358 case 3: 359 $('#pass-strength-result').addClass('good').html( pwsL10n.good ); 360 break; 361 case 4: 362 $('#pass-strength-result').addClass('strong').html( pwsL10n.strong ); 363 break; 364 case 5: 365 $('#pass-strength-result').addClass('short').html( pwsL10n.mismatch ); 366 break; 367 default: 368 $('#pass-strength-result').addClass('short').html( pwsL10n.short ); 369 } 370 } 371 372 /** 373 * Bind Caps Lock detection to a password input field. 374 * 375 * @param {jQuery} $input The password input field. 376 */ 377 function bindCapsLockWarning( $input ) { 378 var $capsWarning, 379 $capsIcon, 380 $capsText, 381 capsLockOn = false; 382 383 // Skip warning on macOS Safari + Firefox (they show native indicators). 384 if ( isMac && ( isSafari || isFirefox ) ) { 385 return; 386 } 387 388 $capsWarning = $( '<div id="caps-warning" class="caps-warning"></div>' ); 389 $capsIcon = $( '<span class="caps-icon" aria-hidden="true"><svg viewBox="0 0 24 26" xmlns="http://www.w3.org/2000/svg" fill="#3c434a" stroke="#3c434a" stroke-width="0.5"><path d="M12 5L19 15H16V19H8V15H5L12 5Z"/><rect x="8" y="21" width="8" height="1.5" rx="0.75"/></svg></span>' ); 390 $capsText = $( '<span>', { 'class': 'caps-warning-text', text: __( 'Caps lock is on.' ) } ); 391 $capsWarning.append( $capsIcon, $capsText ); 392 393 $input.parent( 'div' ).append( $capsWarning ); 394 395 $input.on( 'keydown', function( jqEvent ) { 396 var event = jqEvent.originalEvent; 397 398 // Skip if key is not a printable character. 399 // Key length > 1 usually means non-printable (e.g., "Enter", "Tab"). 400 if ( event.ctrlKey || event.metaKey || event.altKey || ! event.key || event.key.length !== 1 ) { 401 return; 402 } 403 404 var state = isCapsLockOn( event ); 405 406 // React when the state changes or if caps lock is on when the user starts typing. 407 if ( state !== capsLockOn ) { 408 capsLockOn = state; 409 410 if ( capsLockOn ) { 411 $capsWarning.show(); 412 // Don't duplicate existing screen reader Caps lock notifications. 413 if ( event.key !== 'CapsLock' ) { 414 wp.a11y.speak( __( 'Caps lock is on.' ), 'assertive' ); 415 } 416 } else { 417 $capsWarning.hide(); 418 } 419 } 420 } ); 421 422 $input.on( 'blur', function() { 423 if ( ! document.hasFocus() ) { 424 return; 425 } 426 capsLockOn = false; 427 $capsWarning.hide(); 428 } ); 429 } 430 431 /** 432 * Determines if Caps Lock is currently enabled. 433 * 434 * On macOS Safari and Firefox, the native warning is preferred, 435 * so this function returns false to suppress custom warnings. 436 * 437 * @param {KeyboardEvent} event The keydown event object. 438 * 439 * @return {boolean} True if Caps Lock is on, false otherwise. 440 */ 441 function isCapsLockOn( event ) { 442 return event.getModifierState( 'CapsLock' ); 443 } 444 445 /** 446 * Toggles the visibility of the weak password checkbox. 447 */ 448 function showOrHideWeakPasswordCheckbox() { 449 var passStrengthResult = $('#pass-strength-result'); 450 451 if ( passStrengthResult.length ) { 452 var passStrength = passStrengthResult[0]; 453 454 if ( passStrength.className ) { 455 $pass1.addClass( passStrength.className ); 456 if ( $( passStrength ).is( '.short, .bad' ) ) { 457 if ( ! $weakCheckbox.prop( 'checked' ) ) { 458 $submitButtons.prop( 'disabled', true ); 459 } 460 $weakRow.show(); 461 } else { 462 if ( $( passStrength ).is( '.empty' ) ) { 463 $submitButtons.prop( 'disabled', true ); 464 $weakCheckbox.prop( 'checked', false ); 465 } else { 466 $submitButtons.prop( 'disabled', false ); 467 } 468 $weakRow.hide(); 469 } 470 } 471 } 472 } 473 474 // Debug information copy section. 475 clipboard.on( 'success', function( e ) { 476 var triggerElement = $( e.trigger ), 477 successElement = $( '.success', triggerElement.closest( '.application-password-display' ) ); 478 479 // Clear the selection and move focus back to the trigger. 480 e.clearSelection(); 481 482 // Show success visual feedback. 483 clearTimeout( successTimeout ); 484 successElement.removeClass( 'hidden' ); 485 486 // Hide success visual feedback after 3 seconds since last success. 487 successTimeout = setTimeout( function() { 488 successElement.addClass( 'hidden' ); 489 }, 3000 ); 490 491 // Handle success audible feedback. 492 wp.a11y.speak( __( 'Application password has been copied to your clipboard.' ) ); 493 } ); 494 495 $( function() { 496 var $colorpicker, $stylesheet, $selectedMenuIconStyles, initialMenuIconStyles, user_id, current_user_id, 497 select = $( '#display_name' ), 498 username = $( '#user_login' ).val(), 499 myAccount = $( '#wp-admin-bar-my-account' ), 500 displayNames = myAccount.find( '.user-display-name' ), 501 usernameLine = myAccount.find( '#wp-admin-bar-user-info .username' ), 502 menus = myAccount.children( '.ab-sub-wrapper' ).children( '[role="menu"]' ).filter( function() { 503 return this.getAttribute( 'aria-label' ) === displayNames.first().text(); 504 } ); 505 506 $( '#pass1' ).val( '' ).on( 'input' + ' pwupdate', check_pass_strength ); 507 $('#pass-strength-result').show(); 508 $('.color-palette').on( 'click', function() { 509 $(this).siblings('input[name="admin_color"]').prop('checked', true); 510 }); 511 512 if ( select.length ) { 513 $('#first_name, #last_name, #nickname').on( 'blur.user_profile', function() { 514 var dub = [], 515 inputs = { 516 display_nickname : $('#nickname').val() || '', 517 display_username : $('#user_login').val() || '', 518 display_firstname : $('#first_name').val() || '', 519 display_lastname : $('#last_name').val() || '' 520 }; 521 522 if ( inputs.display_firstname && inputs.display_lastname ) { 523 inputs.display_firstlast = inputs.display_firstname + ' ' + inputs.display_lastname; 524 inputs.display_lastfirst = inputs.display_lastname + ' ' + inputs.display_firstname; 525 } 526 527 $.each( $('option', select), function( i, el ){ 528 dub.push( el.value ); 529 }); 530 531 $.each(inputs, function( id, value ) { 532 if ( ! value ) { 533 return; 534 } 535 536 var val = value.replace(/<\/?[a-z][^>]*>/gi, ''); 537 538 if ( inputs[id].length && $.inArray( val, dub ) === -1 ) { 539 dub.push(val); 540 $('<option />', { 541 'text': val 542 }).appendTo( select ); 543 } 544 }); 545 }); 546 547 /** 548 * Updates the display name in the admin toolbar whenever the display name dropdown is updated for one's own profile. 549 */ 550 select.on( 'change', function() { 551 if ( user_id !== current_user_id ) { 552 return; 553 } 554 555 var display_name = this.value.trim() || username; 556 557 displayNames.text( display_name ); 558 menus.attr( 'aria-label', display_name ); 559 560 // Only show the username when it differs from the display name. 561 usernameLine.prop( 'hidden', display_name === username ); 562 } ); 563 } 564 565 $colorpicker = $( '#color-picker' ); 566 $stylesheet = $( '#colors-css' ); 567 $selectedMenuIconStyles = $(); 568 initialMenuIconStyles = $colorpicker.find( '.selected .menu_icon_styles' ).val(); 569 user_id = $( 'input#user_id' ).val(); 570 current_user_id = $( 'input[name="checkuser_id"]' ).val(); 571 572 $colorpicker.on( 'click.colorpicker', '.color-option', function() { 573 var colors, 574 $this = $(this); 575 576 if ( $this.hasClass( 'selected' ) ) { 577 return; 578 } 579 580 $this.siblings( '.selected' ).removeClass( 'selected' ); 581 $this.addClass( 'selected' ).find( 'input[type="radio"]' ).prop( 'checked', true ); 582 583 // Set color scheme. 584 if ( user_id === current_user_id ) { 585 // Load the colors stylesheet. 586 // The default color scheme won't have one, so we'll need to create an element. 587 if ( 0 === $stylesheet.length ) { 588 $stylesheet = $( '<link rel="stylesheet" />' ).appendTo( 'head' ); 589 } 590 $stylesheet.attr( 'href', $this.children( '.css_url' ).val() ); 591 592 // Repaint icons. 593 if ( typeof wp !== 'undefined' && wp.svgPainter ) { 594 try { 595 colors = JSON.parse( $this.children( '.icon_colors' ).val() ); 596 } catch ( error ) {} 597 598 if ( colors ) { 599 wp.svgPainter.setColors( colors ); 600 wp.svgPainter.paint(); 601 } 602 } 603 604 // Color the inline SVG menu icons. 605 if ( 0 === $selectedMenuIconStyles.length ) { 606 // Remove the color scheme styles that were already printed. 607 if ( initialMenuIconStyles ) { 608 $( 'style' ).each( function() { 609 this.textContent = this.textContent.replace( initialMenuIconStyles, '' ); 610 } ); 611 } 612 // Insert before the colors stylesheet, so that the stylesheet can override these styles. 613 $selectedMenuIconStyles = $( '<style />' ).insertBefore( $stylesheet ); 614 } 615 $selectedMenuIconStyles.text( $this.children( '.menu_icon_styles' ).val() || '' ); 616 617 // Update user option. 618 $.post( ajaxurl, { 619 action: 'save-user-color-scheme', 620 color_scheme: $this.children( 'input[name="admin_color"]' ).val(), 621 nonce: $('#color-nonce').val() 622 }).done( function( response ) { 623 if ( response.success ) { 624 $( 'body' ).removeClass( response.data.previousScheme ).addClass( response.data.currentScheme ); 625 } 626 }); 627 } 628 }); 629 630 bindPasswordForm(); 631 bindPasswordResetLink(); 632 $submitButtons.on( 'click', function() { 633 isSubmitting = true; 634 }); 635 636 $form = $( '#your-profile, #createuser' ); 637 originalFormContent = $form.serialize(); 638 }); 639 640 $( '#destroy-sessions' ).on( 'click', function( e ) { 641 var $this = $(this); 642 643 wp.ajax.post( 'destroy-sessions', { 644 nonce: $( '#_wpnonce' ).val(), 645 user_id: $( '#user_id' ).val() 646 }).done( function( response ) { 647 $this.prop( 'disabled', true ); 648 $this.siblings( '.notice' ).remove(); 649 $this.before( '<div class="notice notice-success inline" role="alert"><p>' + response.message + '</p></div>' ); 650 }).fail( function( response ) { 651 $this.siblings( '.notice' ).remove(); 652 $this.before( '<div class="notice notice-error inline" role="alert"><p>' + response.message + '</p></div>' ); 653 }); 654 655 e.preventDefault(); 656 }); 657 658 window.generatePassword = generatePassword; 659 660 // Warn the user if password was generated but not saved. 661 $( window ).on( 'beforeunload', function () { 662 if ( true === updateLock ) { 663 return __( 'Your new password has not been saved.' ); 664 } 665 if ( originalFormContent !== $form.serialize() && ! isSubmitting ) { 666 return __( 'The changes you made will be lost if you navigate away from this page.' ); 667 } 668 }); 669 670 /* 671 * We need to generate a password as soon as the Reset Password page is loaded, 672 * to avoid double clicking the button to retrieve the first generated password. 673 * See ticket #39638. 674 */ 675 $( function() { 676 if ( $( '.reset-pass-submit' ).length ) { 677 $( '.reset-pass-submit button.wp-generate-pw' ).trigger( 'click' ); 678 } 679 }); 680 681 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Nov 1 08:20:35 2026 | Cross-referenced by PHPXref |