Make WordPress Core

Ticket #43438: 43438.15.2.diff

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