user-profile.js 14 KB

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