privacy-tools.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /**
  2. * Interactions used by the User Privacy tools in WordPress.
  3. *
  4. * @output wp-admin/js/privacy-tools.js
  5. */
  6. // Privacy request action handling.
  7. jQuery( function( $ ) {
  8. var __ = wp.i18n.__,
  9. copiedNoticeTimeout;
  10. function setActionState( $action, state ) {
  11. $action.children().addClass( 'hidden' );
  12. $action.children( '.' + state ).removeClass( 'hidden' );
  13. }
  14. function clearResultsAfterRow( $requestRow ) {
  15. $requestRow.removeClass( 'has-request-results' );
  16. if ( $requestRow.next().hasClass( 'request-results' ) ) {
  17. $requestRow.next().remove();
  18. }
  19. }
  20. function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
  21. var itemList = '',
  22. resultRowClasses = 'request-results';
  23. clearResultsAfterRow( $requestRow );
  24. if ( additionalMessages.length ) {
  25. $.each( additionalMessages, function( index, value ) {
  26. itemList = itemList + '<li>' + value + '</li>';
  27. });
  28. itemList = '<ul>' + itemList + '</ul>';
  29. }
  30. $requestRow.addClass( 'has-request-results' );
  31. if ( $requestRow.hasClass( 'status-request-confirmed' ) ) {
  32. resultRowClasses = resultRowClasses + ' status-request-confirmed';
  33. }
  34. if ( $requestRow.hasClass( 'status-request-failed' ) ) {
  35. resultRowClasses = resultRowClasses + ' status-request-failed';
  36. }
  37. $requestRow.after( function() {
  38. return '<tr class="' + resultRowClasses + '"><th colspan="5">' +
  39. '<div class="notice inline notice-alt ' + classes + '">' +
  40. '<p>' + summaryMessage + '</p>' +
  41. itemList +
  42. '</div>' +
  43. '</td>' +
  44. '</tr>';
  45. });
  46. }
  47. $( '.export-personal-data-handle' ).on( 'click', function( event ) {
  48. var $this = $( this ),
  49. $action = $this.parents( '.export-personal-data' ),
  50. $requestRow = $this.parents( 'tr' ),
  51. $progress = $requestRow.find( '.export-progress' ),
  52. $rowActions = $this.parents( '.row-actions' ),
  53. requestID = $action.data( 'request-id' ),
  54. nonce = $action.data( 'nonce' ),
  55. exportersCount = $action.data( 'exporters-count' ),
  56. sendAsEmail = $action.data( 'send-as-email' ) ? true : false;
  57. event.preventDefault();
  58. event.stopPropagation();
  59. $rowActions.addClass( 'processing' );
  60. $action.trigger( 'blur' );
  61. clearResultsAfterRow( $requestRow );
  62. setExportProgress( 0 );
  63. function onExportDoneSuccess( zipUrl ) {
  64. var summaryMessage = __( 'This user&#8217;s personal data export link was sent.' );
  65. if ( 'undefined' !== typeof zipUrl ) {
  66. summaryMessage = __( 'This user&#8217;s personal data export file was downloaded.' );
  67. }
  68. setActionState( $action, 'export-personal-data-success' );
  69. appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, [] );
  70. if ( 'undefined' !== typeof zipUrl ) {
  71. window.location = zipUrl;
  72. } else if ( ! sendAsEmail ) {
  73. onExportFailure( __( 'No personal data export file was generated.' ) );
  74. }
  75. setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 );
  76. }
  77. function onExportFailure( errorMessage ) {
  78. var summaryMessage = __( 'An error occurred while attempting to export personal data.' );
  79. setActionState( $action, 'export-personal-data-failed' );
  80. if ( errorMessage ) {
  81. appendResultsAfterRow( $requestRow, 'notice-error', summaryMessage, [ errorMessage ] );
  82. }
  83. setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 );
  84. }
  85. function setExportProgress( exporterIndex ) {
  86. var progress = ( exportersCount > 0 ? exporterIndex / exportersCount : 0 ),
  87. progressString = Math.round( progress * 100 ).toString() + '%';
  88. $progress.html( progressString );
  89. }
  90. function doNextExport( exporterIndex, pageIndex ) {
  91. $.ajax(
  92. {
  93. url: window.ajaxurl,
  94. data: {
  95. action: 'wp-privacy-export-personal-data',
  96. exporter: exporterIndex,
  97. id: requestID,
  98. page: pageIndex,
  99. security: nonce,
  100. sendAsEmail: sendAsEmail
  101. },
  102. method: 'post'
  103. }
  104. ).done( function( response ) {
  105. var responseData = response.data;
  106. if ( ! response.success ) {
  107. // e.g. invalid request ID.
  108. setTimeout( function() { onExportFailure( response.data ); }, 500 );
  109. return;
  110. }
  111. if ( ! responseData.done ) {
  112. setTimeout( doNextExport( exporterIndex, pageIndex + 1 ) );
  113. } else {
  114. setExportProgress( exporterIndex );
  115. if ( exporterIndex < exportersCount ) {
  116. setTimeout( doNextExport( exporterIndex + 1, 1 ) );
  117. } else {
  118. setTimeout( function() { onExportDoneSuccess( responseData.url ); }, 500 );
  119. }
  120. }
  121. }).fail( function( jqxhr, textStatus, error ) {
  122. // e.g. Nonce failure.
  123. setTimeout( function() { onExportFailure( error ); }, 500 );
  124. });
  125. }
  126. // And now, let's begin.
  127. setActionState( $action, 'export-personal-data-processing' );
  128. doNextExport( 1, 1 );
  129. });
  130. $( '.remove-personal-data-handle' ).on( 'click', function( event ) {
  131. var $this = $( this ),
  132. $action = $this.parents( '.remove-personal-data' ),
  133. $requestRow = $this.parents( 'tr' ),
  134. $progress = $requestRow.find( '.erasure-progress' ),
  135. $rowActions = $this.parents( '.row-actions' ),
  136. requestID = $action.data( 'request-id' ),
  137. nonce = $action.data( 'nonce' ),
  138. erasersCount = $action.data( 'erasers-count' ),
  139. hasRemoved = false,
  140. hasRetained = false,
  141. messages = [];
  142. event.preventDefault();
  143. event.stopPropagation();
  144. $rowActions.addClass( 'processing' );
  145. $action.trigger( 'blur' );
  146. clearResultsAfterRow( $requestRow );
  147. setErasureProgress( 0 );
  148. function onErasureDoneSuccess() {
  149. var summaryMessage = __( 'No personal data was found for this user.' ),
  150. classes = 'notice-success';
  151. setActionState( $action, 'remove-personal-data-success' );
  152. if ( false === hasRemoved ) {
  153. if ( false === hasRetained ) {
  154. summaryMessage = __( 'No personal data was found for this user.' );
  155. } else {
  156. summaryMessage = __( 'Personal data was found for this user but was not erased.' );
  157. classes = 'notice-warning';
  158. }
  159. } else {
  160. if ( false === hasRetained ) {
  161. summaryMessage = __( 'All of the personal data found for this user was erased.' );
  162. } else {
  163. summaryMessage = __( 'Personal data was found for this user but some of the personal data found was not erased.' );
  164. classes = 'notice-warning';
  165. }
  166. }
  167. appendResultsAfterRow( $requestRow, classes, summaryMessage, messages );
  168. setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 );
  169. }
  170. function onErasureFailure() {
  171. var summaryMessage = __( 'An error occurred while attempting to find and erase personal data.' );
  172. setActionState( $action, 'remove-personal-data-failed' );
  173. appendResultsAfterRow( $requestRow, 'notice-error', summaryMessage, [] );
  174. setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 );
  175. }
  176. function setErasureProgress( eraserIndex ) {
  177. var progress = ( erasersCount > 0 ? eraserIndex / erasersCount : 0 ),
  178. progressString = Math.round( progress * 100 ).toString() + '%';
  179. $progress.html( progressString );
  180. }
  181. function doNextErasure( eraserIndex, pageIndex ) {
  182. $.ajax({
  183. url: window.ajaxurl,
  184. data: {
  185. action: 'wp-privacy-erase-personal-data',
  186. eraser: eraserIndex,
  187. id: requestID,
  188. page: pageIndex,
  189. security: nonce
  190. },
  191. method: 'post'
  192. }).done( function( response ) {
  193. var responseData = response.data;
  194. if ( ! response.success ) {
  195. setTimeout( function() { onErasureFailure(); }, 500 );
  196. return;
  197. }
  198. if ( responseData.items_removed ) {
  199. hasRemoved = hasRemoved || responseData.items_removed;
  200. }
  201. if ( responseData.items_retained ) {
  202. hasRetained = hasRetained || responseData.items_retained;
  203. }
  204. if ( responseData.messages ) {
  205. messages = messages.concat( responseData.messages );
  206. }
  207. if ( ! responseData.done ) {
  208. setTimeout( doNextErasure( eraserIndex, pageIndex + 1 ) );
  209. } else {
  210. setErasureProgress( eraserIndex );
  211. if ( eraserIndex < erasersCount ) {
  212. setTimeout( doNextErasure( eraserIndex + 1, 1 ) );
  213. } else {
  214. setTimeout( function() { onErasureDoneSuccess(); }, 500 );
  215. }
  216. }
  217. }).fail( function() {
  218. setTimeout( function() { onErasureFailure(); }, 500 );
  219. });
  220. }
  221. // And now, let's begin.
  222. setActionState( $action, 'remove-personal-data-processing' );
  223. doNextErasure( 1, 1 );
  224. });
  225. // Privacy Policy page, copy action.
  226. $( document ).on( 'click', function( event ) {
  227. var $parent,
  228. range,
  229. $target = $( event.target ),
  230. copiedNotice = $target.siblings( '.success' );
  231. clearTimeout( copiedNoticeTimeout );
  232. if ( $target.is( 'button.privacy-text-copy' ) ) {
  233. $parent = $target.closest( '.privacy-settings-accordion-panel' );
  234. if ( $parent.length ) {
  235. try {
  236. var documentPosition = document.documentElement.scrollTop,
  237. bodyPosition = document.body.scrollTop;
  238. // Setup copy.
  239. window.getSelection().removeAllRanges();
  240. // Hide tutorial content to remove from copied content.
  241. range = document.createRange();
  242. $parent.addClass( 'hide-privacy-policy-tutorial' );
  243. // Copy action.
  244. range.selectNodeContents( $parent[0] );
  245. window.getSelection().addRange( range );
  246. document.execCommand( 'copy' );
  247. // Reset section.
  248. $parent.removeClass( 'hide-privacy-policy-tutorial' );
  249. window.getSelection().removeAllRanges();
  250. // Return scroll position - see #49540.
  251. if ( documentPosition > 0 && documentPosition !== document.documentElement.scrollTop ) {
  252. document.documentElement.scrollTop = documentPosition;
  253. } else if ( bodyPosition > 0 && bodyPosition !== document.body.scrollTop ) {
  254. document.body.scrollTop = bodyPosition;
  255. }
  256. // Display and speak notice to indicate action complete.
  257. copiedNotice.addClass( 'visible' );
  258. wp.a11y.speak( __( 'The suggested policy text has been copied to your clipboard.' ) );
  259. // Delay notice dismissal.
  260. copiedNoticeTimeout = setTimeout( function() {
  261. copiedNotice.removeClass( 'visible' );
  262. }, 3000 );
  263. } catch ( er ) {}
  264. }
  265. }
  266. });
  267. // Label handling to focus the create page button on Privacy settings page.
  268. $( 'body.options-privacy-php label[for=create-page]' ).on( 'click', function( e ) {
  269. e.preventDefault();
  270. $( 'input#create-page' ).trigger( 'focus' );
  271. } );
  272. // Accordion handling in various new Privacy settings pages.
  273. $( '.privacy-settings-accordion' ).on( 'click', '.privacy-settings-accordion-trigger', function() {
  274. var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
  275. if ( isExpanded ) {
  276. $( this ).attr( 'aria-expanded', 'false' );
  277. $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
  278. } else {
  279. $( this ).attr( 'aria-expanded', 'true' );
  280. $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
  281. }
  282. } );
  283. });