[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> user-profile.js (source)

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


Generated : Fri Jul 24 08:20:19 2026 Cross-referenced by PHPXref