Make WordPress Core

Ticket #43438: 43438.14.diff

File 43438.14.diff, 51.4 KB (added by desrosj, 7 years ago)
  • src/wp-admin/includes/ajax-actions.php

     
    44064406                }
    44074407
    44084408                if ( $exporter_index > count( $exporters ) ) {
    4409                         wp_send_json_error( __( 'Exporter index out of range.' ) );
     4409                        wp_send_json_error( __( 'Exporter index is out of range.' ) );
    44104410                }
    44114411
    44124412                if ( $page < 1 ) {
     
    44294429                                sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
    44304430                        );
    44314431                }
     4432
     4433                $exporter_friendly_name = $exporter['exporter_friendly_name'];
     4434
    44324435                if ( ! array_key_exists( 'callback', $exporter ) ) {
    44334436                        wp_send_json_error(
    44344437                                /* translators: %s: exporter friendly name */
    4435                                 sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
     4438                                sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
    44364439                        );
    44374440                }
    44384441                if ( ! is_callable( $exporter['callback'] ) ) {
    44394442                        wp_send_json_error(
    44404443                                /* translators: %s: exporter friendly name */
    4441                                 sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
     4444                                sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
    44424445                        );
    44434446                }
    44444447
    4445                 $callback               = $exporter['callback'];
    4446                 $exporter_friendly_name = $exporter['exporter_friendly_name'];
     4448                $callback = $exporter['callback'];
     4449                $response = call_user_func( $callback, $email_address, $page );
    44474450
    4448                 $response = call_user_func( $callback, $email_address, $page );
    44494451                if ( is_wp_error( $response ) ) {
    44504452                        wp_send_json_error( $response );
    44514453                }
     
    45364538        $request = wp_get_user_request_data( $request_id );
    45374539
    45384540        if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
    4539                 wp_send_json_error( __( 'Invalid request ID.' ) );
     4541                wp_send_json_error( __( 'Invalid request type.' ) );
    45404542        }
    45414543
    45424544        $email_address = $request->email;
     
    46034605                        wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
    46044606                }
    46054607
    4606                 if ( ! array_key_exists( 'callback', $eraser ) ) {
     4608                if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    46074609                        /* translators: %d: array index */
    4608                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );
     4610                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
    46094611                }
    46104612
    4611                 if ( ! is_callable( $eraser['callback'] ) ) {
    4612                         /* translators: %d: array index */
    4613                         wp_send_json_error( sprintf( __( 'Eraser callback at index %d is not a valid callback.' ), $eraser_index ) );
     4613                $eraser_friendly_name = $eraser['eraser_friendly_name'];
     4614
     4615                if ( ! array_key_exists( 'callback', $eraser ) ) {
     4616                        wp_send_json_error(
     4617                                sprintf(
     4618                                        /* translators: %d: array index */
     4619                                        __( 'Eraser does not include a callback: %s.' ),
     4620                                        esc_html( $eraser_friendly_name )
     4621                                )
     4622                        );
    46144623                }
    46154624
    4616                 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    4617                         /* translators: %d: array index */
    4618                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
     4625                if ( ! is_callable( $eraser['callback'] ) ) {
     4626                        wp_send_json_error(
     4627                                sprintf(
     4628                                        /* translators: %d: array index */
     4629                                        __( 'Eraser callback is not valid: %s.' ),
     4630                                        esc_html( $eraser_friendly_name )
     4631                                )
     4632                        );
    46194633                }
    46204634
    4621                 $callback             = $eraser['callback'];
    4622                 $eraser_friendly_name = $eraser['eraser_friendly_name'];
    4623 
     4635                $callback = $eraser['callback'];
    46244636                $response = call_user_func( $callback, $email_address, $page );
    46254637
    46264638                if ( is_wp_error( $response ) ) {
  • tests/phpunit/includes/testcase-ajax.php

     
    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() {
  • tests/phpunit/tests/ajax/PrivacyErasePersonalData.php

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

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