| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-admin/js/privacy-tools.js 3 */ 4 5 /** 6 * Sets the interactions used by the User Privacy tools in WordPress. 7 * 8 * @param {jQueryStatic} $ The jQuery object. 9 */ 10 jQuery( function( $ ) { 11 var __ = wp.i18n.__, 12 copiedNoticeTimeout; 13 14 /** 15 * Sets the state of the action. 16 * 17 * @since 4.9.6 18 * 19 * @param {jQuery} $action The action to set the state for. 20 * @param {string} state The state to set the action to. 21 * @return {void} 22 */ 23 function setActionState( $action, state ) { 24 $action.children().addClass( 'hidden' ); 25 $action.children( '.' + state ).removeClass( 'hidden' ); 26 } 27 28 /** 29 * Clears any results row after the request row. 30 * 31 * @since 4.9.6 32 * 33 * @param {jQuery} $requestRow The request row to clear results for. 34 * @return {void} 35 */ 36 function clearResultsAfterRow( $requestRow ) { 37 if ( $requestRow.next().hasClass( 'request-results' ) ) { 38 $requestRow.next().remove(); 39 } 40 } 41 42 /** 43 * Appends a results row after the request row. 44 * 45 * @since 4.9.6 46 * 47 * @param {jQuery} $requestRow The request row to append the results after. 48 * @param {string} classes The classes to add to the results row. 49 * @param {string} summaryMessage The summary message to display in the results row. 50 * @param {string[]} additionalMessages Additional messages to display in the results row. 51 * @return {void} 52 */ 53 function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) { 54 var itemList = '', 55 resultRowClasses = 'request-results'; 56 57 clearResultsAfterRow( $requestRow ); 58 59 if ( additionalMessages.length ) { 60 $.each( additionalMessages, function( index, value ) { 61 itemList = itemList + '<li>' + value + '</li>'; 62 }); 63 itemList = '<ul>' + itemList + '</ul>'; 64 } 65 66 if ( $requestRow.hasClass( 'status-request-confirmed' ) ) { 67 resultRowClasses = resultRowClasses + ' status-request-confirmed'; 68 } 69 70 if ( $requestRow.hasClass( 'status-request-failed' ) ) { 71 resultRowClasses = resultRowClasses + ' status-request-failed'; 72 } 73 74 $requestRow.after( function() { 75 return '<tr class="' + resultRowClasses + '"><th colspan="5">' + 76 '<div class="notice inline notice-alt ' + classes + '" role="alert">' + 77 '<p>' + summaryMessage + '</p>' + 78 itemList + 79 '</div>' + 80 '</th>' + 81 '</tr>'; 82 }); 83 } 84 85 $( '.export-personal-data-handle' ).on( 'click', function( event ) { 86 var $this = $( this ), 87 $action = $this.parents( '.export-personal-data' ), 88 $requestRow = $this.parents( 'tr' ), 89 $progress = $requestRow.find( '.export-progress' ), 90 $rowActions = $this.parents( '.row-actions' ), 91 requestID = $action.data( 'request-id' ), 92 nonce = $action.data( 'nonce' ), 93 exportersCount = $action.data( 'exporters-count' ), 94 sendAsEmail = $action.data( 'send-as-email' ) ? true : false; 95 96 event.preventDefault(); 97 event.stopPropagation(); 98 99 $rowActions.addClass( 'processing' ); 100 101 $action.trigger( 'blur' ); 102 clearResultsAfterRow( $requestRow ); 103 setExportProgress( 0 ); 104 105 /** 106 * Handles a successful export. 107 * 108 * @since 4.9.6 109 * 110 * @param {string} [zipUrl] Optional. The URL of the generated ZIP file. Undefined when 111 * the export link is sent by email instead. 112 * @return {void} 113 */ 114 function onExportDoneSuccess( zipUrl ) { 115 var summaryMessage = __( 'This user’s personal data export link was sent.' ); 116 117 if ( 'undefined' !== typeof zipUrl ) { 118 summaryMessage = __( 'This user’s personal data export file was downloaded.' ); 119 } 120 121 setActionState( $action, 'export-personal-data-success' ); 122 123 appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, [] ); 124 125 if ( 'undefined' !== typeof zipUrl ) { 126 window.location = zipUrl; 127 } else if ( ! sendAsEmail ) { 128 onExportFailure( __( 'No personal data export file was generated.' ) ); 129 } 130 131 setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 ); 132 } 133 134 /** 135 * Handles an export failure. 136 * 137 * @since 4.9.6 138 * 139 * @param {string} [errorMessage] Optional. The error message to display. The results 140 * row is only added when a message is passed. 141 * @return {void} 142 */ 143 function onExportFailure( errorMessage ) { 144 var summaryMessage = __( 'An error occurred while attempting to export personal data.' ); 145 146 setActionState( $action, 'export-personal-data-failed' ); 147 148 if ( errorMessage ) { 149 appendResultsAfterRow( $requestRow, 'notice-error', summaryMessage, [ errorMessage ] ); 150 } 151 152 setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 ); 153 } 154 155 /** 156 * Updates the export progress indicator based on the number of completed exporters. 157 * 158 * @since 5.4.0 159 * 160 * @param {number} exporterIndex The number of exporters that have finished. Should be 161 * between 0 and exportersCount. 162 * @return {void} 163 */ 164 function setExportProgress( exporterIndex ) { 165 var progress = ( exportersCount > 0 ? exporterIndex / exportersCount : 0 ), 166 progressString = Math.round( progress * 100 ).toString() + '%'; 167 168 $progress.html( progressString ); 169 } 170 171 /** 172 * Performs the next export request. 173 * 174 * @since 4.9.6 175 * 176 * @param {number} exporterIndex The 1-based index of the exporter to process, between 1 177 * and exportersCount. 178 * @param {number} pageIndex The 1-based index of the page to process for the current exporter. 179 * @return {void} 180 */ 181 function doNextExport( exporterIndex, pageIndex ) { 182 $.ajax( 183 { 184 url: window.ajaxurl, 185 data: { 186 action: 'wp-privacy-export-personal-data', 187 exporter: exporterIndex, 188 id: requestID, 189 page: pageIndex, 190 security: nonce, 191 sendAsEmail: sendAsEmail 192 }, 193 method: 'post' 194 } 195 ).done( function( response ) { 196 var responseData = response.data; 197 198 if ( ! response.success ) { 199 // e.g. invalid request ID. 200 setTimeout( function() { onExportFailure( response.data ); }, 500 ); 201 return; 202 } 203 204 if ( ! responseData.done ) { 205 setTimeout( doNextExport( exporterIndex, pageIndex + 1 ) ); 206 } else { 207 setExportProgress( exporterIndex ); 208 if ( exporterIndex < exportersCount ) { 209 setTimeout( doNextExport( exporterIndex + 1, 1 ) ); 210 } else { 211 setTimeout( function() { onExportDoneSuccess( responseData.url ); }, 500 ); 212 } 213 } 214 }).fail( function( jqxhr, textStatus, error ) { 215 // e.g. Nonce failure. 216 setTimeout( function() { onExportFailure( error ); }, 500 ); 217 }); 218 } 219 220 // And now, let's begin. 221 setActionState( $action, 'export-personal-data-processing' ); 222 doNextExport( 1, 1 ); 223 }); 224 225 $( '.remove-personal-data-handle' ).on( 'click', function( event ) { 226 var $this = $( this ), 227 $action = $this.parents( '.remove-personal-data' ), 228 $requestRow = $this.parents( 'tr' ), 229 $progress = $requestRow.find( '.erasure-progress' ), 230 $rowActions = $this.parents( '.row-actions' ), 231 requestID = $action.data( 'request-id' ), 232 nonce = $action.data( 'nonce' ), 233 erasersCount = $action.data( 'erasers-count' ), 234 hasRemoved = false, 235 hasRetained = false, 236 messages = []; 237 238 event.preventDefault(); 239 event.stopPropagation(); 240 241 $rowActions.addClass( 'processing' ); 242 243 $action.trigger( 'blur' ); 244 clearResultsAfterRow( $requestRow ); 245 setErasureProgress( 0 ); 246 247 /** 248 * Handles a successful erasure. 249 * 250 * @since 4.9.6 251 * 252 * @return {void} 253 */ 254 function onErasureDoneSuccess() { 255 var summaryMessage = __( 'No personal data was found for this user.' ), 256 classes = 'notice-success'; 257 258 setActionState( $action, 'remove-personal-data-success' ); 259 260 if ( false === hasRemoved ) { 261 if ( false === hasRetained ) { 262 summaryMessage = __( 'No personal data was found for this user.' ); 263 } else { 264 summaryMessage = __( 'Personal data was found for this user but was not erased.' ); 265 classes = 'notice-warning'; 266 } 267 } else { 268 if ( false === hasRetained ) { 269 summaryMessage = __( 'All of the personal data found for this user was erased.' ); 270 } else { 271 summaryMessage = __( 'Personal data was found for this user but some of the personal data found was not erased.' ); 272 classes = 'notice-warning'; 273 } 274 } 275 appendResultsAfterRow( $requestRow, classes, summaryMessage, messages ); 276 277 setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 ); 278 } 279 280 /** 281 * Handles an erasure failure. 282 * 283 * @since 4.9.6 284 * 285 * @return {void} 286 */ 287 function onErasureFailure() { 288 var summaryMessage = __( 'An error occurred while attempting to find and erase personal data.' ); 289 290 setActionState( $action, 'remove-personal-data-failed' ); 291 292 appendResultsAfterRow( $requestRow, 'notice-error', summaryMessage, [] ); 293 294 setTimeout( function() { $rowActions.removeClass( 'processing' ); }, 500 ); 295 } 296 297 /** 298 * Updates the erasure progress indicator based on the number of completed erasers. 299 * 300 * @since 5.4.0 301 * 302 * @param {number} eraserIndex The number of erasers that have finished. Should be 303 * between 0 and erasersCount. 304 * @return {void} 305 */ 306 function setErasureProgress( eraserIndex ) { 307 var progress = ( erasersCount > 0 ? eraserIndex / erasersCount : 0 ), 308 progressString = Math.round( progress * 100 ).toString() + '%'; 309 310 $progress.html( progressString ); 311 } 312 313 /** 314 * Performs the next erasure request. 315 * 316 * @since 4.9.6 317 * 318 * @param {number} eraserIndex The 1-based index of the eraser to process, between 1 319 * and erasersCount. 320 * @param {number} pageIndex The 1-based index of the page to process for the current eraser. 321 * @return {void} 322 */ 323 function doNextErasure( eraserIndex, pageIndex ) { 324 $.ajax({ 325 url: window.ajaxurl, 326 data: { 327 action: 'wp-privacy-erase-personal-data', 328 eraser: eraserIndex, 329 id: requestID, 330 page: pageIndex, 331 security: nonce 332 }, 333 method: 'post' 334 }).done( function( response ) { 335 var responseData = response.data; 336 337 if ( ! response.success ) { 338 setTimeout( function() { onErasureFailure(); }, 500 ); 339 return; 340 } 341 if ( responseData.items_removed ) { 342 hasRemoved = hasRemoved || responseData.items_removed; 343 } 344 if ( responseData.items_retained ) { 345 hasRetained = hasRetained || responseData.items_retained; 346 } 347 if ( responseData.messages ) { 348 messages = messages.concat( responseData.messages ); 349 } 350 if ( ! responseData.done ) { 351 setTimeout( doNextErasure( eraserIndex, pageIndex + 1 ) ); 352 } else { 353 setErasureProgress( eraserIndex ); 354 if ( eraserIndex < erasersCount ) { 355 setTimeout( doNextErasure( eraserIndex + 1, 1 ) ); 356 } else { 357 setTimeout( function() { onErasureDoneSuccess(); }, 500 ); 358 } 359 } 360 }).fail( function() { 361 setTimeout( function() { onErasureFailure(); }, 500 ); 362 }); 363 } 364 365 // And now, let's begin. 366 setActionState( $action, 'remove-personal-data-processing' ); 367 368 doNextErasure( 1, 1 ); 369 }); 370 371 // Privacy Policy page, copy action. 372 $( document ).on( 'click', function( event ) { 373 var $parent, 374 range, 375 $target = $( event.target ), 376 copiedNotice = $target.siblings( '.success' ); 377 378 clearTimeout( copiedNoticeTimeout ); 379 380 if ( $target.is( 'button.privacy-text-copy' ) ) { 381 $parent = $target.closest( '.privacy-settings-accordion-panel' ); 382 383 if ( $parent.length ) { 384 try { 385 var documentPosition = document.documentElement.scrollTop, 386 bodyPosition = document.body.scrollTop; 387 388 // Setup copy. 389 window.getSelection().removeAllRanges(); 390 391 // Hide tutorial content to remove from copied content. 392 range = document.createRange(); 393 $parent.addClass( 'hide-privacy-policy-tutorial' ); 394 395 // Copy action. Select only the dedicated copy-content wrapper 396 // so the actions toolbar (which contains the "Copied!" notice 397 // and button label) is never part of the selection - see #58969. 398 range.selectNodeContents( $parent.find( '.privacy-text-copy-content' )[0] ); 399 window.getSelection().addRange( range ); 400 document.execCommand( 'copy' ); 401 402 // Reset section. 403 $parent.removeClass( 'hide-privacy-policy-tutorial' ); 404 window.getSelection().removeAllRanges(); 405 406 // Return scroll position - see #49540. 407 if ( documentPosition > 0 && documentPosition !== document.documentElement.scrollTop ) { 408 document.documentElement.scrollTop = documentPosition; 409 } else if ( bodyPosition > 0 && bodyPosition !== document.body.scrollTop ) { 410 document.body.scrollTop = bodyPosition; 411 } 412 413 // Display and speak notice to indicate action complete. 414 copiedNotice.addClass( 'visible' ); 415 wp.a11y.speak( __( 'The suggested policy text has been copied to your clipboard.' ) ); 416 417 // Delay notice dismissal. 418 copiedNoticeTimeout = setTimeout( function() { 419 copiedNotice.removeClass( 'visible' ); 420 }, 3000 ); 421 } catch ( er ) {} 422 } 423 } 424 }); 425 426 // Label handling to focus the create page button on Privacy settings page. 427 $( 'body.options-privacy-php label[for=create-page]' ).on( 'click', function( e ) { 428 e.preventDefault(); 429 $( 'input#create-page' ).trigger( 'focus' ); 430 } ); 431 432 // Accordion handling in various new Privacy settings pages. 433 $( '.privacy-settings-accordion' ).on( 'click', '.privacy-settings-accordion-trigger', function() { 434 var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) ); 435 436 if ( isExpanded ) { 437 $( this ).attr( 'aria-expanded', 'false' ); 438 $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true ); 439 } else { 440 $( this ).attr( 'aria-expanded', 'true' ); 441 $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false ); 442 } 443 } ); 444 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Oct 11 08:20:32 2026 | Cross-referenced by PHPXref |