[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   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( '&nbsp;' );
 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);


Generated : Wed Sep 2 08:20:30 2026 Cross-referenced by PHPXref