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