Make WordPress Core

Ticket #43438: 43438.19.diff

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