Make WordPress Core

Ticket #43438: 43438.15.diff

File 43438.15.diff, 50.7 KB (added by desrosj, 6 years ago)
  • src/wp-admin/includes/ajax-actions.php

     
    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 ) {
     
    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'];
     4471                $callback = $exporter['callback'];
     4472                $response = call_user_func( $callback, $email_address, $page );
    44704473
    4471                 $response = call_user_func( $callback, $email_address, $page );
    44724474                if ( is_wp_error( $response ) ) {
    44734475                        wp_send_json_error( $response );
    44744476                }
     
    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;
     
    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 ) ) {
     4631                if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    46304632                        /* translators: %d: array index */
    4631                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );
     4633                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
    46324634                }
    46334635
    4634                 if ( ! is_callable( $eraser['callback'] ) ) {
    4635                         /* translators: %d: array index */
    4636                         wp_send_json_error( sprintf( __( 'Eraser callback at index %d is not a valid callback.' ), $eraser_index ) );
     4636                $eraser_friendly_name = $eraser['eraser_friendly_name'];
     4637
     4638                if ( ! array_key_exists( 'callback', $eraser ) ) {
     4639                        wp_send_json_error(
     4640                                sprintf(
     4641                                        /* translators: %d: array index */
     4642                                        __( 'Eraser does not include a callback: %s.' ),
     4643                                        esc_html( $eraser_friendly_name )
     4644                                )
     4645                        );
    46374646                }
    46384647
    4639                 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    4640                         /* translators: %d: array index */
    4641                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
     4648                if ( ! is_callable( $eraser['callback'] ) ) {
     4649                        wp_send_json_error(
     4650                                sprintf(
     4651                                        /* translators: %d: array index */
     4652                                        __( 'Eraser callback is not valid: %s.' ),
     4653                                        esc_html( $eraser_friendly_name )
     4654                                )
     4655                        );
    46424656                }
    46434657
    4644                 $callback             = $eraser['callback'];
    4645                 $eraser_friendly_name = $eraser['eraser_friendly_name'];
    4646 
     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

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

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