Make WordPress Core

Ticket #43438: 43438.16.diff

File 43438.16.diff, 49.0 KB (added by birgire, 7 years ago)
  • src/wp-admin/includes/ajax-actions.php

    diff --git src/wp-admin/includes/ajax-actions.php src/wp-admin/includes/ajax-actions.php
    index f3c5428..05c49af 100644
    function wp_ajax_wp_privacy_export_personal_data() { 
    44294429                }
    44304430
    44314431                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.' ) );
    44334433                }
    44344434
    44354435                if ( $page < 1 ) {
    function wp_ajax_wp_privacy_export_personal_data() { 
    44524452                                sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
    44534453                        );
    44544454                }
     4455
     4456                $exporter_friendly_name = $exporter['exporter_friendly_name'];
     4457
    44554458                if ( ! array_key_exists( 'callback', $exporter ) ) {
    44564459                        wp_send_json_error(
    44574460                                /* 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 ) )
    44594462                        );
    44604463                }
    44614464                if ( ! is_callable( $exporter['callback'] ) ) {
    44624465                        wp_send_json_error(
    44634466                                /* 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 ) )
    44654468                        );
    44664469                }
    44674470
    4468                 $callback               = $exporter['callback'];
    4469                 $exporter_friendly_name = $exporter['exporter_friendly_name'];
    4470 
     4471                $callback = $exporter['callback'];
    44714472                $response = call_user_func( $callback, $email_address, $page );
     4473
    44724474                if ( is_wp_error( $response ) ) {
    44734475                        wp_send_json_error( $response );
    44744476                }
    function wp_ajax_wp_privacy_erase_personal_data() { 
    45594561        $request = wp_get_user_request_data( $request_id );
    45604562
    45614563        if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
    4562                 wp_send_json_error( __( 'Invalid request ID.' ) );
     4564                wp_send_json_error( __( 'Invalid request type.' ) );
    45634565        }
    45644566
    45654567        $email_address = $request->email;
    function wp_ajax_wp_privacy_erase_personal_data() { 
    46264628                        wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
    46274629                }
    46284630
    4629                 if ( ! array_key_exists( 'callback', $eraser ) ) {
    4630                         /* translators: %d: array index */
    4631                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );
    4632                 }
    4633 
    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 ) );
    4637                 }
    4638 
    46394631                if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    46404632                        /* translators: %d: array index */
    46414633                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
    46424634                }
    46434635
    4644                 $callback             = $eraser['callback'];
    46454636                $eraser_friendly_name = $eraser['eraser_friendly_name'];
    46464637
     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                        );
     4646                }
     4647
     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                        );
     4656                }
     4657
     4658                $callback = $eraser['callback'];
    46474659                $response = call_user_func( $callback, $email_address, $page );
    46484660
    46494661                if ( is_wp_error( $response ) ) {
  • tests/phpunit/includes/testcase-ajax.php

    diff --git tests/phpunit/includes/testcase-ajax.php tests/phpunit/includes/testcase-ajax.php
    index bf5a5bc..736d362 100644
    abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase { 
    119119                'delete-theme',
    120120                'install-theme',
    121121                'get-post-thumbnail-html',
     122                'wp-privacy-export-personal-data',
     123                'wp-privacy-erase-personal-data',
    122124        );
    123125
    124126        public static function setUpBeforeClass() {
  • new file tests/phpunit/tests/ajax/PrivacyErasePersonalData.php

    diff --git tests/phpunit/tests/ajax/PrivacyErasePersonalData.php tests/phpunit/tests/ajax/PrivacyErasePersonalData.php
    new file mode 100644
    index 0000000..3533fc2
    - +  
     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 */
     19class 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 $key_to_unset
     98         */
     99        protected $key_to_unset;
     100
     101        /**
     102         * A value to change the test exporter callback to.
     103         *
     104         * @since 4.9.9
     105         *
     106         * @var string $new_callback_value
     107         */
     108        protected $new_callback_value;
     109
     110        /**
     111         * Create user erase request fixtures.
     112         *
     113         * @param WP_UnitTest_Factory $factory Factory.
     114         */
     115        public static function wpSetUpBeforeClass( $factory ) {
     116                self::$request_email        = 'requester@example.com';
     117                self::$request_id           = wp_create_user_request( self::$request_email, 'remove_personal_data' );
     118                self::$action               = 'wp-privacy-erase-personal-data';
     119                self::$eraser               = 1;
     120                self::$eraser_key           = 'custom-eraser';
     121                self::$eraser_friendly_name = 'Custom Eraser';
     122                self::$page                 = 1;
     123        }
     124
     125        /**
     126         * Register a custom personal data eraser.
     127         */
     128        public function setUp() {
     129                parent::setUp();
     130
     131                $this->key_to_unset = '';
     132
     133                // Make sure the erasers response is not modified and avoid sending emails.
     134                remove_all_filters( 'wp_privacy_personal_data_erasure_page' );
     135                remove_all_actions( 'wp_privacy_personal_data_erased' );
     136
     137                // Only use our custom privacy personal data eraser.
     138                remove_all_filters( 'wp_privacy_personal_data_erasers' );
     139                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
     140
     141                $this->_setRole( 'administrator' );
     142        }
     143
     144        /**
     145         * Clean up after each test method.
     146         */
     147        public function tearDown() {
     148                remove_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
     149                $this->new_callback_value = '';
     150
     151                parent::tearDown();
     152        }
     153
     154        /**
     155         * Unsets an array index in the test eraser.
     156         *
     157         * @since 4.9.9
     158         *
     159         * @param array $erasers Erasers.
     160         * @return array $erasers Erasers.
     161         */
     162        public function filter_unset_eraser_index( $erasers ) {
     163                if ( ! empty( $this->key_to_unset ) ) {
     164                        unset( $erasers[ self::$eraser_key ][ $this->key_to_unset ] );
     165                }
     166
     167                return $erasers;
     168        }
     169
     170        /**
     171         * Change the test eraser callback to a specified value.
     172         *
     173         * @since 4.9.9
     174         *
     175         * @param array $erasers List of data erasers.
     176         * @return array $erasersList of data erasers.
     177         */
     178        public function filter_eraser_callback_value( $erasers ) {
     179                $erasers[ self::$eraser_key ]['callback'] = $this->new_callback_value;
     180
     181                return $erasers;
     182        }
     183
     184        /**
     185         * The function should send an error when the request ID is missing.
     186         *
     187         * @since 4.9.9
     188         *
     189         * @ticket 43438
     190         */
     191        public function test_error_when_missing_request_id() {
     192                $this->assertNotWPError( self::$request_id );
     193
     194                // Set up a request.
     195                $this->_make_ajax_call(
     196                        array(
     197                                'id' => null, // Missing request ID.
     198                        )
     199                );
     200
     201                $this->assertFalse( $this->_last_response_parsed['success'] );
     202                $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
     203        }
     204
     205        /**
     206         * The function should send an error when the request ID is less than 1.
     207         *
     208         * @since 4.9.9
     209         *
     210         * @ticket 43438
     211         */
     212        public function test_error_when_request_id_invalid() {
     213                $this->assertNotWPError( self::$request_id );
     214
     215                // Set up a request.
     216                $this->_make_ajax_call(
     217                        array(
     218                                'id' => -1, // Invalid request ID.
     219                        )
     220                );
     221
     222                $this->assertFalse( $this->_last_response_parsed['success'] );
     223                $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
     224        }
     225
     226        /**
     227         * The function should send an error when the current user is missing required capabilities.
     228         *
     229         * @since 4.9.9
     230         *
     231         * @ticket 43438
     232         */
     233        public function test_error_when_current_user_missing_required_capabilities() {
     234                $this->_setRole( 'author' );
     235
     236                $this->assertFalse( current_user_can( 'erase_others_personal_data' ) );
     237                $this->assertFalse( current_user_can( 'delete_users' ) );
     238
     239                $this->_make_ajax_call();
     240
     241                $this->assertFalse( $this->_last_response_parsed['success'] );
     242                $this->assertSame( 'Invalid request.', $this->_last_response_parsed['data'] );
     243        }
     244
     245        /**
     246         * The function should send an error when the nonce does not validate.
     247         *
     248         * @since 4.9.9
     249         */
     250        public function test_failure_with_invalid_nonce() {
     251                $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
     252
     253                $this->_make_ajax_call(
     254                        array(
     255                                'security' => 'invalid-nonce',
     256                        )
     257                );
     258        }
     259
     260        /**
     261         * The function should send an error when the request type is incorrect.
     262         *
     263         * @since 4.9.9
     264         */
     265        public function test_error_when_incorrect_request_type() {
     266                $request_id = wp_create_user_request(
     267                        'export-request@example.com',
     268                        'export_personal_data' // Incorrect request type, expects 'remove_personal_data'.
     269                );
     270
     271                $this->_make_ajax_call(
     272                        array(
     273                                'security' => wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ),
     274                                'id'       => $request_id,
     275                        )
     276                );
     277
     278                $this->assertFalse( $this->_last_response_parsed['success'] );
     279                $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
     280        }
     281
     282        /**
     283         * The function should send an error when the request email is invalid.
     284         *
     285         * @since 4.9.9
     286         */
     287        public function test_error_when_invalid_email() {
     288                wp_update_post(
     289                        array(
     290                                'ID'         => self::$request_id,
     291                                'post_title' => '', // Invalid requester's email address.
     292                        )
     293                );
     294
     295                $this->_make_ajax_call();
     296
     297                $this->assertFalse( $this->_last_response_parsed['success'] );
     298                $this->assertSame( 'Invalid email address in request.', $this->_last_response_parsed['data'] );
     299        }
     300
     301        /**
     302         * The function should send an error when the eraser index is missing.
     303         *
     304         * @since 4.9.9
     305         */
     306        public function test_error_when_missing_eraser_index() {
     307                $this->_make_ajax_call(
     308                        array(
     309                                'eraser' => null, // Missing eraser index.
     310                        )
     311                );
     312
     313                $this->assertFalse( $this->_last_response_parsed['success'] );
     314                $this->assertSame( 'Missing eraser index.', $this->_last_response_parsed['data'] );
     315        }
     316
     317        /**
     318         * The function should send an error when the page index is missing.
     319         *
     320         * @since 4.9.9
     321         */
     322        public function test_error_when_missing_page_index() {
     323                $this->_make_ajax_call(
     324                        array(
     325                                'page' => null, // Missing page index.
     326                        )
     327                );
     328
     329                $this->assertFalse( $this->_last_response_parsed['success'] );
     330                $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
     331        }
     332
     333        /**
     334         * The function should send an error when the eraser index is negative.
     335         *
     336         * @since 4.9.9
     337         */
     338        public function test_error_when_negative_eraser_index() {
     339                $this->_make_ajax_call(
     340                        array(
     341                                'eraser' => -1, // Negative eraser index.
     342                        )
     343                );
     344
     345                $this->assertFalse( $this->_last_response_parsed['success'] );
     346                $this->assertSame( 'Eraser index cannot be less than one.', $this->_last_response_parsed['data'] );
     347        }
     348
     349        /**
     350         * The function should send an error when the eraser index is out of range.
     351         *
     352         * @since 4.9.9
     353         */
     354        public function test_error_when_eraser_index_out_of_range() {
     355                $this->_make_ajax_call(
     356                        array(
     357                                'eraser' => PHP_INT_MAX, // Out of range eraser index.
     358                        )
     359                );
     360
     361                $this->assertFalse( $this->_last_response_parsed['success'] );
     362                $this->assertSame( 'Eraser index is out of range.', $this->_last_response_parsed['data'] );
     363        }
     364
     365        /**
     366         * The function should send an error when the page index is less than one.
     367         *
     368         * @since 4.9.9
     369         */
     370        public function test_error_when_page_index_less_than_one() {
     371                $this->_make_ajax_call(
     372                        array(
     373                                'page' => 0, // Page index less than one.
     374                        )
     375                );
     376
     377                $this->assertFalse( $this->_last_response_parsed['success'] );
     378                $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
     379        }
     380
     381        /**
     382         * The function should send an error when an eraser is not an array.
     383         *
     384         * @since 4.9.9
     385         */
     386        public function test_error_when_eraser_not_array() {
     387                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_not_array' ), 20 );
     388                $this->_make_ajax_call();
     389
     390                $this->assertFalse( $this->_last_response_parsed['success'] );
     391                $this->assertSame(
     392                        sprintf(
     393                                'Expected an array describing the eraser at index %s.',
     394                                self::$eraser
     395                        ),
     396                        $this->_last_response_parsed['data']
     397                );
     398        }
     399
     400        /**
     401         * Change the eraser information to an invalid value.
     402         *
     403         * @since 4.9.9
     404         *
     405         * @param array $erasers Erasers.
     406         * @return array $erasers Erasers.
     407         */
     408        public function filter_eraser_not_array( $erasers ) {
     409                $erasers[ self::$eraser_key ] = false;
     410
     411                return $erasers;
     412        }
     413
     414        /**
     415         * The function should send an error when an eraser is missing a friendly name.
     416         *
     417         * @since 4.9.9
     418         */
     419        public function test_error_when_eraser_missing_friendly_name() {
     420                $this->key_to_unset = 'eraser_friendly_name';
     421
     422                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 );
     423                $this->_make_ajax_call();
     424
     425                $this->assertFalse( $this->_last_response_parsed['success'] );
     426                $this->assertSame(
     427                        sprintf(
     428                                'Eraser array at index %s does not include a friendly name.',
     429                                self::$eraser
     430                        ),
     431                        $this->_last_response_parsed['data']
     432                );
     433        }
     434
     435        /**
     436         * The function should send an error when an eraser is missing a callback.
     437         *
     438         * @since 4.9.9
     439         */
     440        public function test_error_when_eraser_missing_callback() {
     441                $this->key_to_unset = 'callback';
     442
     443                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 );
     444                $this->_make_ajax_call();
     445
     446                $this->assertFalse( $this->_last_response_parsed['success'] );
     447                $this->assertSame(
     448                        sprintf(
     449                                'Eraser does not include a callback: %s.',
     450                                self::$eraser_friendly_name
     451                        ),
     452                        $this->_last_response_parsed['data']
     453                );
     454        }
     455
     456        /**
     457         * The function should send an error when an eraser, at a given index, has an invalid callback.
     458         *
     459         * @since 4.9.9
     460         */
     461        public function test_error_when_eraser_index_invalid_callback() {
     462                $this->new_callback_value = false;
     463
     464                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_callback_value' ), 20 );
     465                $this->_make_ajax_call();
     466
     467                $this->assertFalse( $this->_last_response_parsed['success'] );
     468                $this->assertSame(
     469                        sprintf(
     470                                'Eraser callback is not valid: %s.',
     471                                self::$eraser_friendly_name
     472                        ),
     473                        $this->_last_response_parsed['data']
     474                );
     475        }
     476
     477        /**
     478         * The function should send an error when an eraser, at a given index, is missing an array response.
     479         *
     480         * @since 4.9.9
     481         */
     482        public function test_error_when_eraser_index_invalid_response() {
     483                $this->new_callback_value = '__return_null';
     484
     485                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_callback_value' ), 20 );
     486                $this->_make_ajax_call();
     487
     488                $this->assertFalse( $this->_last_response_parsed['success'] );
     489                $this->assertSame(
     490                        sprintf(
     491                                'Did not receive array from %1$s eraser (index %2$d).',
     492                                self::$eraser_friendly_name,
     493                                self::$eraser
     494                        ),
     495                        $this->_last_response_parsed['data']
     496                );
     497        }
     498
     499        /**
     500         * Change the test eraser's callback to one that unsets an index in the response.
     501         *
     502         * @since 4.9.9
     503         *
     504         * @param array $erasers Erasers.
     505         * @return array $erasers Erasers.
     506         */
     507        public function filter_callback_unset_index( $erasers ) {
     508                $erasers[ self::$eraser_key ]['callback'] = array( $this, 'filter_unset_response_index' );
     509
     510                return $erasers;
     511        }
     512
     513        /**
     514         * Unsets an array index in a response.
     515         *
     516         * @since 4.9.9
     517         *
     518         * @param string $email_address The requester's email address.
     519         * @param int    $page          Page number.
     520         * @return array $return Export data.
     521         */
     522        public function filter_unset_response_index( $email_address, $page = 1 ) {
     523                $response = $this->callback_personal_data_eraser( $email_address, $page );
     524
     525                if ( ! empty( $this->key_to_unset ) ) {
     526                        unset( $response[ $this->key_to_unset ] );
     527                }
     528
     529                return $response;
     530        }
     531
     532        /**
     533         * The function should send an error when missing an items_removed index.
     534         *
     535         * @since 4.9.9
     536         */
     537        public function test_error_when_eraser_items_removed_missing() {
     538                $this->key_to_unset = 'items_removed';
     539
     540                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 );
     541                $this->_make_ajax_call();
     542
     543                $this->assertFalse( $this->_last_response_parsed['success'] );
     544                $this->assertSame(
     545                        sprintf(
     546                                'Expected items_removed key in response array from %1$s eraser (index %2$d).',
     547                                self::$eraser_friendly_name,
     548                                self::$eraser
     549                        ),
     550                        $this->_last_response_parsed['data']
     551                );
     552        }
     553
     554        /**
     555         * The function should send an error when missing an items_retained index.
     556         *
     557         * @since 4.9.9
     558         */
     559        public function test_error_when_eraser_items_retained_missing() {
     560                $this->key_to_unset = 'items_retained';
     561
     562                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 );
     563                $this->_make_ajax_call();
     564
     565                $this->assertFalse( $this->_last_response_parsed['success'] );
     566                $this->assertSame(
     567                        sprintf(
     568                                'Expected items_retained key in response array from %1$s eraser (index %2$d).',
     569                                self::$eraser_friendly_name,
     570                                self::$eraser
     571                        ),
     572                        $this->_last_response_parsed['data']
     573                );
     574        }
     575
     576        /**
     577         * The function should send an error when missing a messages index.
     578         *
     579         * @since 4.9.9
     580         */
     581        public function test_error_when_eraser_messages_missing() {
     582                $this->key_to_unset = 'messages';
     583
     584                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 );
     585                $this->_make_ajax_call();
     586
     587                $this->assertFalse( $this->_last_response_parsed['success'] );
     588                $this->assertSame(
     589                        sprintf(
     590                                'Expected messages key in response array from %1$s eraser (index %2$d).',
     591                                self::$eraser_friendly_name,
     592                                self::$eraser
     593                        ),
     594                        $this->_last_response_parsed['data']
     595                );
     596        }
     597
     598        /**
     599         * The function should send an error when the messages index is not an array.
     600         *
     601         * @since 4.9.9
     602         */
     603        public function test_error_when_eraser_messages_not_array() {
     604                $this->new_callback_value = array( $this, 'filter_response_messages_invalid' );
     605
     606                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_callback_value' ), 20 );
     607                $this->_make_ajax_call();
     608
     609                $this->assertFalse( $this->_last_response_parsed['success'] );
     610                $this->assertSame(
     611                        sprintf(
     612                                'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).',
     613                                self::$eraser_friendly_name,
     614                                self::$eraser
     615                        ),
     616                        $this->_last_response_parsed['data']
     617                );
     618        }
     619
     620        /**
     621         * Change the messages index to an invalid value (not an array).
     622         *
     623         * @since 4.9.9
     624         *
     625         * @param string $email_address The requester's email address.
     626         * @param int    $page          Page number.
     627         * @return array $return Export data.
     628         */
     629        public function filter_response_messages_invalid( $email_address, $page = 1 ) {
     630                $response             = $this->callback_personal_data_eraser( $email_address, $page );
     631                $response['messages'] = true;
     632
     633                return $response;
     634        }
     635
     636        /**
     637         * The function should send an error when an eraser is missing 'done' in array response.
     638         *
     639         * @since 4.9.9
     640         */
     641        public function test_error_when_eraser_missing_done_response() {
     642                $this->key_to_unset = 'done';
     643
     644                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_callback_unset_index' ), 20 );
     645                $this->_make_ajax_call();
     646
     647                $this->assertFalse( $this->_last_response_parsed['success'] );
     648                $this->assertSame(
     649                        sprintf(
     650                                'Expected done flag in response array from %1$s eraser (index %2$d).',
     651                                self::$eraser_friendly_name,
     652                                self::$eraser
     653                        ),
     654                        $this->_last_response_parsed['data']
     655                );
     656        }
     657
     658        /**
     659         * The function should successfully send erasers response data when the current user has the required
     660         * capabilities.
     661         *
     662         * @since 4.9.9
     663         *
     664         * @ticket 43438
     665         */
     666        public function test_success_when_current_user_has_required_capabilities() {
     667                $this->assertTrue( current_user_can( 'erase_others_personal_data' ) );
     668                $this->assertTrue( current_user_can( 'delete_users' ) );
     669
     670                $this->_make_ajax_call();
     671
     672                $this->assertSame( 'A message regarding retained data for requester@example.com.', $this->_last_response_parsed['data']['messages'][0] );
     673                $this->assertTrue( $this->_last_response_parsed['success'] );
     674                $this->assertTrue( $this->_last_response_parsed['data']['items_removed'] );
     675                $this->assertTrue( $this->_last_response_parsed['data']['items_retained'] );
     676                $this->assertTrue( $this->_last_response_parsed['data']['done'] );
     677        }
     678
     679        /**
     680         * Test that the function's output should be filterable with the `wp_privacy_personal_data_erasure_page` filter.
     681         *
     682         * @since 4.9.9
     683         */
     684        public function test_output_should_be_filterable() {
     685                add_filter( 'wp_privacy_personal_data_erasure_page', array( $this, 'filter_exporter_data_response' ), 20, 6 );
     686                $this->_make_ajax_call();
     687
     688                $expected_new_index = self::$request_email . '-' . self::$request_id . '-' . self::$eraser_key;
     689
     690                $this->assertTrue( $this->_last_response_parsed['success'] );
     691                $this->assertSame( 'filtered removed', $this->_last_response_parsed['data']['items_removed'] );
     692                $this->assertSame( 'filtered retained', $this->_last_response_parsed['data']['items_retained'] );
     693                $this->assertSame( array( 'filtered messages' ), $this->_last_response_parsed['data']['messages'] );
     694                $this->assertSame( 'filtered done', $this->_last_response_parsed['data']['done'] );
     695                $this->assertSame( $expected_new_index, $this->_last_response_parsed['data']['new_index'] );
     696        }
     697
     698        /**
     699         * Filters the exporter response.
     700         *
     701         * @since 4.9.9
     702         *
     703         * @param array  $response        The personal data for the given exporter and page.
     704         * @param int    $eraser_index    The index of the eraser that provided this data.
     705         * @param string $email_address   The email address associated with this personal data.
     706         * @param int    $page            The page for this response.
     707         * @param int    $request_id      The privacy request post ID associated with this request.
     708         * @param string $eraser_key      The key (slug) of the eraser that provided this data.
     709         * @return array Filtered export response.
     710         */
     711        public function filter_exporter_data_response( $response, $eraser_index, $email_address, $page, $request_id, $eraser_key ) {
     712                $response['items_removed']  = 'filtered removed';
     713                $response['items_retained'] = 'filtered retained';
     714                $response['messages']       = array( 'filtered messages' );
     715                $response['done']           = 'filtered done';
     716                $response['new_index']      = $email_address . '-' . $request_id . '-' . $eraser_key;
     717
     718                return $response;
     719        }
     720
     721        /**
     722         * Register handler for a custom personal data eraser.
     723         *
     724         * @since 4.9.9
     725         *
     726         * @param array $erasers An array of personal data erasers.
     727         * @return array $erasers An array of personal data erasers.
     728         */
     729        public function register_custom_personal_data_eraser( $erasers ) {
     730                $erasers[ self::$eraser_key ] = array(
     731                        'eraser_friendly_name' => self::$eraser_friendly_name,
     732                        'callback'             => array( $this, 'callback_personal_data_eraser' ),
     733                );
     734                return $erasers;
     735        }
     736
     737        /**
     738         * Custom Personal Data Eraser.
     739         *
     740         * @since 4.9.9
     741         *
     742         * @param  string $email_address The comment author email address.
     743         * @param  int    $page          Page number.
     744         * @return array  $return        Erase data.
     745         */
     746        public function callback_personal_data_eraser( $email_address, $page = 1 ) {
     747                if ( 1 === $page ) {
     748                        return array(
     749                                'items_removed'  => true,
     750                                'items_retained' => true,
     751                                'messages'       => array( sprintf( 'A message regarding retained data for %s.', $email_address ) ),
     752                                'done'           => true,
     753                        );
     754                }
     755
     756                return array(
     757                        'items_removed'  => false,
     758                        'items_retained' => false,
     759                        'messages'       => array(),
     760                        'done'           => true,
     761                );
     762        }
     763
     764        /**
     765         * Helper function for ajax handler.
     766         *
     767         * @since 4.9.9
     768         *
     769         * @param array $args Ajax request arguments.
     770         */
     771        protected function _make_ajax_call( $args = array() ) {
     772                $this->_last_response_parsed = null;
     773                $this->_last_response        = '';
     774
     775                $defaults = array(
     776                        'action'   => self::$action,
     777                        'security' => wp_create_nonce( self::$action . '-' . self::$request_id ),
     778                        'page'     => self::$page,
     779                        'id'       => self::$request_id,
     780                        'eraser'   => self::$eraser,
     781                );
     782
     783                $_POST = wp_parse_args( $args, $defaults );
     784
     785                try {
     786                        $this->_handleAjax( self::$action );
     787                } catch ( WPAjaxDieContinueException $e ) {
     788                        unset( $e );
     789                }
     790
     791                if ( $this->_last_response ) {
     792                        $this->_last_response_parsed = json_decode( $this->_last_response, true );
     793                }
     794        }
     795}
  • new file tests/phpunit/tests/ajax/PrivacyExportPersonalData.php

    diff --git tests/phpunit/tests/ajax/PrivacyExportPersonalData.php tests/phpunit/tests/ajax/PrivacyExportPersonalData.php
    new file mode 100644
    index 0000000..98c63b2
    - +  
     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 ajax1
     16 * @group privacy
     17 * @covers wp_ajax_wp_privacy_export_personal_data
     18 */
     19class 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         * An array key in the test exporter to unset.
     103         *
     104         * @since 4.9.9
     105         *
     106         * @var string $key_to_unset
     107         */
     108        protected $key_to_unset;
     109
     110        /**
     111         * A value to change the test exporter callback to.
     112         *
     113         * @since 4.9.9
     114         *
     115         * @var string $new_callback_value
     116         */
     117        protected $new_callback_value;
     118
     119        /**
     120         * Create user export request fixtures.
     121         *
     122         * @since 4.9.9
     123         *
     124         * @param WP_UnitTest_Factory $factory Factory.
     125         */
     126        public static function wpSetUpBeforeClass( $factory ) {
     127                self::$request_email          = 'requester@example.com';
     128                self::$request_id             = wp_create_user_request( self::$request_email, 'export_personal_data' );
     129                self::$action                 = 'wp-privacy-export-personal-data';
     130                self::$exporter               = 1;
     131                self::$exporter_key           = 'custom-exporter';
     132                self::$exporter_friendly_name = 'Custom Exporter';
     133                self::$page                   = 1;
     134                self::$send_as_email          = false;
     135        }
     136
     137        /**
     138         * Setup before each test method.
     139         *
     140         * @since 4.9.9
     141         */
     142        public function setUp() {
     143                parent::setUp();
     144
     145                // Make sure the exporter response is not modified and avoid e.g. writing export file to disk.
     146                remove_all_filters( 'wp_privacy_personal_data_export_page' );
     147
     148                // Only use our custom privacy personal data exporter.
     149                remove_all_filters( 'wp_privacy_personal_data_exporters' );
     150                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
     151
     152                $this->_setRole( 'administrator' );
     153        }
     154
     155        /**
     156         * Clean up after each test method.
     157         */
     158        public function tearDown() {
     159                remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
     160                $this->key_to_unset       = '';
     161                $this->new_callback_value = '';
     162
     163                parent::tearDown();
     164        }
     165
     166        /**
     167         * Unset a specified key in the test exporter array.
     168         *
     169         * @param array $exporters List of data exporters.
     170         * @return array $exporters List of data exporters.
     171         */
     172        public function filter_unset_exporter_value( $exporters ) {
     173                if ( ! empty( $this->key_to_unset ) ) {
     174                        unset( $exporters[ self::$exporter_key ][ $this->key_to_unset ] );
     175                }
     176
     177                return $exporters;
     178        }
     179
     180        /**
     181         * Change the test exporter callback to a specified value.
     182         *
     183         * @since 4.9.9
     184         *
     185         * @param array $exporters List of data exporters.
     186         * @return array $exporters List of data exporters.
     187         */
     188        public function filter_exporter_callback_index_value( $exporters ) {
     189                $exporters[ self::$exporter_key ]['callback'] = $this->new_callback_value;
     190
     191                return $exporters;
     192        }
     193
     194        /**
     195         * The function should send an error when the request ID is missing.
     196         *
     197         * @since 4.9.9
     198         */
     199        public function test_error_when_missing_request_id() {
     200                $this->_make_ajax_call(
     201                        array(
     202                                'id' => null, // Missing request ID.
     203                        )
     204                );
     205
     206                $this->assertFalse( $this->_last_response_parsed['success'] );
     207                $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
     208        }
     209
     210        /**
     211         * The function should send an error when the request ID is less than 1.
     212         *
     213         * @since 4.9.9
     214         */
     215        public function test_error_when_invalid_id() {
     216                $this->_make_ajax_call(
     217                        array(
     218                                'id' => -1, // Invalid request ID, less than 1.
     219                        )
     220                );
     221
     222                $this->assertFalse( $this->_last_response_parsed['success'] );
     223                $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
     224        }
     225
     226        /**
     227         * The function should send an error when the current user is missing the required capability.
     228         *
     229         * @since 4.9.9
     230         */
     231        public function test_error_when_current_user_missing_required_capability() {
     232                $this->_setRole( 'author' );
     233
     234                $this->_make_ajax_call();
     235
     236                $this->assertFalse( $this->_last_response_parsed['success'] );
     237                $this->assertFalse( current_user_can( 'export_others_personal_data' ) );
     238                $this->assertSame( 'Invalid request.', $this->_last_response_parsed['data'] );
     239        }
     240
     241        /**
     242         * The function should send an error when the nonce does not validate.
     243         *
     244         * @since 4.9.9
     245         */
     246        public function test_failure_with_invalid_nonce() {
     247                $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
     248
     249                $this->_make_ajax_call(
     250                        array(
     251                                'security' => 'invalid-nonce',
     252                        )
     253                );
     254        }
     255
     256        /**
     257         * The function should send an error when the request type is incorrect.
     258         *
     259         * @since 4.9.9
     260         */
     261        public function test_error_when_incorrect_request_type() {
     262                $request_id = wp_create_user_request(
     263                        'erase-request@example.com',
     264                        'remove_personal_data' // Incorrect request type, expects 'export_personal_data'.
     265                );
     266
     267                $this->_make_ajax_call(
     268                        array(
     269                                'security' => wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ),
     270                                'id'       => $request_id,
     271                        )
     272                );
     273
     274                $this->assertFalse( $this->_last_response_parsed['success'] );
     275                $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
     276        }
     277
     278        /**
     279         * The function should send an error when the requester's email address is invalid.
     280         *
     281         * @since 4.9.9
     282         */
     283        public function test_error_when_invalid_email_address() {
     284                wp_update_post(
     285                        array(
     286                                'ID'         => self::$request_id,
     287                                'post_title' => '', // Invalid requester's email address.
     288                        )
     289                );
     290
     291                $this->_make_ajax_call();
     292
     293                $this->assertFalse( $this->_last_response_parsed['success'] );
     294                $this->assertSame( 'A valid email address must be given.', $this->_last_response_parsed['data'] );
     295        }
     296
     297        /**
     298         * The function should send an error when the exporter index is missing.
     299         *
     300         * @since 4.9.9
     301         */
     302        public function test_error_when_missing_exporter_index() {
     303                $this->_make_ajax_call(
     304                        array(
     305                                'exporter' => null, // Missing exporter index.
     306                        )
     307                );
     308
     309                $this->assertFalse( $this->_last_response_parsed['success'] );
     310                $this->assertSame( 'Missing exporter index.', $this->_last_response_parsed['data'] );
     311        }
     312
     313        /**
     314         * The function should send an error when the page index is missing.
     315         *
     316         * @since 4.9.9
     317         */
     318        public function test_error_when_missing_page_index() {
     319                $this->_make_ajax_call(
     320                        array(
     321                                'page' => null, // Missing page index.
     322                        )
     323                );
     324
     325                $this->assertFalse( $this->_last_response_parsed['success'] );
     326                $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
     327        }
     328
     329        /**
     330         * The function should send an error when an exporter has improperly used the `wp_privacy_personal_data_exporters` filter.
     331         *
     332         * @since 4.9.9
     333         */
     334        public function test_error_when_exporter_has_improperly_used_exporters_filter() {
     335                // Improper filter usage: returns false instead of an expected array.
     336                add_filter( 'wp_privacy_personal_data_exporters', '__return_false', 999 );
     337                $this->_make_ajax_call();
     338
     339                $this->assertFalse( $this->_last_response_parsed['success'] );
     340                $this->assertSame( 'An exporter has improperly used the registration filter.', $this->_last_response_parsed['data'] );
     341        }
     342
     343        /**
     344         * The function should send an error when the exporter index is negative.
     345         *
     346         * @since 4.9.9
     347         */
     348        public function test_error_when_negative_exporter_index() {
     349                $this->_make_ajax_call(
     350                        array(
     351                                'exporter' => -1, // Negative exporter index.
     352                        )
     353                );
     354
     355                $this->assertFalse( $this->_last_response_parsed['success'] );
     356                $this->assertSame( 'Exporter index cannot be negative.', $this->_last_response_parsed['data'] );
     357        }
     358
     359        /**
     360         * The function should send an error when the exporter index is out of range.
     361         *
     362         * @since 4.9.9
     363         */
     364        public function test_error_when_exporter_index_out_of_range() {
     365                $this->_make_ajax_call(
     366                        array(
     367                                'exporter' => PHP_INT_MAX, // Out of range exporter index.
     368                        )
     369                );
     370
     371                $this->assertFalse( $this->_last_response_parsed['success'] );
     372                $this->assertSame( 'Exporter index is out of range.', $this->_last_response_parsed['data'] );
     373        }
     374
     375        /**
     376         * The function should send an error when the page index is less than one.
     377         *
     378         * @since 4.9.9
     379         */
     380        public function test_error_when_page_index_less_than_one() {
     381                $this->_make_ajax_call(
     382                        array(
     383                                'page' => 0, // Page index less than one.
     384                        )
     385                );
     386
     387                $this->assertFalse( $this->_last_response_parsed['success'] );
     388                $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
     389        }
     390
     391        /**
     392         * The function should send an error when an exporter is not an array.
     393         *
     394         * @since 4.9.9
     395         */
     396        public function test_error_when_exporter_not_array() {
     397                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_not_array' ), 20 );
     398                $this->_make_ajax_call();
     399
     400                $this->assertFalse( $this->_last_response_parsed['success'] );
     401                $this->assertSame(
     402                        sprintf(
     403                                'Expected an array describing the exporter at index %s.',
     404                                self::$exporter_key
     405                        ),
     406                        $this->_last_response_parsed['data']
     407                );
     408        }
     409
     410        /**
     411         * Change the exporter information to an invalid value.
     412         *
     413         * @since 4.9.9
     414         *
     415         * @param array $exporters Exporters.
     416         * @return array $exporters Exporters.
     417         */
     418        public function filter_exporter_not_array( $exporters ) {
     419                $exporters[ self::$exporter_key ] = false;
     420
     421                return $exporters;
     422        }
     423
     424        /**
     425         * The function should send an error when an exporter is missing a friendly name.
     426         *
     427         * @since 4.9.9
     428         */
     429        public function test_error_when_exporter_missing_friendly_name() {
     430                $this->key_to_unset = 'exporter_friendly_name';
     431
     432                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_unset_exporter_value' ), 20 );
     433                $this->_make_ajax_call();
     434
     435                $this->assertFalse( $this->_last_response_parsed['success'] );
     436                $this->assertSame(
     437                        sprintf(
     438                                'Exporter array at index %s does not include a friendly name.',
     439                                self::$exporter_key
     440                        ),
     441                        $this->_last_response_parsed['data']
     442                );
     443        }
     444
     445        /**
     446         * The function should send an error when an exporter is missing a callback.
     447         *
     448         * @since 4.9.9
     449         */
     450        public function test_error_when_exporter_missing_callback() {
     451                $this->key_to_unset = 'callback';
     452
     453                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_unset_exporter_value' ), 20 );
     454                $this->_make_ajax_call();
     455
     456                $this->assertFalse( $this->_last_response_parsed['success'] );
     457                $this->assertSame(
     458                        sprintf(
     459                                'Exporter does not include a callback: %s.',
     460                                self::$exporter_friendly_name
     461                        ),
     462                        $this->_last_response_parsed['data']
     463                );
     464        }
     465
     466        /**
     467         * The function should send an error when an exporter, at a given index, has an invalid callback.
     468         *
     469         * @since 4.9.9
     470         */
     471        public function test_error_when_exporter_index_invalid_callback() {
     472                $this->new_callback_value = false;
     473
     474                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     475                $this->_make_ajax_call();
     476
     477                $this->assertFalse( $this->_last_response_parsed['success'] );
     478                $this->assertSame(
     479                        sprintf(
     480                                'Exporter callback is not a valid callback: %s.',
     481                                self::$exporter_friendly_name
     482                        ),
     483                        $this->_last_response_parsed['data']
     484                );
     485        }
     486
     487        /**
     488         * When an exporter callback returns a WP_Error, it should be passed as the error.
     489         *
     490         * @since 4.9.9
     491         */
     492        public function test_error_when_exporter_callback_returns_wp_error() {
     493                $this->new_callback_value = array( $this, 'callback_return_wp_error' );
     494
     495                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     496                $this->_make_ajax_call();
     497
     498                $this->assertFalse( $this->_last_response_parsed['success'] );
     499                $this->assertSame( 'passed_message', $this->_last_response_parsed['data'][0]['code'] );
     500                $this->assertSame( 'This is a WP_Error message.', $this->_last_response_parsed['data'][0]['message'] );
     501        }
     502
     503        /**
     504         * Callback for exporter's response.
     505         *
     506         * @since 4.9.9
     507         *
     508         * @param string $email_address The requester's email address.
     509         * @param int    $page          Page number.
     510         * @return WP_Error WP_Error instance.
     511         */
     512        public function callback_return_wp_error( $email_address, $page = 1 ) {
     513                return new WP_Error( 'passed_message', 'This is a WP_Error message.' );
     514        }
     515
     516        /**
     517         * The function should send an error when an exporter, at a given index, is missing an array response.
     518         *
     519         * @since 4.9.9
     520         */
     521        public function test_error_when_exporter_index_invalid_response() {
     522                $this->new_callback_value = '__return_null';
     523
     524                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     525                $this->_make_ajax_call();
     526
     527                $this->assertFalse( $this->_last_response_parsed['success'] );
     528                $this->assertSame(
     529                        sprintf(
     530                                'Expected response as an array from exporter: %s.',
     531                                self::$exporter_friendly_name
     532                        ),
     533                        $this->_last_response_parsed['data']
     534                );
     535        }
     536
     537        /**
     538         * The function should send an error when an exporter is missing data in array response.
     539         *
     540         * @since 4.9.9
     541         */
     542        public function test_error_when_exporter_missing_data_response() {
     543                $this->new_callback_value = array( $this, 'callback_missing_data_response' );
     544
     545                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     546                $this->_make_ajax_call();
     547
     548                $this->assertFalse( $this->_last_response_parsed['success'] );
     549                $this->assertSame(
     550                        sprintf(
     551                                'Expected data in response array from exporter: %s.',
     552                                self::$exporter_friendly_name
     553                        ),
     554                        $this->_last_response_parsed['data']
     555                );
     556        }
     557
     558        /**
     559         * Callback for exporter's response.
     560         *
     561         * @since 4.9.9
     562         *
     563         * @param string $email_address The requester's email address.
     564         * @param int    $page          Page number.
     565         * @return array $return Export data.
     566         */
     567        public function callback_missing_data_response( $email_address, $page = 1 ) {
     568                $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
     569                unset( $response['data'] ); // Missing data part of response.
     570
     571                return $response;
     572        }
     573
     574        /**
     575         * The function should send an error when an exporter is missing 'data' array in array response.
     576         *
     577         * @since 4.9.9
     578         */
     579        public function test_function_should_error_when_exporter_missing_data_array_response() {
     580                $this->new_callback_value = array( $this, 'callback_missing_data_array_response' );
     581
     582                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     583                $this->_make_ajax_call();
     584
     585                $this->assertFalse( $this->_last_response_parsed['success'] );
     586                $this->assertSame(
     587                        sprintf(
     588                                'Expected data array in response array from exporter: %s.',
     589                                self::$exporter_friendly_name
     590                        ),
     591                        $this->_last_response_parsed['data']
     592                );
     593        }
     594
     595        /**
     596         * Callback for exporter's response.
     597         *
     598         * @since 4.9.9
     599         *
     600         * @param  string $email_address The requester's email address.
     601         * @param  int    $page          Page number.
     602         *
     603         * @return array $return Export data.
     604         */
     605        public function callback_missing_data_array_response( $email_address, $page = 1 ) {
     606                $response         = $this->callback_custom_personal_data_exporter( $email_address, $page );
     607                $response['data'] = false; // Not an array.
     608                return $response;
     609        }
     610
     611        /**
     612         * The function should send an error when an exporter is missing 'done' in array response.
     613         *
     614         * @since 4.9.9
     615         */
     616        public function test_error_when_exporter_missing_done_response() {
     617                $this->new_callback_value = array( $this, 'callback_missing_done_response' );
     618
     619                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_index_value' ), 20 );
     620                $this->_make_ajax_call();
     621
     622                $this->assertFalse( $this->_last_response_parsed['success'] );
     623                $this->assertSame(
     624                        sprintf(
     625                                'Expected done (boolean) in response array from exporter: %s.',
     626                                self::$exporter_friendly_name
     627                        ),
     628                        $this->_last_response_parsed['data']
     629                );
     630        }
     631
     632        /**
     633         * Remove the response's done flag.
     634         *
     635         * @since 4.9.9
     636         *
     637         * @param string $email_address The requester's email address.
     638         * @param int    $page          Page number.
     639         * @return array $return Export data.
     640         */
     641        public function callback_missing_done_response( $email_address, $page = 1 ) {
     642                $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
     643                unset( $response['done'] );
     644
     645                return $response;
     646        }
     647
     648        /**
     649         * The function should successfully send exporter data response when the current user has the required capability.
     650         *
     651         * @since 4.9.9
     652         */
     653        public function test_succeeds_when_current_user_has_required_capability() {
     654                $this->_make_ajax_call();
     655
     656                $this->assertTrue( $this->_last_response_parsed['success'] );
     657                $this->assertTrue( current_user_can( 'export_others_personal_data' ) );
     658                $this->assertSame( 'custom-exporter-item-id', $this->_last_response_parsed['data']['data']['item_id'] );
     659                $this->assertSame( 'Email', $this->_last_response_parsed['data']['data']['data'][0]['name'] );
     660                $this->assertSame( self::$request_email, $this->_last_response_parsed['data']['data']['data'][0]['value'] );
     661        }
     662
     663        /**
     664         * The function's output should be filterable with the `wp_privacy_personal_data_export_page` filter.
     665         *
     666         * @since 4.9.9
     667         */
     668        public function test_output_should_be_filterable() {
     669                add_filter( 'wp_privacy_personal_data_export_page', array( $this, 'filter_exporter_data_response' ), 20, 7 );
     670                $this->_make_ajax_call();
     671
     672                $expected_group_label = sprintf(
     673                        '%s-%s-%s-%s-%s-%s',
     674                        self::$exporter,
     675                        self::$page,
     676                        self::$request_email,
     677                        self::$request_id,
     678                        self::$send_as_email,
     679                        self::$exporter_key
     680                );
     681
     682                $this->assertTrue( $this->_last_response_parsed['success'] );
     683                $this->assertSame( $expected_group_label, $this->_last_response_parsed['data']['group_label'] );
     684                $this->assertSame( 'filtered_group_id', $this->_last_response_parsed['data']['group_id'] );
     685                $this->assertSame( 'filtered_item_id', $this->_last_response_parsed['data']['item_id'] );
     686                $this->assertSame( 'filtered_name', $this->_last_response_parsed['data']['data'][0]['name'] );
     687                $this->assertSame( 'filtered_value', $this->_last_response_parsed['data']['data'][0]['value'] );
     688        }
     689
     690        /**
     691         * Filter exporter's data response.
     692         *
     693         * @since 4.9.9
     694         *
     695         * @param array  $response        The personal data for the given exporter and page.
     696         * @param int    $exporter_index  The index of the exporter that provided this data.
     697         * @param string $email_address   The email address associated with this personal data.
     698         * @param int    $page            The page for this response.
     699         * @param int    $request_id      The privacy request post ID associated with this request.
     700         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
     701         * @param string $exporter_key    The key (slug) of the exporter that provided this data.
     702         *
     703         * @return array $response The personal data for the given exporter and page.
     704         */
     705        public function filter_exporter_data_response( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) {
     706                $group_label                  = sprintf(
     707                        '%s-%s-%s-%s-%s-%s',
     708                        $exporter_index,
     709                        $page,
     710                        $email_address,
     711                        $request_id,
     712                        $send_as_email,
     713                        $exporter_key
     714                );
     715                $response['group_label']      = $group_label;
     716                $response['group_id']         = 'filtered_group_id';
     717                $response['item_id']          = 'filtered_item_id';
     718                $response['data'][0]['name']  = 'filtered_name';
     719                $response['data'][0]['value'] = 'filtered_value';
     720
     721                return $response;
     722        }
     723
     724        /**
     725         * Filter to register a custom personal data exporter.
     726         *
     727         * @since 4.9.9
     728         *
     729         * @param array $exporters An array of personal data exporters.
     730         * @return array $exporters An array of personal data exporters.
     731         */
     732        public function filter_register_custom_personal_data_exporter( $exporters ) {
     733                $exporters[ self::$exporter_key ] = array(
     734                        'exporter_friendly_name' => self::$exporter_friendly_name,
     735                        'callback'               => array( $this, 'callback_custom_personal_data_exporter' ),
     736                );
     737                return $exporters;
     738        }
     739
     740        /**
     741         * Callback for a custom personal data exporter.
     742         *
     743         * @since 4.9.9
     744         *
     745         * @param string $email_address The requester's email address.
     746         * @param int    $page          Page number.
     747         *
     748         * @return array $response Export data response.
     749         */
     750        public function callback_custom_personal_data_exporter( $email_address, $page = 1 ) {
     751                $data_to_export = array();
     752
     753                if ( 1 === $page ) {
     754                        $data_to_export = array(
     755                                'group_id'    => self::$exporter_key . '-group-id',
     756                                'group_label' => self::$exporter_key . '-group-label',
     757                                'item_id'     => self::$exporter_key . '-item-id',
     758                                'data'        => array(
     759                                        array(
     760                                                'name'  => 'Email',
     761                                                'value' => $email_address,
     762                                        ),
     763                                ),
     764                        );
     765                }
     766
     767                return array(
     768                        'data' => $data_to_export,
     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                        'exporter'    => self::$exporter,
     788                        'page'        => self::$page,
     789                        'sendAsEmail' => self::$send_as_email,
     790                        'id'          => self::$request_id,
     791                );
     792
     793                $_POST = wp_parse_args( $args, $defaults );
     794
     795                try {
     796                        $this->_handleAjax( self::$action );
     797                } catch ( WPAjaxDieContinueException $e ) {
     798                        unset( $e );
     799                }
     800
     801                if ( $this->_last_response ) {
     802                        $this->_last_response_parsed = json_decode( $this->_last_response, true );
     803                }
     804        }
     805}