Make WordPress Core

Ticket #43438: 43438.17.diff

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