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


Generated : Thu Oct 30 08:20:06 2025 Cross-referenced by PHPXref