You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
14 KiB

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