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