user-profile.js (13500B)
1 /** 2 * @output wp-admin/js/user-profile.js 3 */ 4 5 /* global ajaxurl, pwsL10n, userProfileL10n */ 6 (function($) { 7 var updateLock = false, 8 __ = wp.i18n.__, 9 $pass1Row, 10 $pass1, 11 $pass2, 12 $weakRow, 13 $weakCheckbox, 14 $toggleButton, 15 $submitButtons, 16 $submitButton, 17 currentPass, 18 $passwordWrapper; 19 20 function generatePassword() { 21 if ( typeof zxcvbn !== 'function' ) { 22 setTimeout( generatePassword, 50 ); 23 return; 24 } else if ( ! $pass1.val() || $passwordWrapper.hasClass( 'is-open' ) ) { 25 // zxcvbn loaded before user entered password, or generating new password. 26 $pass1.val( $pass1.data( 'pw' ) ); 27 $pass1.trigger( 'pwupdate' ); 28 showOrHideWeakPasswordCheckbox(); 29 } else { 30 // zxcvbn loaded after the user entered password, check strength. 31 check_pass_strength(); 32 showOrHideWeakPasswordCheckbox(); 33 } 34 35 // Install screen. 36 if ( 1 !== parseInt( $toggleButton.data( 'start-masked' ), 10 ) ) { 37 // Show the password not masked if admin_password hasn't been posted yet. 38 $pass1.attr( 'type', 'text' ); 39 } else { 40 // Otherwise, mask the password. 41 $toggleButton.trigger( 'click' ); 42 } 43 44 // Once zxcvbn loads, passwords strength is known. 45 $( '#pw-weak-text-label' ).text( __( 'Confirm use of weak password' ) ); 46 } 47 48 function bindPass1() { 49 currentPass = $pass1.val(); 50 51 if ( 1 === parseInt( $pass1.data( 'reveal' ), 10 ) ) { 52 generatePassword(); 53 } 54 55 $pass1.on( 'input' + ' pwupdate', function () { 56 if ( $pass1.val() === currentPass ) { 57 return; 58 } 59 60 currentPass = $pass1.val(); 61 62 // Refresh password strength area. 63 $pass1.removeClass( 'short bad good strong' ); 64 showOrHideWeakPasswordCheckbox(); 65 } ); 66 } 67 68 function resetToggle( show ) { 69 $toggleButton 70 .attr({ 71 'aria-label': show ? __( 'Show password' ) : __( 'Hide password' ) 72 }) 73 .find( '.text' ) 74 .text( show ? __( 'Show' ) : __( 'Hide' ) ) 75 .end() 76 .find( '.dashicons' ) 77 .removeClass( show ? 'dashicons-hidden' : 'dashicons-visibility' ) 78 .addClass( show ? 'dashicons-visibility' : 'dashicons-hidden' ); 79 } 80 81 function bindToggleButton() { 82 $toggleButton = $pass1Row.find('.wp-hide-pw'); 83 $toggleButton.show().on( 'click', function () { 84 if ( 'password' === $pass1.attr( 'type' ) ) { 85 $pass1.attr( 'type', 'text' ); 86 resetToggle( false ); 87 } else { 88 $pass1.attr( 'type', 'password' ); 89 resetToggle( true ); 90 } 91 }); 92 } 93 94 /** 95 * Handle the password reset button. Sets up an ajax callback to trigger sending 96 * a password reset email. 97 */ 98 function bindPasswordRestLink() { 99 $( '#generate-reset-link' ).on( 'click', function() { 100 var $this = $(this), 101 data = { 102 'user_id': userProfileL10n.user_id, // The user to send a reset to. 103 'nonce': userProfileL10n.nonce // Nonce to validate the action. 104 }; 105 106 // Remove any previous error messages. 107 $this.parent().find( '.notice-error' ).remove(); 108 109 // Send the reset request. 110 var resetAction = wp.ajax.post( 'send-password-reset', data ); 111 112 // Handle reset success. 113 resetAction.done( function( response ) { 114 addInlineNotice( $this, true, response ); 115 } ); 116 117 // Handle reset failure. 118 resetAction.fail( function( response ) { 119 addInlineNotice( $this, false, response ); 120 } ); 121 122 }); 123 124 } 125 126 /** 127 * Helper function to insert an inline notice of success or failure. 128 * 129 * @param {jQuery Object} $this The button element: the message will be inserted 130 * above this button 131 * @param {bool} success Whether the message is a success message. 132 * @param {string} message The message to insert. 133 */ 134 function addInlineNotice( $this, success, message ) { 135 var resultDiv = $( '<div />' ); 136 137 // Set up the notice div. 138 resultDiv.addClass( 'notice inline' ); 139 140 // Add a class indicating success or failure. 141 resultDiv.addClass( 'notice-' + ( success ? 'success' : 'error' ) ); 142 143 // Add the message, wrapping in a p tag, with a fadein to highlight each message. 144 resultDiv.text( $( $.parseHTML( message ) ).text() ).wrapInner( '<p />'); 145 146 // Disable the button when the callback has succeeded. 147 $this.prop( 'disabled', success ); 148 149 // Remove any previous notices. 150 $this.siblings( '.notice' ).remove(); 151 152 // Insert the notice. 153 $this.before( resultDiv ); 154 } 155 156 function bindPasswordForm() { 157 var $generateButton, 158 $cancelButton; 159 160 $pass1Row = $( '.user-pass1-wrap, .user-pass-wrap, .reset-pass-submit' ); 161 162 // Hide the confirm password field when JavaScript support is enabled. 163 $('.user-pass2-wrap').hide(); 164 165 $submitButton = $( '#submit, #wp-submit' ).on( 'click', function () { 166 updateLock = false; 167 }); 168 169 $submitButtons = $submitButton.add( ' #createusersub' ); 170 171 $weakRow = $( '.pw-weak' ); 172 $weakCheckbox = $weakRow.find( '.pw-checkbox' ); 173 $weakCheckbox.on( 'change', function() { 174 $submitButtons.prop( 'disabled', ! $weakCheckbox.prop( 'checked' ) ); 175 } ); 176 177 $pass1 = $('#pass1'); 178 if ( $pass1.length ) { 179 bindPass1(); 180 } else { 181 // Password field for the login form. 182 $pass1 = $( '#user_pass' ); 183 } 184 185 /* 186 * Fix a LastPass mismatch issue, LastPass only changes pass2. 187 * 188 * This fixes the issue by copying any changes from the hidden 189 * pass2 field to the pass1 field, then running check_pass_strength. 190 */ 191 $pass2 = $( '#pass2' ).on( 'input', function () { 192 if ( $pass2.val().length > 0 ) { 193 $pass1.val( $pass2.val() ); 194 $pass2.val(''); 195 currentPass = ''; 196 $pass1.trigger( 'pwupdate' ); 197 } 198 } ); 199 200 // Disable hidden inputs to prevent autofill and submission. 201 if ( $pass1.is( ':hidden' ) ) { 202 $pass1.prop( 'disabled', true ); 203 $pass2.prop( 'disabled', true ); 204 } 205 206 $passwordWrapper = $pass1Row.find( '.wp-pwd' ); 207 $generateButton = $pass1Row.find( 'button.wp-generate-pw' ); 208 209 bindToggleButton(); 210 211 $generateButton.show(); 212 $generateButton.on( 'click', function () { 213 updateLock = true; 214 215 // Make sure the password fields are shown. 216 $generateButton.attr( 'aria-expanded', 'true' ); 217 $passwordWrapper 218 .show() 219 .addClass( 'is-open' ); 220 221 // Enable the inputs when showing. 222 $pass1.attr( 'disabled', false ); 223 $pass2.attr( 'disabled', false ); 224 225 // Set the password to the generated value. 226 generatePassword(); 227 228 // Show generated password in plaintext by default. 229 resetToggle ( false ); 230 231 // Generate the next password and cache. 232 wp.ajax.post( 'generate-password' ) 233 .done( function( data ) { 234 $pass1.data( 'pw', data ); 235 } ); 236 } ); 237 238 $cancelButton = $pass1Row.find( 'button.wp-cancel-pw' ); 239 $cancelButton.on( 'click', function () { 240 updateLock = false; 241 242 // Disable the inputs when hiding to prevent autofill and submission. 243 $pass1.prop( 'disabled', true ); 244 $pass2.prop( 'disabled', true ); 245 246 // Clear password field and update the UI. 247 $pass1.val( '' ).trigger( 'pwupdate' ); 248 resetToggle( false ); 249 250 // Hide password controls. 251 $passwordWrapper 252 .hide() 253 .removeClass( 'is-open' ); 254 255 // Stop an empty password from being submitted as a change. 256 $submitButtons.prop( 'disabled', false ); 257 } ); 258 259 $pass1Row.closest( 'form' ).on( 'submit', function () { 260 updateLock = false; 261 262 $pass1.prop( 'disabled', false ); 263 $pass2.prop( 'disabled', false ); 264 $pass2.val( $pass1.val() ); 265 }); 266 } 267 268 function check_pass_strength() { 269 var pass1 = $('#pass1').val(), strength; 270 271 $('#pass-strength-result').removeClass('short bad good strong empty'); 272 if ( ! pass1 || '' === pass1.trim() ) { 273 $( '#pass-strength-result' ).addClass( 'empty' ).html( ' ' ); 274 return; 275 } 276 277 strength = wp.passwordStrength.meter( pass1, wp.passwordStrength.userInputDisallowedList(), pass1 ); 278 279 switch ( strength ) { 280 case -1: 281 $( '#pass-strength-result' ).addClass( 'bad' ).html( pwsL10n.unknown ); 282 break; 283 case 2: 284 $('#pass-strength-result').addClass('bad').html( pwsL10n.bad ); 285 break; 286 case 3: 287 $('#pass-strength-result').addClass('good').html( pwsL10n.good ); 288 break; 289 case 4: 290 $('#pass-strength-result').addClass('strong').html( pwsL10n.strong ); 291 break; 292 case 5: 293 $('#pass-strength-result').addClass('short').html( pwsL10n.mismatch ); 294 break; 295 default: 296 $('#pass-strength-result').addClass('short').html( pwsL10n['short'] ); 297 } 298 } 299 300 function showOrHideWeakPasswordCheckbox() { 301 var passStrength = $('#pass-strength-result')[0]; 302 303 if ( passStrength.className ) { 304 $pass1.addClass( passStrength.className ); 305 if ( $( passStrength ).is( '.short, .bad' ) ) { 306 if ( ! $weakCheckbox.prop( 'checked' ) ) { 307 $submitButtons.prop( 'disabled', true ); 308 } 309 $weakRow.show(); 310 } else { 311 if ( $( passStrength ).is( '.empty' ) ) { 312 $submitButtons.prop( 'disabled', true ); 313 $weakCheckbox.prop( 'checked', false ); 314 } else { 315 $submitButtons.prop( 'disabled', false ); 316 } 317 $weakRow.hide(); 318 } 319 } 320 } 321 322 $( function() { 323 var $colorpicker, $stylesheet, user_id, current_user_id, 324 select = $( '#display_name' ), 325 current_name = select.val(), 326 greeting = $( '#wp-admin-bar-my-account' ).find( '.display-name' ); 327 328 $( '#pass1' ).val( '' ).on( 'input' + ' pwupdate', check_pass_strength ); 329 $('#pass-strength-result').show(); 330 $('.color-palette').on( 'click', function() { 331 $(this).siblings('input[name="admin_color"]').prop('checked', true); 332 }); 333 334 if ( select.length ) { 335 $('#first_name, #last_name, #nickname').on( 'blur.user_profile', function() { 336 var dub = [], 337 inputs = { 338 display_nickname : $('#nickname').val() || '', 339 display_username : $('#user_login').val() || '', 340 display_firstname : $('#first_name').val() || '', 341 display_lastname : $('#last_name').val() || '' 342 }; 343 344 if ( inputs.display_firstname && inputs.display_lastname ) { 345 inputs.display_firstlast = inputs.display_firstname + ' ' + inputs.display_lastname; 346 inputs.display_lastfirst = inputs.display_lastname + ' ' + inputs.display_firstname; 347 } 348 349 $.each( $('option', select), function( i, el ){ 350 dub.push( el.value ); 351 }); 352 353 $.each(inputs, function( id, value ) { 354 if ( ! value ) { 355 return; 356 } 357 358 var val = value.replace(/<\/?[a-z][^>]*>/gi, ''); 359 360 if ( inputs[id].length && $.inArray( val, dub ) === -1 ) { 361 dub.push(val); 362 $('<option />', { 363 'text': val 364 }).appendTo( select ); 365 } 366 }); 367 }); 368 369 /** 370 * Replaces "Howdy, *" in the admin toolbar whenever the display name dropdown is updated for one's own profile. 371 */ 372 select.on( 'change', function() { 373 if ( user_id !== current_user_id ) { 374 return; 375 } 376 377 var display_name = this.value.trim() || current_name; 378 379 greeting.text( display_name ); 380 } ); 381 } 382 383 $colorpicker = $( '#color-picker' ); 384 $stylesheet = $( '#colors-css' ); 385 user_id = $( 'input#user_id' ).val(); 386 current_user_id = $( 'input[name="checkuser_id"]' ).val(); 387 388 $colorpicker.on( 'click.colorpicker', '.color-option', function() { 389 var colors, 390 $this = $(this); 391 392 if ( $this.hasClass( 'selected' ) ) { 393 return; 394 } 395 396 $this.siblings( '.selected' ).removeClass( 'selected' ); 397 $this.addClass( 'selected' ).find( 'input[type="radio"]' ).prop( 'checked', true ); 398 399 // Set color scheme. 400 if ( user_id === current_user_id ) { 401 // Load the colors stylesheet. 402 // The default color scheme won't have one, so we'll need to create an element. 403 if ( 0 === $stylesheet.length ) { 404 $stylesheet = $( '<link rel="stylesheet" />' ).appendTo( 'head' ); 405 } 406 $stylesheet.attr( 'href', $this.children( '.css_url' ).val() ); 407 408 // Repaint icons. 409 if ( typeof wp !== 'undefined' && wp.svgPainter ) { 410 try { 411 colors = JSON.parse( $this.children( '.icon_colors' ).val() ); 412 } catch ( error ) {} 413 414 if ( colors ) { 415 wp.svgPainter.setColors( colors ); 416 wp.svgPainter.paint(); 417 } 418 } 419 420 // Update user option. 421 $.post( ajaxurl, { 422 action: 'save-user-color-scheme', 423 color_scheme: $this.children( 'input[name="admin_color"]' ).val(), 424 nonce: $('#color-nonce').val() 425 }).done( function( response ) { 426 if ( response.success ) { 427 $( 'body' ).removeClass( response.data.previousScheme ).addClass( response.data.currentScheme ); 428 } 429 }); 430 } 431 }); 432 433 bindPasswordForm(); 434 bindPasswordRestLink(); 435 }); 436 437 $( '#destroy-sessions' ).on( 'click', function( e ) { 438 var $this = $(this); 439 440 wp.ajax.post( 'destroy-sessions', { 441 nonce: $( '#_wpnonce' ).val(), 442 user_id: $( '#user_id' ).val() 443 }).done( function( response ) { 444 $this.prop( 'disabled', true ); 445 $this.siblings( '.notice' ).remove(); 446 $this.before( '<div class="notice notice-success inline"><p>' + response.message + '</p></div>' ); 447 }).fail( function( response ) { 448 $this.siblings( '.notice' ).remove(); 449 $this.before( '<div class="notice notice-error inline"><p>' + response.message + '</p></div>' ); 450 }); 451 452 e.preventDefault(); 453 }); 454 455 window.generatePassword = generatePassword; 456 457 // Warn the user if password was generated but not saved. 458 $( window ).on( 'beforeunload', function () { 459 if ( true === updateLock ) { 460 return __( 'Your new password has not been saved.' ); 461 } 462 } ); 463 464 /* 465 * We need to generate a password as soon as the Reset Password page is loaded, 466 * to avoid double clicking the button to retrieve the first generated password. 467 * See ticket #39638. 468 */ 469 $( function() { 470 if ( $( '.reset-pass-submit' ).length ) { 471 $( '.reset-pass-submit button.wp-generate-pw' ).trigger( 'click' ); 472 } 473 }); 474 475 })(jQuery);