Ticket #43438: 43438.15.diff
File 43438.15.diff, 50.7 KB (added by , 6 years ago) |
---|
-
src/wp-admin/includes/ajax-actions.php
4429 4429 } 4430 4430 4431 4431 if ( $exporter_index > count( $exporters ) ) { 4432 wp_send_json_error( __( 'Exporter index out of range.' ) );4432 wp_send_json_error( __( 'Exporter index is out of range.' ) ); 4433 4433 } 4434 4434 4435 4435 if ( $page < 1 ) { … … 4452 4452 sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key ) 4453 4453 ); 4454 4454 } 4455 4456 $exporter_friendly_name = $exporter['exporter_friendly_name']; 4457 4455 4458 if ( ! array_key_exists( 'callback', $exporter ) ) { 4456 4459 wp_send_json_error( 4457 4460 /* translators: %s: exporter friendly name */ 4458 sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter ['exporter_friendly_name']) )4461 sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) ) 4459 4462 ); 4460 4463 } 4461 4464 if ( ! is_callable( $exporter['callback'] ) ) { 4462 4465 wp_send_json_error( 4463 4466 /* translators: %s: exporter friendly name */ 4464 sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter ['exporter_friendly_name']) )4467 sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) ) 4465 4468 ); 4466 4469 } 4467 4470 4468 $callback 4469 $ exporter_friendly_name = $exporter['exporter_friendly_name'];4471 $callback = $exporter['callback']; 4472 $response = call_user_func( $callback, $email_address, $page ); 4470 4473 4471 $response = call_user_func( $callback, $email_address, $page );4472 4474 if ( is_wp_error( $response ) ) { 4473 4475 wp_send_json_error( $response ); 4474 4476 } … … 4559 4561 $request = wp_get_user_request_data( $request_id ); 4560 4562 4561 4563 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 4562 wp_send_json_error( __( 'Invalid request ID.' ) );4564 wp_send_json_error( __( 'Invalid request type.' ) ); 4563 4565 } 4564 4566 4565 4567 $email_address = $request->email; … … 4626 4628 wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) ); 4627 4629 } 4628 4630 4629 if ( ! array_key_exists( ' callback', $eraser ) ) {4631 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) { 4630 4632 /* translators: %d: array index */ 4631 wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );4633 wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) ); 4632 4634 } 4633 4635 4634 if ( ! is_callable( $eraser['callback'] ) ) { 4635 /* translators: %d: array index */ 4636 wp_send_json_error( sprintf( __( 'Eraser callback at index %d is not a valid callback.' ), $eraser_index ) ); 4636 $eraser_friendly_name = $eraser['eraser_friendly_name']; 4637 4638 if ( ! array_key_exists( 'callback', $eraser ) ) { 4639 wp_send_json_error( 4640 sprintf( 4641 /* translators: %d: array index */ 4642 __( 'Eraser does not include a callback: %s.' ), 4643 esc_html( $eraser_friendly_name ) 4644 ) 4645 ); 4637 4646 } 4638 4647 4639 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) { 4640 /* translators: %d: array index */ 4641 wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) ); 4648 if ( ! is_callable( $eraser['callback'] ) ) { 4649 wp_send_json_error( 4650 sprintf( 4651 /* translators: %d: array index */ 4652 __( 'Eraser callback is not valid: %s.' ), 4653 esc_html( $eraser_friendly_name ) 4654 ) 4655 ); 4642 4656 } 4643 4657 4644 $callback = $eraser['callback']; 4645 $eraser_friendly_name = $eraser['eraser_friendly_name']; 4646 4658 $callback = $eraser['callback']; 4647 4659 $response = call_user_func( $callback, $email_address, $page ); 4648 4660 4649 4661 if ( is_wp_error( $response ) ) { -
tests/phpunit/includes/testcase-ajax.php
119 119 'delete-theme', 120 120 'install-theme', 121 121 'get-post-thumbnail-html', 122 'wp-privacy-export-personal-data', 123 'wp-privacy-erase-personal-data', 122 124 ); 123 125 124 126 public static function setUpBeforeClass() { -
tests/phpunit/tests/ajax/PrivacyErasePersonalData.php
1 <?php 2 /** 3 * Testing Ajax handler for erasing personal data. 4 * 5 * @package WordPress\UnitTests 6 * 7 * @since 4.9.9 8 */ 9 10 /** 11 * Tests_Ajax_PrivacyExportPersonalData class. 12 * 13 * @since 4.9.9 14 * 15 * @group ajax 16 * @group privacy 17 * @covers wp_ajax_wp_privacy_erase_personal_data 18 */ 19 class Tests_Ajax_PrivacyErasePersonalData extends WP_Ajax_UnitTestCase { 20 /** 21 * User Request ID. 22 * 23 * @since 4.9.9 24 * 25 * @var int $request_id 26 */ 27 protected static $request_id; 28 29 /** 30 * User Request Email. 31 * 32 * @since 4.9.9 33 * 34 * @var string $request_email 35 */ 36 protected static $request_email; 37 38 /** 39 * Ajax Action. 40 * 41 * @since 4.9.9 42 * 43 * @var string $action 44 */ 45 protected static $action; 46 47 /** 48 * Eraser Index. 49 * 50 * @since 4.9.9 51 * 52 * @var int $eraser 53 */ 54 protected static $eraser; 55 56 /** 57 * Eraser Key. 58 * 59 * @since 4.9.9 60 * 61 * @var string $eraser_key 62 */ 63 protected static $eraser_key; 64 65 /** 66 * Eraser Friendly Name. 67 * 68 * @since 4.9.9 69 * 70 * @var string $eraser_friendly_name 71 */ 72 protected static $eraser_friendly_name; 73 74 /** 75 * Page Index. 76 * 77 * @since 4.9.9 78 * 79 * @var int $page 80 */ 81 protected static $page; 82 83 /** 84 * Last response parsed. 85 * 86 * @since 4.9.9 87 * 88 * @var array $_last_response_parsed 89 */ 90 protected $_last_response_parsed; 91 92 /** 93 * A response index to unset. 94 * 95 * @since 4.9.9 96 * 97 * @var string $index_to_unset 98 */ 99 protected $index_to_unset; 100 101 /** 102 * Create user erase request fixtures. 103 * 104 * @param WP_UnitTest_Factory $factory Factory. 105 */ 106 public static function wpSetUpBeforeClass( $factory ) { 107 self::$request_email = 'requester@example.com'; 108 self::$request_id = wp_create_user_request( self::$request_email, 'remove_personal_data' ); 109 self::$action = 'wp-privacy-erase-personal-data'; 110 self::$eraser = 1; 111 self::$eraser_key = 'custom-eraser'; 112 self::$eraser_friendly_name = 'Custom Eraser'; 113 self::$page = 1; 114 } 115 116 /** 117 * Register a custom personal data eraser. 118 */ 119 public function setUp() { 120 parent::setUp(); 121 122 $this->index_to_unset = ''; 123 124 // Make sure the erasers response is not modified and avoid sending emails. 125 remove_all_filters( 'wp_privacy_personal_data_erasure_page' ); 126 remove_all_actions( 'wp_privacy_personal_data_erased' ); 127 128 // Only use our custom privacy personal data eraser. 129 remove_all_filters( 'wp_privacy_personal_data_erasers' ); 130 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) ); 131 132 $this->_setRole( 'administrator' ); 133 } 134 135 /** 136 * Clean up after each test method. 137 */ 138 public function tearDown() { 139 remove_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) ); 140 141 parent::tearDown(); 142 } 143 144 /** 145 * The function should send an error when the request ID is missing. 146 * 147 * @since 4.9.9 148 * 149 * @ticket 43438 150 */ 151 public function test_error_when_missing_request_id() { 152 $this->assertNotWPError( self::$request_id ); 153 154 // Set up a request. 155 $this->_make_ajax_call( 156 array( 157 'id' => null, 158 ) 159 ); 160 161 $this->assertFalse( $this->_last_response_parsed['success'] ); 162 $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] ); 163 } 164 165 /** 166 * The function should send an error when the request ID is less than 1. 167 * 168 * @since 4.9.9 169 * 170 * @ticket 43438 171 */ 172 public function test_error_when_request_id_invalid() { 173 $this->assertNotWPError( self::$request_id ); 174 175 // Set up a request. 176 $this->_make_ajax_call( 177 array( 178 'id' => -1, 179 ) 180 ); 181 182 $this->assertFalse( $this->_last_response_parsed['success'] ); 183 $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] ); 184 } 185 186 /** 187 * The function should send an error when the current user is missing required capabilities. 188 * 189 * @since 4.9.9 190 * 191 * @ticket 43438 192 */ 193 public function test_error_when_current_user_missing_required_capabilities() { 194 $this->_setRole( 'author' ); 195 196 $this->assertFalse( current_user_can( 'erase_others_personal_data' ) ); 197 $this->assertFalse( current_user_can( 'delete_users' ) ); 198 199 $this->_make_ajax_call(); 200 201 $this->assertFalse( $this->_last_response_parsed['success'] ); 202 $this->assertSame( 'Invalid request.', $this->_last_response_parsed['data'] ); 203 } 204 205 /** 206 * The function should send an error when the nonce does not validate. 207 * 208 * @since 4.9.9 209 */ 210 public function test_failure_with_invalid_nonce() { 211 $this->setExpectedException( 'WPAjaxDieStopException', '-1' ); 212 213 $this->_make_ajax_call( 214 array( 215 'security' => 'not-a-valid-nonce', 216 ) 217 ); 218 } 219 220 /** 221 * The function should send an error when the request type is incorrect. 222 * 223 * @since 4.9.9 224 */ 225 public function test_error_when_incorrect_request_type() { 226 $request_id = wp_create_user_request( 'export-request@example.com', 'export_personal_data' ); 227 228 $this->_make_ajax_call( 229 array( 230 'security' => wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ), 231 'id' => $request_id, 232 ) 233 ); 234 235 $this->assertFalse( $this->_last_response_parsed['success'] ); 236 $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] ); 237 } 238 239 /** 240 * The function should send an error when the request email is invalid. 241 * 242 * @since 4.9.9 243 */ 244 public function test_error_when_invalid_email() { 245 wp_update_post( 246 array( 247 'ID' => self::$request_id, 248 'post_title' => '', 249 ) 250 ); 251 252 $this->_make_ajax_call(); 253 254 $this->assertFalse( $this->_last_response_parsed['success'] ); 255 $this->assertSame( 'Invalid email address in request.', $this->_last_response_parsed['data'] ); 256 } 257 258 /** 259 * The function should send an error when the eraser index is missing. 260 * 261 * @since 4.9.9 262 */ 263 public function test_error_when_missing_eraser_index() { 264 $this->_make_ajax_call( 265 array( 266 'eraser' => null, 267 ) 268 ); 269 270 $this->assertFalse( $this->_last_response_parsed['success'] ); 271 $this->assertSame( 'Missing eraser index.', $this->_last_response_parsed['data'] ); 272 } 273 274 /** 275 * The function should send an error when the page index is missing. 276 * 277 * @since 4.9.9 278 */ 279 public function test_error_when_missing_page_index() { 280 $this->_make_ajax_call( 281 array( 282 'page' => null, 283 ) 284 ); 285 286 $this->assertFalse( $this->_last_response_parsed['success'] ); 287 $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] ); 288 } 289 290 /** 291 * The function should send an error when the eraser index is negative. 292 * 293 * @since 4.9.9 294 */ 295 public function test_error_when_negative_eraser_index() { 296 $this->_make_ajax_call( 297 array( 298 'eraser' => -1, // Negative eraser index. 299 ) 300 ); 301 302 $this->assertFalse( $this->_last_response_parsed['success'] ); 303 $this->assertSame( 'Eraser index cannot be less than one.', $this->_last_response_parsed['data'] ); 304 } 305 306 /** 307 * The function should send an error when the eraser index is out of range. 308 * 309 * @since 4.9.9 310 */ 311 public function test_error_when_eraser_index_out_of_range() { 312 $this->_make_ajax_call( 313 array( 314 'eraser' => PHP_INT_MAX, // Out of range eraser index. 315 ) 316 ); 317 318 $this->assertFalse( $this->_last_response_parsed['success'] ); 319 $this->assertSame( 'Eraser index is out of range.', $this->_last_response_parsed['data'] ); 320 } 321 322 /** 323 * The function should send an error when the page index is less than one. 324 * 325 * @since 4.9.9 326 */ 327 public function test_error_when_page_index_less_than_one() { 328 $this->_make_ajax_call( 329 array( 330 'page' => 0, // Page index less than one. 331 ) 332 ); 333 334 $this->assertFalse( $this->_last_response_parsed['success'] ); 335 $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] ); 336 } 337 338 /** 339 * The function should send an error when an eraser is not an array. 340 * 341 * @since 4.9.9 342 */ 343 public function test_error_when_eraser_not_array() { 344 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_not_array' ), 20 ); 345 $this->_make_ajax_call(); 346 347 $this->assertFalse( $this->_last_response_parsed['success'] ); 348 $this->assertSame( 349 sprintf( 350 'Expected an array describing the eraser at index %s.', 351 self::$eraser 352 ), 353 $this->_last_response_parsed['data'] 354 ); 355 } 356 357 /** 358 * Change the eraser information to an invalid value. 359 * 360 * @since 4.9.9 361 * 362 * @param array $erasers Erasers. 363 * @return array $erasers Erasers. 364 */ 365 public function filter_eraser_not_array( $erasers ) { 366 $erasers[ self::$eraser_key ] = false; 367 368 return $erasers; 369 } 370 371 /** 372 * Unsets an array index in the test eraser. 373 * 374 * @since 4.9.9 375 * 376 * @param array $erasers Erasers. 377 * @return array $erasers Erasers. 378 */ 379 public function filter_unset_eraser_index( $erasers ) { 380 if ( ! empty( $this->index_to_unset ) ) { 381 unset( $erasers[ self::$eraser_key ][ $this->index_to_unset ] ); 382 } 383 384 return $erasers; 385 } 386 387 /** 388 * The function should send an error when an eraser is missing a friendly name. 389 * 390 * @since 4.9.9 391 */ 392 public function test_error_when_eraser_missing_friendly_name() { 393 $this->index_to_unset = 'eraser_friendly_name'; 394 395 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 ); 396 $this->_make_ajax_call(); 397 398 $this->assertFalse( $this->_last_response_parsed['success'] ); 399 $this->assertSame( 400 sprintf( 401 'Eraser array at index %s does not include a friendly name.', 402 self::$eraser 403 ), 404 $this->_last_response_parsed['data'] 405 ); 406 } 407 408 /** 409 * The function should send an error when an eraser is missing a callback. 410 * 411 * @since 4.9.9 412 */ 413 public function test_error_when_eraser_missing_callback() { 414 $this->index_to_unset = 'callback'; 415 416 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 ); 417 $this->_make_ajax_call(); 418 419 $this->assertFalse( $this->_last_response_parsed['success'] ); 420 $this->assertSame( 421 sprintf( 422 'Eraser does not include a callback: %s.', 423 self::$eraser_friendly_name 424 ), 425 $this->_last_response_parsed['data'] 426 ); 427 } 428 429 /** 430 * The function should send an error when an eraser, at a given index, has an invalid callback. 431 * 432 * @since 4.9.9 433 */ 434 public function test_error_when_eraser_index_invalid_callback() { 435 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_index_invalid_callback' ), 20 ); 436 $this->_make_ajax_call(); 437 438 $this->assertFalse( $this->_last_response_parsed['success'] ); 439 $this->assertSame( 440 sprintf( 441 'Eraser callback is not valid: %s.', 442 self::$eraser_friendly_name 443 ), 444 $this->_last_response_parsed['data'] 445 ); 446 } 447 448 /** 449 * Change the eraser callback to be invalid. 450 * 451 * @since 4.9.9 452 * 453 * @param array $erasers Erasers. 454 * @return array $erasers Erasers. 455 */ 456 public function filter_eraser_index_invalid_callback( $erasers ) { 457 $erasers[ self::$eraser_key ]['callback'] = false; 458 459 return $erasers; 460 } 461 462 /** 463 * The function should send an error when an eraser, at a given index, is missing an array response. 464 * 465 * @since 4.9.9 466 */ 467 public function test_error_when_eraser_index_invalid_response() { 468 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_index_invalid_response' ), 20 ); 469 $this->_make_ajax_call(); 470 471 $this->assertFalse( $this->_last_response_parsed['success'] ); 472 $this->assertSame( 473 sprintf( 474 'Did not receive array from %1$s eraser (index %2$d).', 475 self::$eraser_friendly_name, 476 self::$eraser 477 ), 478 $this->_last_response_parsed['data'] 479 ); 480 } 481 482 /** 483 * Change the eraser callback response to an invalid value. 484 * 485 * @since 4.9.9 486 * 487 * @param array $erasers Erasers. 488 * @return array $erasers Erasers. 489 */ 490 public function filter_eraser_index_invalid_response( $erasers ) { 491 $erasers[ self::$eraser_key ]['callback'] = '__return_null'; 492 493 return $erasers; 494 } 495 496 /** 497 * Unsets an array index in a response. 498 * 499 * @since 4.9.9 500 * 501 * @param string $email_address The requester's email address. 502 * @param int $page Page number. 503 * @return array $return Export data. 504 */ 505 public function filter_unset_response_index( $email_address, $page = 1 ) { 506 $response = $this->callback_personal_data_eraser( $email_address, $page ); 507 508 if ( ! empty( $this->index_to_unset ) ) { 509 unset( $response[ $this->index_to_unset ] ); 510 } 511 512 return $response; 513 } 514 515 /** 516 * Change the test eraser's callback to one that unsets an index in the response. 517 * 518 * @since 4.9.9 519 * 520 * @param array $erasers Erasers. 521 * @return array $erasers Erasers. 522 */ 523 public function filter_callback_unset_index( $erasers ) { 524 $erasers[ self::$eraser_key ]['callback'] = array( $this, 'filter_unset_response_index' ); 525 526 return $erasers; 527 } 528 529 /** 530 * The function should send an error when missing an items_removed index. 531 * 532 * @since 4.9.9 533 */ 534 public function test_error_when_eraser_items_removed_missing() { 535 $this->index_to_unset = 'items_removed'; 536 537 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 ); 538 $this->_make_ajax_call(); 539 540 $this->assertFalse( $this->_last_response_parsed['success'] ); 541 $this->assertSame( 542 sprintf( 543 'Expected items_removed key in response array from %1$s eraser (index %2$d).', 544 self::$eraser_friendly_name, 545 self::$eraser 546 ), 547 $this->_last_response_parsed['data'] 548 ); 549 } 550 551 /** 552 * The function should send an error when missing an items_retained index. 553 * 554 * @since 4.9.9 555 */ 556 public function test_error_when_eraser_items_retained_missing() { 557 $this->index_to_unset = 'items_retained'; 558 559 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 ); 560 $this->_make_ajax_call(); 561 562 $this->assertFalse( $this->_last_response_parsed['success'] ); 563 $this->assertSame( 564 sprintf( 565 'Expected items_retained key in response array from %1$s eraser (index %2$d).', 566 self::$eraser_friendly_name, 567 self::$eraser 568 ), 569 $this->_last_response_parsed['data'] 570 ); 571 } 572 573 /** 574 * The function should send an error when missing a messages index. 575 * 576 * @since 4.9.9 577 */ 578 public function test_error_when_eraser_messages_missing() { 579 $this->index_to_unset = 'messages'; 580 581 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 ); 582 $this->_make_ajax_call(); 583 584 $this->assertFalse( $this->_last_response_parsed['success'] ); 585 $this->assertSame( 586 sprintf( 587 'Expected messages key in response array from %1$s eraser (index %2$d).', 588 self::$eraser_friendly_name, 589 self::$eraser 590 ), 591 $this->_last_response_parsed['data'] 592 ); 593 } 594 595 /** 596 * The function should send an error when the messages index is not an array. 597 * 598 * @since 4.9.9 599 */ 600 public function test_error_when_eraser_messages_not_array() { 601 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_invalid_messages_index' ), 20 ); 602 $this->_make_ajax_call(); 603 604 $this->assertFalse( $this->_last_response_parsed['success'] ); 605 $this->assertSame( 606 sprintf( 607 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).', 608 self::$eraser_friendly_name, 609 self::$eraser 610 ), 611 $this->_last_response_parsed['data'] 612 ); 613 } 614 615 /** 616 * Change the test eraser's callback to one that returns an invalid value at the message index. 617 * 618 * @since 4.9.9 619 * 620 * @param array $erasers Erasers. 621 * @return array $erasers Erasers. 622 */ 623 public function filter_callback_invalid_messages_index( $erasers ) { 624 $erasers[ self::$eraser_key ]['callback'] = array( $this, 'filter_response_messages_invalid' ); 625 626 return $erasers; 627 } 628 629 /** 630 * Change the messages index to an invalid value (not an array). 631 * 632 * @since 4.9.9 633 * 634 * @param string $email_address The requester's email address. 635 * @param int $page Page number. 636 * @return array $return Export data. 637 */ 638 public function filter_response_messages_invalid( $email_address, $page = 1 ) { 639 $response = $this->callback_personal_data_eraser( $email_address, $page ); 640 $response['messages'] = true; 641 642 return $response; 643 } 644 645 /** 646 * The function should send an error when an eraser is missing 'done' in array response. 647 * 648 * @since 4.9.9 649 */ 650 public function test_error_when_eraser_missing_done_response() { 651 $this->index_to_unset = 'done'; 652 653 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 ); 654 $this->_make_ajax_call(); 655 656 $this->assertFalse( $this->_last_response_parsed['success'] ); 657 $this->assertSame( 658 sprintf( 659 'Expected done flag in response array from %1$s eraser (index %2$d).', 660 self::$eraser_friendly_name, 661 self::$eraser 662 ), 663 $this->_last_response_parsed['data'] 664 ); 665 } 666 667 /** 668 * The function should successfully send erasers response data when the current user has the required 669 * capabilities. 670 * 671 * @since 4.9.9 672 * 673 * @ticket 43438 674 */ 675 public function test_success_when_current_user_has_required_capabilities() { 676 $this->assertTrue( current_user_can( 'erase_others_personal_data' ) ); 677 $this->assertTrue( current_user_can( 'delete_users' ) ); 678 679 $this->_make_ajax_call(); 680 681 $this->assertSame( 'A message regarding retained data for requester@example.com.', $this->_last_response_parsed['data']['messages'][0] ); 682 $this->assertTrue( $this->_last_response_parsed['success'] ); 683 $this->assertTrue( $this->_last_response_parsed['data']['items_removed'] ); 684 $this->assertTrue( $this->_last_response_parsed['data']['items_retained'] ); 685 $this->assertTrue( $this->_last_response_parsed['data']['done'] ); 686 } 687 688 /** 689 * Test that the function's output should be filterable with the `wp_privacy_personal_data_erasure_page` filter. 690 * 691 * @since 4.9.9 692 */ 693 public function test_output_should_be_filterable() { 694 add_filter( 'wp_privacy_personal_data_erasure_page', array( $this, 'filter_exporter_data_response' ), 20, 6 ); 695 $this->_make_ajax_call(); 696 697 $expected_new_index = self::$request_email . '-' . self::$request_id . '-' . self::$eraser_key; 698 699 $this->assertTrue( $this->_last_response_parsed['success'] ); 700 $this->assertSame( 'filtered removed', $this->_last_response_parsed['data']['items_removed'] ); 701 $this->assertSame( 'filtered retained', $this->_last_response_parsed['data']['items_retained'] ); 702 $this->assertSame( array( 'filtered messages' ), $this->_last_response_parsed['data']['messages'] ); 703 $this->assertSame( 'filtered done', $this->_last_response_parsed['data']['done'] ); 704 $this->assertSame( $expected_new_index, $this->_last_response_parsed['data']['new_index'] ); 705 } 706 707 /** 708 * Filters the exporter response. 709 * 710 * @since 4.9.9 711 * 712 * @param array $response The personal data for the given exporter and page. 713 * @param int $eraser_index The index of the eraser that provided this data. 714 * @param string $email_address The email address associated with this personal data. 715 * @param int $page The page for this response. 716 * @param int $request_id The privacy request post ID associated with this request. 717 * @param string $eraser_key The key (slug) of the eraser that provided this data. 718 * @return array Filtered export response. 719 */ 720 public function filter_exporter_data_response( $response, $eraser_index, $email_address, $page, $request_id, $eraser_key ) { 721 $response['items_removed'] = 'filtered removed'; 722 $response['items_retained'] = 'filtered retained'; 723 $response['messages'] = array( 'filtered messages' ); 724 $response['done'] = 'filtered done'; 725 $response['new_index'] = $email_address . '-' . $request_id . '-' . $eraser_key; 726 727 return $response; 728 } 729 730 /** 731 * Register handler for a custom personal data eraser. 732 * 733 * @since 4.9.9 734 * 735 * @param array $erasers An array of personal data erasers. 736 * @return array $erasers An array of personal data erasers. 737 */ 738 public function register_custom_personal_data_eraser( $erasers ) { 739 $erasers[ self::$eraser_key ] = array( 740 'eraser_friendly_name' => self::$eraser_friendly_name, 741 'callback' => array( $this, 'callback_personal_data_eraser' ), 742 ); 743 return $erasers; 744 } 745 746 /** 747 * Custom Personal Data Eraser. 748 * 749 * @since 4.9.9 750 * 751 * @param string $email_address The comment author email address. 752 * @param int $page Page number. 753 * @return array $return Erase data. 754 */ 755 public function callback_personal_data_eraser( $email_address, $page = 1 ) { 756 if ( 1 === $page ) { 757 return array( 758 'items_removed' => true, 759 'items_retained' => true, 760 'messages' => array( sprintf( 'A message regarding retained data for %s.', $email_address ) ), 761 'done' => true, 762 ); 763 } 764 765 return array( 766 'items_removed' => false, 767 'items_retained' => false, 768 'messages' => array(), 769 'done' => true, 770 ); 771 } 772 773 /** 774 * Helper function for ajax handler. 775 * 776 * @since 4.9.9 777 * 778 * @param array $args Ajax request arguments. 779 */ 780 protected function _make_ajax_call( $args = array() ) { 781 $this->_last_response_parsed = null; 782 $this->_last_response = ''; 783 784 $defaults = array( 785 'action' => self::$action, 786 'security' => wp_create_nonce( self::$action . '-' . self::$request_id ), 787 'page' => self::$page, 788 'id' => self::$request_id, 789 'eraser' => self::$eraser, 790 ); 791 792 $_POST = wp_parse_args( $args, $defaults ); 793 794 try { 795 $this->_handleAjax( self::$action ); 796 } catch ( WPAjaxDieContinueException $e ) { 797 unset( $e ); 798 } 799 800 if ( $this->_last_response ) { 801 $this->_last_response_parsed = json_decode( $this->_last_response, true ); 802 } 803 } 804 } -
tests/phpunit/tests/ajax/PrivacyExportPersonalData.php
1 <?php 2 /** 3 * Testing Ajax handler for exporting personal data. 4 * 5 * @package WordPress\UnitTests 6 * 7 * @since 4.9.9 8 */ 9 10 /** 11 * Tests_Ajax_PrivacyExportPersonalData class. 12 * 13 * @since 4.9.9 14 * 15 * @group ajax 16 * @group privacy 17 * @covers wp_ajax_wp_privacy_export_personal_data 18 */ 19 class Tests_Ajax_PrivacyExportPersonalData extends WP_Ajax_UnitTestCase { 20 /** 21 * User Request ID. 22 * 23 * @since 4.9.9 24 * 25 * @var int $request_id 26 */ 27 protected static $request_id; 28 29 /** 30 * User Request Email. 31 * 32 * @since 4.9.9 33 * 34 * @var string $request_email 35 */ 36 protected static $request_email; 37 38 /** 39 * Ajax Action. 40 * 41 * @since 4.9.9 42 * 43 * @var string $action 44 */ 45 protected static $action; 46 47 /** 48 * Exporter Index. 49 * 50 * @since 4.9.9 51 * 52 * @var int $exporter 53 */ 54 protected static $exporter; 55 56 /** 57 * Exporter Key. 58 * 59 * @since 4.9.9 60 * 61 * @var string $exporter_key 62 */ 63 protected static $exporter_key; 64 65 /** 66 * Exporter Friendly Name. 67 * 68 * @since 4.9.9 69 * 70 * @var string $exporter_friendly_name 71 */ 72 protected static $exporter_friendly_name; 73 74 /** 75 * Page Index. 76 * 77 * @since 4.9.9 78 * 79 * @var int $page 80 */ 81 protected static $page; 82 83 /** 84 * Send As Email. 85 * 86 * @since 4.9.9 87 * 88 * @var bool $send_as_email 89 */ 90 protected static $send_as_email; 91 92 /** 93 * Last response parsed. 94 * 95 * @since 4.9.9 96 * 97 * @var array $_last_response_parsed 98 */ 99 protected $_last_response_parsed; 100 101 /** 102 * Create user export request fixtures. 103 * 104 * @since 4.9.9 105 * 106 * @param WP_UnitTest_Factory $factory Factory. 107 */ 108 public static function wpSetUpBeforeClass( $factory ) { 109 self::$request_email = 'requester@example.com'; 110 self::$request_id = wp_create_user_request( self::$request_email, 'export_personal_data' ); 111 self::$action = 'wp-privacy-export-personal-data'; 112 self::$exporter = 1; 113 self::$exporter_key = 'custom-exporter'; 114 self::$exporter_friendly_name = 'Custom Exporter'; 115 self::$page = 1; 116 self::$send_as_email = false; 117 } 118 119 /** 120 * Setup before each test method. 121 * 122 * @since 4.9.9 123 */ 124 public function setUp() { 125 parent::setUp(); 126 127 // Make sure the exporter response is not modified and avoid e.g. writing export file to disk. 128 remove_all_filters( 'wp_privacy_personal_data_export_page' ); 129 130 // Only use our custom privacy personal data exporter. 131 remove_all_filters( 'wp_privacy_personal_data_exporters' ); 132 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) ); 133 134 $this->_setRole( 'administrator' ); 135 } 136 137 /** 138 * Clean up after each test method. 139 */ 140 public function tearDown() { 141 remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) ); 142 143 parent::tearDown(); 144 } 145 146 /** 147 * The function should send an error when the request ID is missing. 148 * 149 * @since 4.9.9 150 */ 151 public function test_error_when_missing_request_id() { 152 $this->_make_ajax_call( 153 array( 154 'id' => null, // Missing request ID. 155 ) 156 ); 157 158 $this->assertFalse( $this->_last_response_parsed['success'] ); 159 $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] ); 160 } 161 162 /** 163 * The function should send an error when the request ID is invalid. 164 * 165 * @since 4.9.9 166 */ 167 public function test_error_when_invalid_id() { 168 $this->_make_ajax_call( 169 array( 170 'id' => -1, // Invalid request ID. 171 ) 172 ); 173 174 $this->assertFalse( $this->_last_response_parsed['success'] ); 175 $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] ); 176 } 177 178 /** 179 * The function should send an error when the current user is missing the required capability. 180 * 181 * @since 4.9.9 182 */ 183 public function test_error_when_current_user_missing_required_capability() { 184 $this->_setRole( 'author' ); 185 186 $this->_make_ajax_call(); 187 188 $this->assertFalse( $this->_last_response_parsed['success'] ); 189 $this->assertFalse( current_user_can( 'export_others_personal_data' ) ); 190 $this->assertSame( 'Invalid request.', $this->_last_response_parsed['data'] ); 191 } 192 193 /** 194 * The function should send an error when the nonce does not validate. 195 * 196 * @since 4.9.9 197 */ 198 public function test_failure_with_invalid_nonce() { 199 $this->setExpectedException( 'WPAjaxDieStopException', '-1' ); 200 201 $this->_make_ajax_call( 202 array( 203 'security' => 'not-a-valid-nonce', 204 ) 205 ); 206 } 207 208 /** 209 * The function should send an error when the request type is incorrect. 210 * 211 * @since 4.9.9 212 */ 213 public function test_error_when_incorrect_request_type() { 214 $request_id = wp_create_user_request( 'erase-request@example.com', 'erase_personal_data' ); 215 216 $this->_make_ajax_call( 217 array( 218 'security' => wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ), 219 'id' => $request_id, 220 ) 221 ); 222 223 $this->assertFalse( $this->_last_response_parsed['success'] ); 224 $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] ); 225 } 226 227 /** 228 * The function should send an error when the requester's email address is invalid. 229 * 230 * @since 4.9.9 231 */ 232 public function test_error_when_invalid_email_address() { 233 wp_update_post( 234 array( 235 'ID' => self::$request_id, 236 'post_title' => '', // Invalid requester's email address. 237 ) 238 ); 239 240 $this->_make_ajax_call(); 241 242 $this->assertFalse( $this->_last_response_parsed['success'] ); 243 $this->assertSame( 'A valid email address must be given.', $this->_last_response_parsed['data'] ); 244 } 245 246 /** 247 * The function should send an error when the exporter index is missing. 248 * 249 * @since 4.9.9 250 */ 251 public function test_error_when_missing_exporter_index() { 252 $this->_make_ajax_call( 253 array( 254 'exporter' => null, // Missing exporter index. 255 ) 256 ); 257 258 $this->assertFalse( $this->_last_response_parsed['success'] ); 259 $this->assertSame( 'Missing exporter index.', $this->_last_response_parsed['data'] ); 260 } 261 262 /** 263 * The function should send an error when the page index is missing. 264 * 265 * @since 4.9.9 266 */ 267 public function test_error_when_missing_page_index() { 268 $this->_make_ajax_call( 269 array( 270 'page' => null, // Missing page index. 271 ) 272 ); 273 274 $this->assertFalse( $this->_last_response_parsed['success'] ); 275 $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] ); 276 } 277 278 /** 279 * The function should send an error when an exporter has improperly used the `wp_privacy_personal_data_exporters` filter. 280 * 281 * @since 4.9.9 282 */ 283 public function test_error_when_exporter_has_improperly_used_exporters_filter() { 284 // Improper filter usage: returns false instead of an expected array. 285 add_filter( 'wp_privacy_personal_data_exporters', '__return_false', 999 ); 286 $this->_make_ajax_call(); 287 288 $this->assertFalse( $this->_last_response_parsed['success'] ); 289 $this->assertSame( 'An exporter has improperly used the registration filter.', $this->_last_response_parsed['data'] ); 290 } 291 292 /** 293 * The function should send an error when the exporter index is negative. 294 * 295 * @since 4.9.9 296 */ 297 public function test_error_when_negative_exporter_index() { 298 $this->_make_ajax_call( 299 array( 300 'exporter' => -1, // Negative exporter index. 301 ) 302 ); 303 304 $this->assertFalse( $this->_last_response_parsed['success'] ); 305 $this->assertSame( 'Exporter index cannot be negative.', $this->_last_response_parsed['data'] ); 306 } 307 308 /** 309 * The function should send an error when the exporter index is out of range. 310 * 311 * @since 4.9.9 312 */ 313 public function test_error_when_exporter_index_out_of_range() { 314 $this->_make_ajax_call( 315 array( 316 'exporter' => PHP_INT_MAX, // Out of range exporter index. 317 ) 318 ); 319 320 $this->assertFalse( $this->_last_response_parsed['success'] ); 321 $this->assertSame( 'Exporter index is out of range.', $this->_last_response_parsed['data'] ); 322 } 323 324 /** 325 * The function should send an error when the page index is less than one. 326 * 327 * @since 4.9.9 328 */ 329 public function test_error_when_page_index_less_than_one() { 330 $this->_make_ajax_call( 331 array( 332 'page' => 0, // Page index less than one. 333 ) 334 ); 335 336 $this->assertFalse( $this->_last_response_parsed['success'] ); 337 $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] ); 338 } 339 340 /** 341 * The function should send an error when an exporter is not an array. 342 * 343 * @since 4.9.9 344 */ 345 public function test_error_when_exporter_not_array() { 346 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_not_array' ), 20 ); 347 $this->_make_ajax_call(); 348 349 $this->assertFalse( $this->_last_response_parsed['success'] ); 350 $this->assertSame( 351 sprintf( 352 'Expected an array describing the exporter at index %s.', 353 self::$exporter_key 354 ), 355 $this->_last_response_parsed['data'] 356 ); 357 } 358 359 /** 360 * Change the exporter information to an invalid value. 361 * 362 * @since 4.9.9 363 * 364 * @param array $exporters Exporters. 365 * @return array $exporters Exporters. 366 */ 367 public function filter_exporter_not_array( $exporters ) { 368 $exporters[ self::$exporter_key ] = false; 369 370 return $exporters; 371 } 372 373 /** 374 * The function should send an error when an exporter is missing a friendly name. 375 * 376 * @since 4.9.9 377 */ 378 public function test_error_when_exporter_missing_friendly_name() { 379 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_friendly_name' ), 20 ); 380 $this->_make_ajax_call(); 381 382 $this->assertFalse( $this->_last_response_parsed['success'] ); 383 $this->assertSame( 384 sprintf( 385 'Exporter array at index %s does not include a friendly name.', 386 self::$exporter_key 387 ), 388 $this->_last_response_parsed['data'] 389 ); 390 } 391 392 /** 393 * Remove the custom exporter's friendly name. 394 * 395 * @since 4.9.9 396 * 397 * @param array $exporters Exporters. 398 * @return array $exporters Exporters. 399 */ 400 public function filter_exporter_missing_friendly_name( $exporters ) { 401 unset( $exporters[ self::$exporter_key ]['exporter_friendly_name'] ); 402 403 return $exporters; 404 } 405 406 /** 407 * The function should send an error when an exporter is missing a callback. 408 * 409 * @since 4.9.9 410 */ 411 public function test_error_when_exporter_missing_callback() { 412 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_callback' ), 20 ); 413 $this->_make_ajax_call(); 414 415 $this->assertFalse( $this->_last_response_parsed['success'] ); 416 $this->assertSame( 417 sprintf( 418 'Exporter does not include a callback: %s.', 419 self::$exporter_friendly_name 420 ), 421 $this->_last_response_parsed['data'] 422 ); 423 } 424 425 /** 426 * Remove the custom exporter's callback function. 427 * 428 * @since 4.9.9 429 * 430 * @param array $exporters Exporters. 431 * @return array $exporters Exporters. 432 */ 433 public function filter_exporter_missing_callback( $exporters ) { 434 unset( $exporters[ self::$exporter_key ]['callback'] ); 435 436 return $exporters; 437 } 438 439 /** 440 * The function should send an error when an exporter, at a given index, has an invalid callback. 441 * 442 * @since 4.9.9 443 */ 444 public function test_error_when_exporter_index_invalid_callback() { 445 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_index_invalid_callback' ), 20 ); 446 $this->_make_ajax_call(); 447 448 $this->assertFalse( $this->_last_response_parsed['success'] ); 449 $this->assertSame( 450 sprintf( 451 'Exporter callback is not a valid callback: %s.', 452 self::$exporter_friendly_name 453 ), 454 $this->_last_response_parsed['data'] 455 ); 456 } 457 458 /** 459 * Change the exporter callback to be invalid. 460 * 461 * @since 4.9.9 462 * 463 * @param array $exporters Exporters. 464 * @return array $exporters Exporters. 465 */ 466 public function filter_exporter_index_invalid_callback( $exporters ) { 467 $exporters[ self::$exporter_key ]['callback'] = false; 468 469 return $exporters; 470 } 471 472 /** 473 * When an exporter callback returns a WP_Error, it should be passed as the error. 474 * 475 * @since 4.9.9 476 */ 477 public function test_error_when_exporter_callback_returns_wp_error() { 478 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_index_callback_returns_wp_error' ), 20 ); 479 $this->_make_ajax_call(); 480 481 $this->assertFalse( $this->_last_response_parsed['success'] ); 482 $this->assertSame( 'passed_message', $this->_last_response_parsed['data'][0]['code'] ); 483 $this->assertSame( 'This is a WP_Error message.', $this->_last_response_parsed['data'][0]['message'] ); 484 } 485 486 /** 487 * Filter the exporter callback to a function that returns a WP_Error. 488 * 489 * @since 4.9.9 490 * 491 * @param array $exporters Exporters. 492 * @return array $exporters Exporters. 493 */ 494 public function filter_exporter_index_callback_returns_wp_error( $exporters ) { 495 $exporters[ self::$exporter_key ]['callback'] = array( $this, 'callback_return_wp_error' ); 496 497 return $exporters; 498 } 499 500 /** 501 * Callback for exporter's response. 502 * 503 * @since 4.9.9 504 * 505 * @param string $email_address The requester's email address. 506 * @param int $page Page number. 507 * @return WP_Error WP_Error instance. 508 */ 509 public function callback_return_wp_error( $email_address, $page = 1 ) { 510 return new WP_Error( 'passed_message', 'This is a WP_Error message.' ); 511 } 512 513 /** 514 * The function should send an error when an exporter, at a given index, is missing an array response. 515 * 516 * @since 4.9.9 517 */ 518 public function test_error_when_exporter_index_invalid_response() { 519 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_index_invalid_response' ), 20 ); 520 $this->_make_ajax_call(); 521 522 $this->assertFalse( $this->_last_response_parsed['success'] ); 523 $this->assertSame( 524 sprintf( 525 'Expected response as an array from exporter: %s.', 526 self::$exporter_friendly_name 527 ), 528 $this->_last_response_parsed['data'] 529 ); 530 } 531 532 /** 533 * Change the exporter callback response to an invalid value. 534 * 535 * @since 4.9.9 536 * 537 * @param array $exporters Exporters. 538 * @return array $exporters Exporters. 539 */ 540 public function filter_exporter_index_invalid_response( $exporters ) { 541 $exporters[ self::$exporter_key ]['callback'] = '__return_null'; 542 543 return $exporters; 544 } 545 546 /** 547 * The function should send an error when an exporter is missing data in array response. 548 * 549 * @since 4.9.9 550 */ 551 public function test_error_when_exporter_missing_data_response() { 552 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_data_response' ), 20 ); 553 $this->_make_ajax_call(); 554 555 $this->assertFalse( $this->_last_response_parsed['success'] ); 556 $this->assertSame( 557 sprintf( 558 'Expected data in response array from exporter: %s.', 559 self::$exporter_friendly_name 560 ), 561 $this->_last_response_parsed['data'] 562 ); 563 } 564 565 /** 566 * Change the exporter response callback to one that returns an invalid data response value. 567 * 568 * @since 4.9.9 569 * 570 * @param array $exporters Exporters. 571 * @return array $exporters Exporters. 572 */ 573 public function filter_exporter_missing_data_response( $exporters ) { 574 $exporters[ self::$exporter_key ]['callback'] = array( $this, 'callback_missing_data_response' ); 575 return $exporters; 576 } 577 578 /** 579 * Callback for exporter's response. 580 * 581 * @since 4.9.9 582 * 583 * @param string $email_address The requester's email address. 584 * @param int $page Page number. 585 * @return array $return Export data. 586 */ 587 public function callback_missing_data_response( $email_address, $page = 1 ) { 588 $response = $this->callback_custom_personal_data_exporter( $email_address, $page ); 589 unset( $response['data'] ); // Missing data part of response. 590 591 return $response; 592 } 593 594 /** 595 * The function should send an error when an exporter is missing 'data' array in array response. 596 * 597 * @since 4.9.9 598 */ 599 public function test_function_should_error_when_exporter_missing_data_array_response() { 600 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_data_array_response' ), 20 ); 601 $this->_make_ajax_call(); 602 remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_data_array_response' ), 20 ); 603 604 $this->assertFalse( $this->_last_response_parsed['success'] ); 605 $this->assertSame( 606 sprintf( 607 'Expected data array in response array from exporter: %s.', 608 self::$exporter_friendly_name 609 ), 610 $this->_last_response_parsed['data'] 611 ); 612 } 613 614 /** 615 * Filter the array of exporters. 616 * 617 * @since 4.9.9 618 * 619 * @param array $exporters Exporters. 620 * 621 * @return array $exporters Exporters. 622 */ 623 public function filter_exporter_missing_data_array_response( $exporters ) { 624 $exporters[ self::$exporter_key ]['callback'] = array( $this, 'callback_missing_data_array_response' ); 625 return $exporters; 626 } 627 628 /** 629 * Callback for exporter's response. 630 * 631 * @since 4.9.9 632 * 633 * @param string $email_address The requester's email address. 634 * @param int $page Page number. 635 * 636 * @return array $return Export data. 637 */ 638 public function callback_missing_data_array_response( $email_address, $page = 1 ) { 639 $response = $this->callback_custom_personal_data_exporter( $email_address, $page ); 640 $response['data'] = false; // Not an array. 641 return $response; 642 } 643 644 /** 645 * The function should send an error when an exporter is missing 'done' in array response. 646 * 647 * @since 4.9.9 648 */ 649 public function test_error_when_exporter_missing_done_response() { 650 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_missing_done_response' ), 20 ); 651 $this->_make_ajax_call(); 652 653 $this->assertFalse( $this->_last_response_parsed['success'] ); 654 $this->assertSame( 655 sprintf( 656 'Expected done (boolean) in response array from exporter: %s.', 657 self::$exporter_friendly_name 658 ), 659 $this->_last_response_parsed['data'] 660 ); 661 } 662 663 /** 664 * Filter the array of exporters. 665 * 666 * @since 4.9.9 667 * 668 * @param array $exporters Exporters. 669 * @return array $exporters Exporters. 670 */ 671 public function filter_exporter_missing_done_response( $exporters ) { 672 $exporters[ self::$exporter_key ]['callback'] = array( $this, 'callback_missing_done_response' ); 673 674 return $exporters; 675 } 676 677 /** 678 * Remove the response's done flag. 679 * 680 * @since 4.9.9 681 * 682 * @param string $email_address The requester's email address. 683 * @param int $page Page number. 684 * @return array $return Export data. 685 */ 686 public function callback_missing_done_response( $email_address, $page = 1 ) { 687 $response = $this->callback_custom_personal_data_exporter( $email_address, $page ); 688 unset( $response['done'] ); 689 690 return $response; 691 } 692 693 /** 694 * The function should successfully send exporter data response when the current user has the required capability. 695 * 696 * @since 4.9.9 697 */ 698 public function test_succeeds_when_current_user_has_required_capability() { 699 $this->_make_ajax_call(); 700 701 $this->assertTrue( $this->_last_response_parsed['success'] ); 702 $this->assertTrue( current_user_can( 'export_others_personal_data' ) ); 703 $this->assertSame( 'custom-exporter-item-id', $this->_last_response_parsed['data']['data']['item_id'] ); 704 $this->assertSame( 'Email', $this->_last_response_parsed['data']['data']['data'][0]['name'] ); 705 $this->assertSame( self::$request_email, $this->_last_response_parsed['data']['data']['data'][0]['value'] ); 706 } 707 708 /** 709 * The function's output should be filterable with the `wp_privacy_personal_data_export_page` filter. 710 * 711 * @since 4.9.9 712 */ 713 public function test_output_should_be_filterable() { 714 add_filter( 'wp_privacy_personal_data_export_page', array( $this, 'filter_exporter_data_response' ), 20, 7 ); 715 $this->_make_ajax_call(); 716 717 $expected_group_label = sprintf( 718 '%s-%s-%s-%s-%s-%s', 719 self::$exporter, 720 self::$page, 721 self::$request_email, 722 self::$request_id, 723 self::$send_as_email, 724 self::$exporter_key 725 ); 726 727 $this->assertTrue( $this->_last_response_parsed['success'] ); 728 $this->assertSame( $expected_group_label, $this->_last_response_parsed['data']['group_label'] ); 729 $this->assertSame( 'filtered_group_id', $this->_last_response_parsed['data']['group_id'] ); 730 $this->assertSame( 'filtered_item_id', $this->_last_response_parsed['data']['item_id'] ); 731 $this->assertSame( 'filtered_name', $this->_last_response_parsed['data']['data'][0]['name'] ); 732 $this->assertSame( 'filtered_value', $this->_last_response_parsed['data']['data'][0]['value'] ); 733 } 734 735 /** 736 * Filter exporter's data response. 737 * 738 * @since 4.9.9 739 * 740 * @param array $response The personal data for the given exporter and page. 741 * @param int $exporter_index The index of the exporter that provided this data. 742 * @param string $email_address The email address associated with this personal data. 743 * @param int $page The page for this response. 744 * @param int $request_id The privacy request post ID associated with this request. 745 * @param bool $send_as_email Whether the final results of the export should be emailed to the user. 746 * @param string $exporter_key The key (slug) of the exporter that provided this data. 747 * 748 * @return array $response The personal data for the given exporter and page. 749 */ 750 public function filter_exporter_data_response( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { 751 $group_label = sprintf( 752 '%s-%s-%s-%s-%s-%s', 753 $exporter_index, 754 $page, 755 $email_address, 756 $request_id, 757 $send_as_email, 758 $exporter_key 759 ); 760 $response['group_label'] = $group_label; 761 $response['group_id'] = 'filtered_group_id'; 762 $response['item_id'] = 'filtered_item_id'; 763 $response['data'][0]['name'] = 'filtered_name'; 764 $response['data'][0]['value'] = 'filtered_value'; 765 766 return $response; 767 } 768 769 /** 770 * Filter to register a custom personal data exporter. 771 * 772 * @since 4.9.9 773 * 774 * @param array $exporters An array of personal data exporters. 775 * @return array $exporters An array of personal data exporters. 776 */ 777 public function filter_register_custom_personal_data_exporter( $exporters ) { 778 $exporters[ self::$exporter_key ] = array( 779 'exporter_friendly_name' => self::$exporter_friendly_name, 780 'callback' => array( $this, 'callback_custom_personal_data_exporter' ), 781 ); 782 return $exporters; 783 } 784 785 /** 786 * Callback for a custom personal data exporter. 787 * 788 * @since 4.9.9 789 * 790 * @param string $email_address The requester's email address. 791 * @param int $page Page number. 792 * 793 * @return array $response Export data response. 794 */ 795 public function callback_custom_personal_data_exporter( $email_address, $page = 1 ) { 796 $data_to_export = array(); 797 798 if ( 1 === $page ) { 799 $data_to_export = array( 800 'group_id' => self::$exporter_key . '-group-id', 801 'group_label' => self::$exporter_key . '-group-label', 802 'item_id' => self::$exporter_key . '-item-id', 803 'data' => array( 804 array( 805 'name' => 'Email', 806 'value' => $email_address, 807 ), 808 ), 809 ); 810 } 811 812 return array( 813 'data' => $data_to_export, 814 'done' => true, 815 ); 816 } 817 818 /** 819 * Helper function for ajax handler. 820 * 821 * @since 4.9.9 822 * 823 * @param array $args Ajax request arguments. 824 */ 825 protected function _make_ajax_call( $args = array() ) { 826 $this->_last_response_parsed = null; 827 $this->_last_response = ''; 828 829 $defaults = array( 830 'action' => self::$action, 831 'security' => wp_create_nonce( self::$action . '-' . self::$request_id ), 832 'exporter' => self::$exporter, 833 'page' => self::$page, 834 'sendAsEmail' => self::$send_as_email, 835 'id' => self::$request_id, 836 ); 837 838 $_POST = wp_parse_args( $args, $defaults ); 839 840 try { 841 $this->_handleAjax( self::$action ); 842 } catch ( WPAjaxDieContinueException $e ) { 843 unset( $e ); 844 } 845 846 if ( $this->_last_response ) { 847 $this->_last_response_parsed = json_decode( $this->_last_response, true ); 848 } 849 } 850 }