Make WordPress Core

Ticket #43438: 43438-21.diff

File 43438-21.diff, 52.4 KB (added by garrett-eclipse, 6 years ago)

Updated invalid translator comments, and clarified a few others by defining which array index eraser/exporter

  • src/wp-admin/includes/ajax-actions.php

     
    45134513                }
    45144514
    45154515                if ( $exporter_index > count( $exporters ) ) {
    4516                         wp_send_json_error( __( 'Exporter index out of range.' ) );
     4516                        wp_send_json_error( __( 'Exporter index is out of range.' ) );
    45174517                }
    45184518
    45194519                if ( $page < 1 ) {
     
    45264526
    45274527                if ( ! is_array( $exporter ) ) {
    45284528                        wp_send_json_error(
    4529                                 /* translators: %s: array index */
     4529                                /* translators: %s: exporter array index */
    45304530                                sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
    45314531                        );
    45324532                }
    45334533                if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
    45344534                        wp_send_json_error(
    4535                                 /* translators: %s: array index */
     4535                                /* translators: %s: exporter array index */
    45364536                                sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
    45374537                        );
    45384538                }
     4539
     4540                $exporter_friendly_name = $exporter['exporter_friendly_name'];
     4541
    45394542                if ( ! array_key_exists( 'callback', $exporter ) ) {
    45404543                        wp_send_json_error(
    45414544                                /* translators: %s: exporter friendly name */
    4542                                 sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
     4545                                sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
    45434546                        );
    45444547                }
    45454548                if ( ! is_callable( $exporter['callback'] ) ) {
    45464549                        wp_send_json_error(
    45474550                                /* translators: %s: exporter friendly name */
    4548                                 sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
     4551                                sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
    45494552                        );
    45504553                }
    45514554
    4552                 $callback               = $exporter['callback'];
    4553                 $exporter_friendly_name = $exporter['exporter_friendly_name'];
     4555                $callback = $exporter['callback'];
     4556                $response = call_user_func( $callback, $email_address, $page );
    45544557
    4555                 $response = call_user_func( $callback, $email_address, $page );
    45564558                if ( is_wp_error( $response ) ) {
    45574559                        wp_send_json_error( $response );
    45584560                }
     
    46434645        $request = wp_get_user_request_data( $request_id );
    46444646
    46454647        if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
    4646                 wp_send_json_error( __( 'Invalid request ID.' ) );
     4648                wp_send_json_error( __( 'Invalid request type.' ) );
    46474649        }
    46484650
    46494651        $email_address = $request->email;
     
    47064708                $eraser      = $erasers[ $eraser_key ];
    47074709
    47084710                if ( ! is_array( $eraser ) ) {
    4709                         /* translators: %d: array index */
     4711                        /* translators: %d: eraser array index */
    47104712                        wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
    47114713                }
    47124714
     4715                if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
     4716                        /* translators: %d: eraser array index */
     4717                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
     4718                }
     4719
     4720                $eraser_friendly_name = $eraser['eraser_friendly_name'];
     4721
    47134722                if ( ! array_key_exists( 'callback', $eraser ) ) {
    4714                         /* translators: %d: array index */
    4715                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );
     4723                        wp_send_json_error(
     4724                                sprintf(
     4725                                        /* translators: %s: eraser friendly name */
     4726                                        __( 'Eraser does not include a callback: %s.' ),
     4727                                        esc_html( $eraser_friendly_name )
     4728                                )
     4729                        );
    47164730                }
    47174731
    47184732                if ( ! is_callable( $eraser['callback'] ) ) {
    4719                         /* translators: %d: array index */
    4720                         wp_send_json_error( sprintf( __( 'Eraser callback at index %d is not a valid callback.' ), $eraser_index ) );
     4733                        wp_send_json_error(
     4734                                sprintf(
     4735                                        /* translators: %s: eraser friendly name */
     4736                                        __( 'Eraser callback is not valid: %s.' ),
     4737                                        esc_html( $eraser_friendly_name )
     4738                                )
     4739                        );
    47214740                }
    47224741
    4723                 if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
    4724                         /* translators: %d: array index */
    4725                         wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
    4726                 }
    4727 
    4728                 $callback             = $eraser['callback'];
    4729                 $eraser_friendly_name = $eraser['eraser_friendly_name'];
    4730 
     4742                $callback = $eraser['callback'];
    47314743                $response = call_user_func( $callback, $email_address, $page );
    47324744
    47334745                if ( is_wp_error( $response ) ) {
     
    47374749                if ( ! is_array( $response ) ) {
    47384750                        wp_send_json_error(
    47394751                                sprintf(
    4740                                         /* translators: 1: eraser friendly name, 2: array index */
     4752                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47414753                                        __( 'Did not receive array from %1$s eraser (index %2$d).' ),
    47424754                                        esc_html( $eraser_friendly_name ),
    47434755                                        $eraser_index
     
    47484760                if ( ! array_key_exists( 'items_removed', $response ) ) {
    47494761                        wp_send_json_error(
    47504762                                sprintf(
    4751                                         /* translators: 1: eraser friendly name, 2: array index */
     4763                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47524764                                        __( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
    47534765                                        esc_html( $eraser_friendly_name ),
    47544766                                        $eraser_index
     
    47594771                if ( ! array_key_exists( 'items_retained', $response ) ) {
    47604772                        wp_send_json_error(
    47614773                                sprintf(
    4762                                         /* translators: 1: eraser friendly name, 2: array index */
     4774                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47634775                                        __( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
    47644776                                        esc_html( $eraser_friendly_name ),
    47654777                                        $eraser_index
     
    47704782                if ( ! array_key_exists( 'messages', $response ) ) {
    47714783                        wp_send_json_error(
    47724784                                sprintf(
    4773                                         /* translators: 1: eraser friendly name, 2: array index */
     4785                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47744786                                        __( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
    47754787                                        esc_html( $eraser_friendly_name ),
    47764788                                        $eraser_index
     
    47814793                if ( ! is_array( $response['messages'] ) ) {
    47824794                        wp_send_json_error(
    47834795                                sprintf(
    4784                                         /* translators: 1: eraser friendly name, 2: array index */
     4796                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47854797                                        __( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
    47864798                                        esc_html( $eraser_friendly_name ),
    47874799                                        $eraser_index
     
    47924804                if ( ! array_key_exists( 'done', $response ) ) {
    47934805                        wp_send_json_error(
    47944806                                sprintf(
    4795                                         /* translators: 1: eraser friendly name, 2: array index */
     4807                                        /* translators: 1: eraser friendly name, 2: eraser array index */
    47964808                                        __( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
    47974809                                        esc_html( $eraser_friendly_name ),
    47984810                                        $eraser_index
  • 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 5.2.0
     8 */
     9
     10/**
     11 * Tests_Ajax_PrivacyExportPersonalData class.
     12 *
     13 * @since 5.2.0
     14 *
     15 * @group ajax
     16 * @group privacy
     17 *
     18 * @covers ::wp_ajax_wp_privacy_erase_personal_data
     19 */
     20class Tests_Ajax_PrivacyErasePersonalData extends WP_Ajax_UnitTestCase {
     21
     22        /**
     23         * User Request ID.
     24         *
     25         * @since 5.2.0
     26         *
     27         * @var int $request_id
     28         */
     29        protected static $request_id;
     30
     31        /**
     32         * User Request Email.
     33         *
     34         * @since 5.2.0
     35         *
     36         * @var string $request_email
     37         */
     38        protected static $request_email;
     39
     40        /**
     41         * Ajax Action.
     42         *
     43         * @since 5.2.0
     44         *
     45         * @var string $action
     46         */
     47        protected static $action;
     48
     49        /**
     50         * Eraser Index.
     51         *
     52         * @since 5.2.0
     53         *
     54         * @var int $eraser
     55         */
     56        protected static $eraser;
     57
     58        /**
     59         * Eraser Key.
     60         *
     61         * @since 5.2.0
     62         *
     63         * @var string $eraser_key
     64         */
     65        protected static $eraser_key;
     66
     67        /**
     68         * Eraser Friendly Name.
     69         *
     70         * @since 5.2.0
     71         *
     72         * @var string $eraser_friendly_name
     73         */
     74        protected static $eraser_friendly_name;
     75
     76        /**
     77         * Page Index.
     78         *
     79         * @since 5.2.0
     80         *
     81         * @var int $page
     82         */
     83        protected static $page;
     84
     85        /**
     86         * Last response parsed.
     87         *
     88         * @since 5.2.0
     89         *
     90         * @var array $_last_response_parsed
     91         */
     92        protected $_last_response_parsed;
     93
     94        /**
     95         * An array key in the test eraser to unset.
     96         *
     97         * @since 5.2.0
     98         *
     99         * @var string $key_to_unset
     100         */
     101        protected $key_to_unset;
     102
     103        /**
     104         * A value to change the test eraser callback to.
     105         *
     106         * @since 5.2.0
     107         *
     108         * @var string $new_callback_value
     109         */
     110        protected $new_callback_value;
     111
     112        /**
     113         * Create user erase request fixtures.
     114         *
     115         * @param WP_UnitTest_Factory $factory Factory.
     116         */
     117        public static function wpSetUpBeforeClass( $factory ) {
     118                self::$request_email        = 'requester@example.com';
     119                self::$request_id           = wp_create_user_request( self::$request_email, 'remove_personal_data' );
     120                self::$action               = 'wp-privacy-erase-personal-data';
     121                self::$eraser               = 1;
     122                self::$eraser_key           = 'custom-eraser';
     123                self::$eraser_friendly_name = 'Custom Eraser';
     124                self::$page                 = 1;
     125        }
     126
     127        /**
     128         * Register a custom personal data eraser.
     129         */
     130        public function setUp() {
     131                parent::setUp();
     132
     133                $this->key_to_unset = '';
     134
     135                // Make sure the erasers response is not modified and avoid sending emails.
     136                remove_all_filters( 'wp_privacy_personal_data_erasure_page' );
     137                remove_all_actions( 'wp_privacy_personal_data_erased' );
     138
     139                // Only use our custom privacy personal data eraser.
     140                remove_all_filters( 'wp_privacy_personal_data_erasers' );
     141                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
     142
     143                $this->_setRole( 'administrator' );
     144        }
     145
     146        /**
     147         * Clean up after each test method.
     148         */
     149        public function tearDown() {
     150                remove_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
     151                $this->new_callback_value = '';
     152
     153                parent::tearDown();
     154        }
     155
     156        /**
     157         * Helper method for changing the test eraser's callback function.
     158         *
     159         * @param string|array $callback New test eraser callback index value.
     160         */
     161        protected function _set_eraser_callback( $callback ) {
     162                $this->new_callback_value = $callback;
     163                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_callback_value' ), 20 );
     164        }
     165
     166        /**
     167         * Change the test eraser callback to a specified value.
     168         *
     169         * @since 5.2.0
     170         *
     171         * @param array $erasers List of data erasers.
     172         *
     173         * @return array $erasersList of data erasers.
     174         */
     175        public function filter_eraser_callback_value( $erasers ) {
     176                $erasers[ self::$eraser_key ]['callback'] = $this->new_callback_value;
     177
     178                return $erasers;
     179        }
     180
     181        /**
     182         * Helper method for unsetting an array index in the test eraser.
     183         *
     184         * @param string|bool $key Test eraser key to unset.
     185         */
     186        protected function _unset_eraser_key( $key ) {
     187                $this->key_to_unset = $key;
     188                add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 );
     189        }
     190
     191        /**
     192         * Unsets an array key in the test eraser.
     193         *
     194         * If the key is false, the eraser is set to false.
     195         *
     196         * @since 5.2.0
     197         *
     198         * @param array $erasers Erasers.
     199         *
     200         * @return array $erasers Erasers.
     201         */
     202        public function filter_unset_eraser_index( $erasers ) {
     203                if ( false === $this->key_to_unset ) {
     204                        $erasers[ self::$eraser_key ] = false;
     205                } elseif ( ! empty( $this->key_to_unset ) ) {
     206                        unset( $erasers[ self::$eraser_key ][ $this->key_to_unset ] );
     207                }
     208
     209                return $erasers;
     210        }
     211
     212        /**
     213         * Helper method for erasing a key from the eraser response.
     214         *
     215         * @since 5.2.0
     216         *
     217         * @param array $key Response key to unset.
     218         */
     219        protected function _unset_response_key( $key ) {
     220                $this->key_to_unset = $key;
     221                $this->_set_eraser_callback( array( $this, 'filter_unset_response_index' ) );
     222        }
     223
     224        /**
     225         * Unsets an array index in a response.
     226         *
     227         * @since 5.2.0
     228         *
     229         * @param string $email_address The requester's email address.
     230         * @param int    $page          Page number.
     231         *
     232         * @return array $return Export data.
     233         */
     234        public function filter_unset_response_index( $email_address, $page = 1 ) {
     235                $response = $this->callback_personal_data_eraser( $email_address, $page );
     236
     237                if ( ! empty( $this->key_to_unset ) ) {
     238                        unset( $response[ $this->key_to_unset ] );
     239                }
     240
     241                return $response;
     242        }
     243
     244        /**
     245         * The function should send an error when the request ID is missing.
     246         *
     247         * @since 5.2.0
     248         *
     249         * @ticket 43438
     250         */
     251        public function test_error_when_missing_request_id() {
     252                $this->assertNotWPError( self::$request_id );
     253
     254                // Set up a request.
     255                $this->_make_ajax_call(
     256                        array(
     257                                'id' => null, // Missing request ID.
     258                        )
     259                );
     260
     261                $this->assertFalse( $this->_last_response_parsed['success'] );
     262                $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
     263        }
     264
     265        /**
     266         * The function should send an error when the request ID is less than 1.
     267         *
     268         * @since 5.2.0
     269         *
     270         * @ticket 43438
     271         */
     272        public function test_error_when_request_id_invalid() {
     273                $this->assertNotWPError( self::$request_id );
     274
     275                // Set up a request.
     276                $this->_make_ajax_call(
     277                        array(
     278                                'id' => -1, // Invalid request ID.
     279                        )
     280                );
     281
     282                $this->assertFalse( $this->_last_response_parsed['success'] );
     283                $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
     284        }
     285
     286        /**
     287         * The function should send an error when the current user is missing required capabilities.
     288         *
     289         * @since 5.2.0
     290         *
     291         * @ticket 43438
     292         */
     293        public function test_error_when_current_user_missing_required_capabilities() {
     294                $this->_setRole( 'author' );
     295
     296                $this->assertFalse( current_user_can( 'erase_others_personal_data' ) );
     297                $this->assertFalse( current_user_can( 'delete_users' ) );
     298
     299                $this->_make_ajax_call();
     300
     301                $this->assertFalse( $this->_last_response_parsed['success'] );
     302                $this->assertSame( 'Sorry, you are not allowed to perform this action.', $this->_last_response_parsed['data'] );
     303        }
     304
     305        /**
     306         * The function should send an error when the nonce does not validate.
     307         *
     308         * @since 5.2.0
     309         */
     310        public function test_failure_with_invalid_nonce() {
     311                $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
     312
     313                $this->_make_ajax_call(
     314                        array(
     315                                'security' => 'invalid-nonce',
     316                        )
     317                );
     318        }
     319
     320        /**
     321         * The function should send an error when the request type is incorrect.
     322         *
     323         * @since 5.2.0
     324         */
     325        public function test_error_when_incorrect_request_type() {
     326                $request_id = wp_create_user_request(
     327                        'export-request@example.com',
     328                        'export_personal_data' // Incorrect request type, expects 'remove_personal_data'.
     329                );
     330
     331                $this->_make_ajax_call(
     332                        array(
     333                                'security' => wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ),
     334                                'id'       => $request_id,
     335                        )
     336                );
     337
     338                $this->assertFalse( $this->_last_response_parsed['success'] );
     339                $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
     340        }
     341
     342        /**
     343         * The function should send an error when the request email is invalid.
     344         *
     345         * @since 5.2.0
     346         */
     347        public function test_error_when_invalid_email() {
     348                wp_update_post(
     349                        array(
     350                                'ID'         => self::$request_id,
     351                                'post_title' => '', // Invalid requester's email address.
     352                        )
     353                );
     354
     355                $this->_make_ajax_call();
     356
     357                $this->assertFalse( $this->_last_response_parsed['success'] );
     358                $this->assertSame( 'Invalid email address in request.', $this->_last_response_parsed['data'] );
     359        }
     360
     361        /**
     362         * The function should send an error when the eraser index is missing.
     363         *
     364         * @since 5.2.0
     365         */
     366        public function test_error_when_missing_eraser_index() {
     367                $this->_make_ajax_call(
     368                        array(
     369                                'eraser' => null, // Missing eraser index.
     370                        )
     371                );
     372
     373                $this->assertFalse( $this->_last_response_parsed['success'] );
     374                $this->assertSame( 'Missing eraser index.', $this->_last_response_parsed['data'] );
     375        }
     376
     377        /**
     378         * The function should send an error when the page index is missing.
     379         *
     380         * @since 5.2.0
     381         */
     382        public function test_error_when_missing_page_index() {
     383                $this->_make_ajax_call(
     384                        array(
     385                                'page' => null, // Missing page index.
     386                        )
     387                );
     388
     389                $this->assertFalse( $this->_last_response_parsed['success'] );
     390                $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
     391        }
     392
     393        /**
     394         * The function should send an error when the eraser index is negative.
     395         *
     396         * @since 5.2.0
     397         */
     398        public function test_error_when_negative_eraser_index() {
     399                $this->_make_ajax_call(
     400                        array(
     401                                'eraser' => -1, // Negative eraser index.
     402                        )
     403                );
     404
     405                $this->assertFalse( $this->_last_response_parsed['success'] );
     406                $this->assertSame( 'Eraser index cannot be less than one.', $this->_last_response_parsed['data'] );
     407        }
     408
     409        /**
     410         * The function should send an error when the eraser index is out of range.
     411         *
     412         * @since 5.2.0
     413         */
     414        public function test_error_when_eraser_index_out_of_range() {
     415                $this->_make_ajax_call(
     416                        array(
     417                                'eraser' => PHP_INT_MAX, // Out of range eraser index.
     418                        )
     419                );
     420
     421                $this->assertFalse( $this->_last_response_parsed['success'] );
     422                $this->assertSame( 'Eraser index is out of range.', $this->_last_response_parsed['data'] );
     423        }
     424
     425        /**
     426         * The function should send an error when the page index is less than one.
     427         *
     428         * @since 5.2.0
     429         */
     430        public function test_error_when_page_index_less_than_one() {
     431                $this->_make_ajax_call(
     432                        array(
     433                                'page' => 0, // Page index less than one.
     434                        )
     435                );
     436
     437                $this->assertFalse( $this->_last_response_parsed['success'] );
     438                $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
     439        }
     440
     441        /**
     442         * The function should send an error when an eraser is not an array.
     443         *
     444         * @since 5.2.0
     445         */
     446        public function test_error_when_eraser_not_array() {
     447                $this->_unset_eraser_key( false );
     448                $this->_make_ajax_call();
     449
     450                $this->assertFalse( $this->_last_response_parsed['success'] );
     451                $this->assertSame(
     452                        sprintf(
     453                                'Expected an array describing the eraser at index %s.',
     454                                self::$eraser
     455                        ),
     456                        $this->_last_response_parsed['data']
     457                );
     458        }
     459
     460        /**
     461         * The function should send an error when an eraser is missing a friendly name.
     462         *
     463         * @since 5.2.0
     464         */
     465        public function test_error_when_eraser_missing_friendly_name() {
     466                $this->_unset_eraser_key( 'eraser_friendly_name' );
     467                $this->_make_ajax_call();
     468
     469                $this->assertFalse( $this->_last_response_parsed['success'] );
     470                $this->assertSame(
     471                        sprintf(
     472                                'Eraser array at index %s does not include a friendly name.',
     473                                self::$eraser
     474                        ),
     475                        $this->_last_response_parsed['data']
     476                );
     477        }
     478
     479        /**
     480         * The function should send an error when an eraser is missing a callback.
     481         *
     482         * @since 5.2.0
     483         */
     484        public function test_error_when_eraser_missing_callback() {
     485                $this->_unset_eraser_key( 'callback' );
     486                $this->_make_ajax_call();
     487
     488                $this->assertFalse( $this->_last_response_parsed['success'] );
     489                $this->assertSame(
     490                        sprintf(
     491                                'Eraser does not include a callback: %s.',
     492                                self::$eraser_friendly_name
     493                        ),
     494                        $this->_last_response_parsed['data']
     495                );
     496        }
     497
     498        /**
     499         * The function should send an error when an eraser, at a given index, has an invalid callback.
     500         *
     501         * @since 5.2.0
     502         */
     503        public function test_error_when_eraser_index_invalid_callback() {
     504                $this->_set_eraser_callback( false );
     505                $this->_make_ajax_call();
     506
     507                $this->assertFalse( $this->_last_response_parsed['success'] );
     508                $this->assertSame(
     509                        sprintf(
     510                                'Eraser callback is not valid: %s.',
     511                                self::$eraser_friendly_name
     512                        ),
     513                        $this->_last_response_parsed['data']
     514                );
     515        }
     516
     517        /**
     518         * The function should send an error when an eraser, at a given index, is missing an array response.
     519         *
     520         * @since 5.2.0
     521         */
     522        public function test_error_when_eraser_index_invalid_response() {
     523                $this->_set_eraser_callback( '__return_null' );
     524                $this->_make_ajax_call();
     525
     526                $this->assertFalse( $this->_last_response_parsed['success'] );
     527                $this->assertSame(
     528                        sprintf(
     529                                'Did not receive array from %1$s eraser (index %2$d).',
     530                                self::$eraser_friendly_name,
     531                                self::$eraser
     532                        ),
     533                        $this->_last_response_parsed['data']
     534                );
     535        }
     536
     537        /**
     538         * The function should send an error when missing an items_removed index.
     539         *
     540         * @since 5.2.0
     541         */
     542        public function test_error_when_eraser_items_removed_missing() {
     543                $this->_unset_response_key( 'items_removed' );
     544                $this->_make_ajax_call();
     545
     546                $this->assertFalse( $this->_last_response_parsed['success'] );
     547                $this->assertSame(
     548                        sprintf(
     549                                'Expected items_removed key in response array from %1$s eraser (index %2$d).',
     550                                self::$eraser_friendly_name,
     551                                self::$eraser
     552                        ),
     553                        $this->_last_response_parsed['data']
     554                );
     555        }
     556
     557        /**
     558         * The function should send an error when missing an items_retained index.
     559         *
     560         * @since 5.2.0
     561         */
     562        public function test_error_when_eraser_items_retained_missing() {
     563                $this->_unset_response_key( 'items_retained' );
     564                $this->_make_ajax_call();
     565
     566                $this->assertFalse( $this->_last_response_parsed['success'] );
     567                $this->assertSame(
     568                        sprintf(
     569                                'Expected items_retained key in response array from %1$s eraser (index %2$d).',
     570                                self::$eraser_friendly_name,
     571                                self::$eraser
     572                        ),
     573                        $this->_last_response_parsed['data']
     574                );
     575        }
     576
     577        /**
     578         * The function should send an error when missing a messages index.
     579         *
     580         * @since 5.2.0
     581         */
     582        public function test_error_when_eraser_messages_missing() {
     583                $this->_unset_response_key( 'messages' );
     584                $this->_make_ajax_call();
     585
     586                $this->assertFalse( $this->_last_response_parsed['success'] );
     587                $this->assertSame(
     588                        sprintf(
     589                                'Expected messages key in response array from %1$s eraser (index %2$d).',
     590                                self::$eraser_friendly_name,
     591                                self::$eraser
     592                        ),
     593                        $this->_last_response_parsed['data']
     594                );
     595        }
     596
     597        /**
     598         * The function should send an error when the messages index is not an array.
     599         *
     600         * @since 5.2.0
     601         */
     602        public function test_error_when_eraser_messages_not_array() {
     603                $this->_set_eraser_callback( array( $this, 'filter_response_messages_invalid' ) );
     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 5.2.0
     621         *
     622         * @param string $email_address The requester's email address.
     623         * @param int    $page          Page number.
     624         *
     625         * @return array $return Export data.
     626         */
     627        public function filter_response_messages_invalid( $email_address, $page = 1 ) {
     628                $response             = $this->callback_personal_data_eraser( $email_address, $page );
     629                $response['messages'] = true;
     630
     631                return $response;
     632        }
     633
     634        /**
     635         * The function should send an error when an eraser is missing 'done' in array response.
     636         *
     637         * @since 5.2.0
     638         */
     639        public function test_error_when_eraser_missing_done_response() {
     640                $this->_unset_response_key( 'done' );
     641                $this->_make_ajax_call();
     642
     643                $this->assertFalse( $this->_last_response_parsed['success'] );
     644                $this->assertSame(
     645                        sprintf(
     646                                'Expected done flag in response array from %1$s eraser (index %2$d).',
     647                                self::$eraser_friendly_name,
     648                                self::$eraser
     649                        ),
     650                        $this->_last_response_parsed['data']
     651                );
     652        }
     653
     654        /**
     655         * The function should successfully send erasers response data when the current user has the required
     656         * capabilities.
     657         *
     658         * @since 5.2.0
     659         *
     660         * @ticket 43438
     661         */
     662        public function test_success_when_current_user_has_required_capabilities() {
     663                $this->assertTrue( current_user_can( 'erase_others_personal_data' ) );
     664                $this->assertTrue( current_user_can( 'delete_users' ) );
     665
     666                $this->_make_ajax_call();
     667
     668                $this->assertSame(
     669                        sprintf( 'A message regarding retained data for %s.', self::$request_email ),
     670                        $this->_last_response_parsed['data']['messages'][0]
     671                );
     672                $this->assertTrue( $this->_last_response_parsed['success'] );
     673                $this->assertTrue( $this->_last_response_parsed['data']['items_removed'] );
     674                $this->assertTrue( $this->_last_response_parsed['data']['items_retained'] );
     675                $this->assertTrue( $this->_last_response_parsed['data']['done'] );
     676        }
     677
     678        /**
     679         * The function should successfully send erasers response data when no items to erase.
     680         *
     681         * @since 5.2.0
     682         *
     683         * @ticket 43438
     684         */
     685        public function test_success_when_no_items_to_erase() {
     686
     687                $this->_make_ajax_call( array( 'page' => 2 ) );
     688
     689                $this->assertTrue( $this->_last_response_parsed['success'] );
     690                $this->assertFalse( $this->_last_response_parsed['data']['items_removed'] );
     691                $this->assertFalse( $this->_last_response_parsed['data']['items_retained'] );
     692                $this->assertEmpty( $this->_last_response_parsed['data']['messages'] );
     693                $this->assertTrue( $this->_last_response_parsed['data']['done'] );
     694        }
     695
     696        /**
     697         * Test that the function's output should be filterable with the `wp_privacy_personal_data_erasure_page` filter.
     698         *
     699         * @since 5.2.0
     700         */
     701        public function test_output_should_be_filterable() {
     702                add_filter( 'wp_privacy_personal_data_erasure_page', array( $this, 'filter_eraser_data_response' ), 20, 6 );
     703                $this->_make_ajax_call();
     704
     705                $expected_new_index = self::$request_email . '-' . self::$request_id . '-' . self::$eraser_key;
     706
     707                $this->assertTrue( $this->_last_response_parsed['success'] );
     708                $this->assertSame( 'filtered removed', $this->_last_response_parsed['data']['items_removed'] );
     709                $this->assertSame( 'filtered retained', $this->_last_response_parsed['data']['items_retained'] );
     710                $this->assertSame( array( 'filtered messages' ), $this->_last_response_parsed['data']['messages'] );
     711                $this->assertSame( 'filtered done', $this->_last_response_parsed['data']['done'] );
     712                $this->assertSame( $expected_new_index, $this->_last_response_parsed['data']['new_index'] );
     713        }
     714
     715        /**
     716         * Filters the eraser response.
     717         *
     718         * @since 5.2.0
     719         *
     720         * @param array  $response        The personal data for the given eraser and page.
     721         * @param int    $eraser_index    The index of the eraser that provided this data.
     722         * @param string $email_address   The email address associated with this personal data.
     723         * @param int    $page            The page for this response.
     724         * @param int    $request_id      The privacy request post ID associated with this request.
     725         * @param string $eraser_key      The key (slug) of the eraser that provided this data.
     726         *
     727         * @return array Filtered erase response.
     728         */
     729        public function filter_eraser_data_response( $response, $eraser_index, $email_address, $page, $request_id, $eraser_key ) {
     730                $response['items_removed']  = 'filtered removed';
     731                $response['items_retained'] = 'filtered retained';
     732                $response['messages']       = array( 'filtered messages' );
     733                $response['done']           = 'filtered done';
     734                $response['new_index']      = $email_address . '-' . $request_id . '-' . $eraser_key;
     735
     736                return $response;
     737        }
     738
     739        /**
     740         * Register handler for a custom personal data eraser.
     741         *
     742         * @since 5.2.0
     743         *
     744         * @param array $erasers An array of personal data erasers.
     745         *
     746         * @return array $erasers An array of personal data erasers.
     747         */
     748        public function register_custom_personal_data_eraser( $erasers ) {
     749                $erasers[ self::$eraser_key ] = array(
     750                        'eraser_friendly_name' => self::$eraser_friendly_name,
     751                        'callback'             => array( $this, 'callback_personal_data_eraser' ),
     752                );
     753                return $erasers;
     754        }
     755
     756        /**
     757         * Custom Personal Data Eraser.
     758         *
     759         * @since 5.2.0
     760         *
     761         * @param  string $email_address The comment author email address.
     762         * @param  int    $page          Page number.
     763         *
     764         * @return array  $return Erase data.
     765         */
     766        public function callback_personal_data_eraser( $email_address, $page = 1 ) {
     767                if ( 1 === $page ) {
     768                        return array(
     769                                'items_removed'  => true,
     770                                'items_retained' => true,
     771                                'messages'       => array( sprintf( 'A message regarding retained data for %s.', $email_address ) ),
     772                                'done'           => true,
     773                        );
     774                }
     775
     776                return array(
     777                        'items_removed'  => false,
     778                        'items_retained' => false,
     779                        'messages'       => array(),
     780                        'done'           => true,
     781                );
     782        }
     783
     784        /**
     785         * Helper function for ajax handler.
     786         *
     787         * @since 5.2.0
     788         *
     789         * @param array $args Ajax request arguments.
     790         */
     791        protected function _make_ajax_call( $args = array() ) {
     792                $this->_last_response_parsed = null;
     793                $this->_last_response        = '';
     794
     795                $defaults = array(
     796                        'action'   => self::$action,
     797                        'security' => wp_create_nonce( self::$action . '-' . self::$request_id ),
     798                        'page'     => self::$page,
     799                        'id'       => self::$request_id,
     800                        'eraser'   => self::$eraser,
     801                );
     802
     803                $_POST = wp_parse_args( $args, $defaults );
     804
     805                try {
     806                        $this->_handleAjax( self::$action );
     807                } catch ( WPAjaxDieContinueException $e ) {
     808                        unset( $e );
     809                }
     810
     811                if ( $this->_last_response ) {
     812                        $this->_last_response_parsed = json_decode( $this->_last_response, true );
     813                }
     814        }
     815}
  • 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 5.2.0
     8 */
     9
     10/**
     11 * Tests_Ajax_PrivacyExportPersonalData class.
     12 *
     13 * @since 5.2.0
     14 *
     15 * @group ajax
     16 * @group privacy
     17 *
     18 * @covers ::wp_ajax_wp_privacy_export_personal_data
     19 */
     20class Tests_Ajax_PrivacyExportPersonalData extends WP_Ajax_UnitTestCase {
     21
     22        /**
     23         * User Request ID.
     24         *
     25         * @since 5.2.0
     26         *
     27         * @var int $request_id
     28         */
     29        protected static $request_id;
     30
     31        /**
     32         * User Request Email.
     33         *
     34         * @since 5.2.0
     35         *
     36         * @var string $request_email
     37         */
     38        protected static $request_email;
     39
     40        /**
     41         * Ajax Action.
     42         *
     43         * @since 5.2.0
     44         *
     45         * @var string $action
     46         */
     47        protected static $action;
     48
     49        /**
     50         * Exporter Index.
     51         *
     52         * @since 5.2.0
     53         *
     54         * @var int $exporter
     55         */
     56        protected static $exporter;
     57
     58        /**
     59         * Exporter Key.
     60         *
     61         * @since 5.2.0
     62         *
     63         * @var string $exporter_key
     64         */
     65        protected static $exporter_key;
     66
     67        /**
     68         * Exporter Friendly Name.
     69         *
     70         * @since 5.2.0
     71         *
     72         * @var string $exporter_friendly_name
     73         */
     74        protected static $exporter_friendly_name;
     75
     76        /**
     77         * Page Index.
     78         *
     79         * @since 5.2.0
     80         *
     81         * @var int $page
     82         */
     83        protected static $page;
     84
     85        /**
     86         * Send As Email.
     87         *
     88         * @since 5.2.0
     89         *
     90         * @var bool $send_as_email
     91         */
     92        protected static $send_as_email;
     93
     94        /**
     95         * Last response parsed.
     96         *
     97         * @since 5.2.0
     98         *
     99         * @var array $_last_response_parsed
     100         */
     101        protected $_last_response_parsed;
     102
     103        /**
     104         * An array key in the test exporter to unset.
     105         *
     106         * @since 5.2.0
     107         *
     108         * @var string $key_to_unset
     109         */
     110        protected $key_to_unset;
     111
     112        /**
     113         * A value to change the test exporter callback to.
     114         *
     115         * @since 5.2.0
     116         *
     117         * @var string $new_callback_value
     118         */
     119        protected $new_callback_value;
     120
     121        /**
     122         * Create user export request fixtures.
     123         *
     124         * @since 5.2.0
     125         *
     126         * @param WP_UnitTest_Factory $factory Factory.
     127         */
     128        public static function wpSetUpBeforeClass( $factory ) {
     129                self::$request_email          = 'requester@example.com';
     130                self::$request_id             = wp_create_user_request( self::$request_email, 'export_personal_data' );
     131                self::$action                 = 'wp-privacy-export-personal-data';
     132                self::$exporter               = 1;
     133                self::$exporter_key           = 'custom-exporter';
     134                self::$exporter_friendly_name = 'Custom Exporter';
     135                self::$page                   = 1;
     136                self::$send_as_email          = false;
     137        }
     138
     139        /**
     140         * Setup before each test method.
     141         *
     142         * @since 5.2.0
     143         */
     144        public function setUp() {
     145                parent::setUp();
     146
     147                $this->key_to_unset       = '';
     148                $this->new_callback_value = '';
     149
     150                // Make sure the exporter response is not modified and avoid e.g. writing export file to disk.
     151                remove_all_filters( 'wp_privacy_personal_data_export_page' );
     152
     153                // Only use our custom privacy personal data exporter.
     154                remove_all_filters( 'wp_privacy_personal_data_exporters' );
     155                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
     156
     157                $this->_setRole( 'administrator' );
     158        }
     159
     160        /**
     161         * Clean up after each test method.
     162         */
     163        public function tearDown() {
     164                remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
     165
     166                parent::tearDown();
     167        }
     168
     169        /**
     170         * Helper method for changing the test exporter's callback function.
     171         *
     172         * @param string|array $callback New test exporter callback function.
     173         */
     174        protected function _set_exporter_callback( $callback ) {
     175                $this->new_callback_value = $callback;
     176                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_value' ), 20 );
     177        }
     178
     179        /**
     180         * Change the test exporter callback to a specified value.
     181         *
     182         * @since 5.2.0
     183         *
     184         * @param array $exporters List of data exporters.
     185         * @return array $exporters List of data exporters.
     186         */
     187        public function filter_exporter_callback_value( $exporters ) {
     188                $exporters[ self::$exporter_key ]['callback'] = $this->new_callback_value;
     189
     190                return $exporters;
     191        }
     192
     193        /**
     194         * Helper method for unsetting an array index in the test exporter.
     195         *
     196         * @param string $key Test exporter key to unset.
     197         */
     198        protected function _unset_exporter_key( $key ) {
     199                $this->key_to_unset = $key;
     200                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_unset_exporter_key' ), 20 );
     201        }
     202
     203        /**
     204         * Unset a specified key in the test exporter array.
     205         *
     206         * @param array $exporters List of data exporters.
     207         *
     208         * @return array $exporters List of data exporters.
     209         */
     210        public function filter_unset_exporter_key( $exporters ) {
     211                if ( false === $this->key_to_unset ) {
     212                        $exporters[ self::$exporter_key ] = false;
     213                } elseif ( ! empty( $this->key_to_unset ) ) {
     214                        unset( $exporters[ self::$exporter_key ][ $this->key_to_unset ] );
     215                }
     216
     217                return $exporters;
     218        }
     219
     220        /**
     221         * The function should send an error when the request ID is missing.
     222         *
     223         * @since 5.2.0
     224         */
     225        public function test_error_when_missing_request_id() {
     226                $this->_make_ajax_call(
     227                        array(
     228                                'id' => null, // Missing request ID.
     229                        )
     230                );
     231
     232                $this->assertFalse( $this->_last_response_parsed['success'] );
     233                $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
     234        }
     235
     236        /**
     237         * The function should send an error when the request ID is less than 1.
     238         *
     239         * @since 5.2.0
     240         */
     241        public function test_error_when_invalid_id() {
     242                $this->_make_ajax_call(
     243                        array(
     244                                'id' => -1, // Invalid request ID, less than 1.
     245                        )
     246                );
     247
     248                $this->assertFalse( $this->_last_response_parsed['success'] );
     249                $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
     250        }
     251
     252        /**
     253         * The function should send an error when the current user is missing the required capability.
     254         *
     255         * @since 5.2.0
     256         */
     257        public function test_error_when_current_user_missing_required_capability() {
     258                $this->_setRole( 'author' );
     259
     260                $this->_make_ajax_call();
     261
     262                $this->assertFalse( $this->_last_response_parsed['success'] );
     263                $this->assertFalse( current_user_can( 'export_others_personal_data' ) );
     264                $this->assertSame( 'Sorry, you are not allowed to perform this action.', $this->_last_response_parsed['data'] );
     265        }
     266
     267        /**
     268         * The function should send an error when the nonce does not validate.
     269         *
     270         * @since 5.2.0
     271         */
     272        public function test_failure_with_invalid_nonce() {
     273                $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
     274
     275                $this->_make_ajax_call(
     276                        array(
     277                                'security' => 'invalid-nonce',
     278                        )
     279                );
     280        }
     281
     282        /**
     283         * The function should send an error when the request type is incorrect.
     284         *
     285         * @since 5.2.0
     286         */
     287        public function test_error_when_incorrect_request_type() {
     288                $request_id = wp_create_user_request(
     289                        'erase-request@example.com',
     290                        'remove_personal_data' // Incorrect request type, expects 'export_personal_data'.
     291                );
     292
     293                $this->_make_ajax_call(
     294                        array(
     295                                'security' => wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ),
     296                                'id'       => $request_id,
     297                        )
     298                );
     299
     300                $this->assertFalse( $this->_last_response_parsed['success'] );
     301                $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
     302        }
     303
     304        /**
     305         * The function should send an error when the requester's email address is invalid.
     306         *
     307         * @since 5.2.0
     308         */
     309        public function test_error_when_invalid_email_address() {
     310                wp_update_post(
     311                        array(
     312                                'ID'         => self::$request_id,
     313                                'post_title' => '', // Invalid requester's email address.
     314                        )
     315                );
     316
     317                $this->_make_ajax_call();
     318
     319                $this->assertFalse( $this->_last_response_parsed['success'] );
     320                $this->assertSame( 'A valid email address must be given.', $this->_last_response_parsed['data'] );
     321        }
     322
     323        /**
     324         * The function should send an error when the exporter index is missing.
     325         *
     326         * @since 5.2.0
     327         */
     328        public function test_error_when_missing_exporter_index() {
     329                $this->_make_ajax_call(
     330                        array(
     331                                'exporter' => null, // Missing exporter index.
     332                        )
     333                );
     334
     335                $this->assertFalse( $this->_last_response_parsed['success'] );
     336                $this->assertSame( 'Missing exporter index.', $this->_last_response_parsed['data'] );
     337        }
     338
     339        /**
     340         * The function should send an error when the page index is missing.
     341         *
     342         * @since 5.2.0
     343         */
     344        public function test_error_when_missing_page_index() {
     345                $this->_make_ajax_call(
     346                        array(
     347                                'page' => null, // Missing page index.
     348                        )
     349                );
     350
     351                $this->assertFalse( $this->_last_response_parsed['success'] );
     352                $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
     353        }
     354
     355        /**
     356         * The function should send an error when an exporter has improperly used the `wp_privacy_personal_data_exporters` filter.
     357         *
     358         * @since 5.2.0
     359         */
     360        public function test_error_when_exporter_has_improperly_used_exporters_filter() {
     361                // Improper filter usage: returns false instead of an expected array.
     362                add_filter( 'wp_privacy_personal_data_exporters', '__return_false', 999 );
     363                $this->_make_ajax_call();
     364
     365                $this->assertFalse( $this->_last_response_parsed['success'] );
     366                $this->assertSame( 'An exporter has improperly used the registration filter.', $this->_last_response_parsed['data'] );
     367        }
     368
     369        /**
     370         * The function should send an error when the exporter index is negative.
     371         *
     372         * @since 5.2.0
     373         */
     374        public function test_error_when_negative_exporter_index() {
     375                $this->_make_ajax_call(
     376                        array(
     377                                'exporter' => -1, // Negative exporter index.
     378                        )
     379                );
     380
     381                $this->assertFalse( $this->_last_response_parsed['success'] );
     382                $this->assertSame( 'Exporter index cannot be negative.', $this->_last_response_parsed['data'] );
     383        }
     384
     385        /**
     386         * The function should send an error when the exporter index is out of range.
     387         *
     388         * @since 5.2.0
     389         */
     390        public function test_error_when_exporter_index_out_of_range() {
     391                $this->_make_ajax_call(
     392                        array(
     393                                'exporter' => PHP_INT_MAX, // Out of range exporter index.
     394                        )
     395                );
     396
     397                $this->assertFalse( $this->_last_response_parsed['success'] );
     398                $this->assertSame( 'Exporter index is out of range.', $this->_last_response_parsed['data'] );
     399        }
     400
     401        /**
     402         * The function should send an error when the page index is less than one.
     403         *
     404         * @since 5.2.0
     405         */
     406        public function test_error_when_page_index_less_than_one() {
     407                $this->_make_ajax_call(
     408                        array(
     409                                'page' => 0, // Page index less than one.
     410                        )
     411                );
     412
     413                $this->assertFalse( $this->_last_response_parsed['success'] );
     414                $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
     415        }
     416
     417        /**
     418         * The function should send an error when an exporter is not an array.
     419         *
     420         * @since 5.2.0
     421         */
     422        public function test_error_when_exporter_not_array() {
     423                $this->_unset_exporter_key( false );
     424                $this->_make_ajax_call();
     425
     426                $this->assertFalse( $this->_last_response_parsed['success'] );
     427                $this->assertSame(
     428                        sprintf(
     429                                'Expected an array describing the exporter at index %s.',
     430                                self::$exporter_key
     431                        ),
     432                        $this->_last_response_parsed['data']
     433                );
     434        }
     435
     436        /**
     437         * The function should send an error when an exporter is missing a friendly name.
     438         *
     439         * @since 5.2.0
     440         */
     441        public function test_error_when_exporter_missing_friendly_name() {
     442                $this->_unset_exporter_key( 'exporter_friendly_name' );
     443                $this->_make_ajax_call();
     444
     445                $this->assertFalse( $this->_last_response_parsed['success'] );
     446                $this->assertSame(
     447                        sprintf(
     448                                'Exporter array at index %s does not include a friendly name.',
     449                                self::$exporter_key
     450                        ),
     451                        $this->_last_response_parsed['data']
     452                );
     453        }
     454
     455        /**
     456         * The function should send an error when an exporter is missing a callback.
     457         *
     458         * @since 5.2.0
     459         */
     460        public function test_error_when_exporter_missing_callback() {
     461                $this->_unset_exporter_key( 'callback' );
     462                $this->_make_ajax_call();
     463
     464                $this->assertFalse( $this->_last_response_parsed['success'] );
     465                $this->assertSame(
     466                        sprintf(
     467                                'Exporter does not include a callback: %s.',
     468                                self::$exporter_friendly_name
     469                        ),
     470                        $this->_last_response_parsed['data']
     471                );
     472        }
     473
     474        /**
     475         * The function should send an error when an exporter, at a given index, has an invalid callback.
     476         *
     477         * @since 5.2.0
     478         */
     479        public function test_error_when_exporter_index_invalid_callback() {
     480                $this->_set_exporter_callback( false );
     481                $this->_make_ajax_call();
     482
     483                $this->assertFalse( $this->_last_response_parsed['success'] );
     484                $this->assertSame(
     485                        sprintf(
     486                                'Exporter callback is not a valid callback: %s.',
     487                                self::$exporter_friendly_name
     488                        ),
     489                        $this->_last_response_parsed['data']
     490                );
     491        }
     492
     493        /**
     494         * When an exporter callback returns a WP_Error, it should be passed as the error.
     495         *
     496         * @since 5.2.0
     497         */
     498        public function test_error_when_exporter_callback_returns_wp_error() {
     499                $this->_set_exporter_callback( array( $this, 'callback_return_wp_error' ) );
     500                $this->_make_ajax_call();
     501
     502                $this->assertFalse( $this->_last_response_parsed['success'] );
     503                $this->assertSame( 'passed_message', $this->_last_response_parsed['data'][0]['code'] );
     504                $this->assertSame( 'This is a WP_Error message.', $this->_last_response_parsed['data'][0]['message'] );
     505        }
     506
     507        /**
     508         * Callback for exporter's response.
     509         *
     510         * @since 5.2.0
     511         *
     512         * @param string $email_address The requester's email address.
     513         * @param int    $page          Page number.
     514         * @return WP_Error WP_Error instance.
     515         */
     516        public function callback_return_wp_error( $email_address, $page = 1 ) {
     517                return new WP_Error( 'passed_message', 'This is a WP_Error message.' );
     518        }
     519
     520        /**
     521         * The function should send an error when an exporter, at a given index, is missing an array response.
     522         *
     523         * @since 5.2.0
     524         */
     525        public function test_error_when_exporter_index_invalid_response() {
     526                $this->_set_exporter_callback( '__return_null' );
     527                $this->_make_ajax_call();
     528
     529                $this->assertFalse( $this->_last_response_parsed['success'] );
     530                $this->assertSame(
     531                        sprintf(
     532                                'Expected response as an array from exporter: %s.',
     533                                self::$exporter_friendly_name
     534                        ),
     535                        $this->_last_response_parsed['data']
     536                );
     537        }
     538
     539        /**
     540         * The function should send an error when an exporter is missing data in array response.
     541         *
     542         * @since 5.2.0
     543         */
     544        public function test_error_when_exporter_missing_data_response() {
     545                $this->_set_exporter_callback( array( $this, 'callback_missing_data_response' ) );
     546                $this->_make_ajax_call();
     547
     548                $this->assertFalse( $this->_last_response_parsed['success'] );
     549                $this->assertSame(
     550                        sprintf(
     551                                'Expected data in response array from exporter: %s.',
     552                                self::$exporter_friendly_name
     553                        ),
     554                        $this->_last_response_parsed['data']
     555                );
     556        }
     557
     558        /**
     559         * Callback for exporter's response.
     560         *
     561         * @since 5.2.0
     562         *
     563         * @param string $email_address The requester's email address.
     564         * @param int    $page          Page number.
     565         *
     566         * @return array $return Export data.
     567         */
     568        public function callback_missing_data_response( $email_address, $page = 1 ) {
     569                $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
     570                unset( $response['data'] ); // Missing data part of response.
     571
     572                return $response;
     573        }
     574
     575        /**
     576         * The function should send an error when an exporter is missing 'data' array in array response.
     577         *
     578         * @since 5.2.0
     579         */
     580        public function test_function_should_error_when_exporter_missing_data_array_response() {
     581                $this->_set_exporter_callback( array( $this, 'callback_missing_data_array_response' ) );
     582                $this->_make_ajax_call();
     583
     584                $this->assertFalse( $this->_last_response_parsed['success'] );
     585                $this->assertSame(
     586                        sprintf(
     587                                'Expected data array in response array from exporter: %s.',
     588                                self::$exporter_friendly_name
     589                        ),
     590                        $this->_last_response_parsed['data']
     591                );
     592        }
     593
     594        /**
     595         * Callback for exporter's response.
     596         *
     597         * @since 5.2.0
     598         *
     599         * @param  string $email_address The requester's email address.
     600         * @param  int    $page          Page number.
     601         *
     602         * @return array $return Export data.
     603         */
     604        public function callback_missing_data_array_response( $email_address, $page = 1 ) {
     605                $response         = $this->callback_custom_personal_data_exporter( $email_address, $page );
     606                $response['data'] = false; // Not an array.
     607                return $response;
     608        }
     609
     610        /**
     611         * The function should send an error when an exporter is missing 'done' in array response.
     612         *
     613         * @since 5.2.0
     614         */
     615        public function test_error_when_exporter_missing_done_response() {
     616                $this->_set_exporter_callback( array( $this, 'callback_missing_done_response' ) );
     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 5.2.0
     633         *
     634         * @param string $email_address The requester's email address.
     635         * @param int    $page          Page number.
     636         *
     637         * @return array $return Export data.
     638         */
     639        public function callback_missing_done_response( $email_address, $page = 1 ) {
     640                $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
     641                unset( $response['done'] );
     642
     643                return $response;
     644        }
     645
     646        /**
     647         * The function should successfully send exporter data response when the current user has the required capability.
     648         *
     649         * @since 5.2.0
     650         */
     651        public function test_succeeds_when_current_user_has_required_capability() {
     652                $this->assertTrue( current_user_can( 'export_others_personal_data' ) );
     653
     654                $this->_make_ajax_call();
     655
     656                $this->assertTrue( $this->_last_response_parsed['success'] );
     657                $this->assertSame( 'custom-exporter-item-id', $this->_last_response_parsed['data']['data']['item_id'] );
     658                $this->assertSame( 'Email', $this->_last_response_parsed['data']['data']['data'][0]['name'] );
     659                $this->assertSame( self::$request_email, $this->_last_response_parsed['data']['data']['data'][0]['value'] );
     660        }
     661
     662        /**
     663         * The function should successfully send exporter data response when no items to export.
     664         *
     665         * @since 5.2.0
     666         */
     667        public function test_success_when_no_items_to_export() {
     668
     669                $this->_make_ajax_call( array( 'page' => 2 ) );
     670
     671                $this->assertTrue( $this->_last_response_parsed['success'] );
     672                $this->assertEmpty( $this->_last_response_parsed['data']['data'] );
     673                $this->assertTrue( $this->_last_response_parsed['data']['done'] );
     674        }
     675
     676        /**
     677         * The function's output should be filterable with the `wp_privacy_personal_data_export_page` filter.
     678         *
     679         * @since 5.2.0
     680         */
     681        public function test_output_should_be_filterable() {
     682                add_filter( 'wp_privacy_personal_data_export_page', array( $this, 'filter_exporter_data_response' ), 20, 7 );
     683                $this->_make_ajax_call();
     684
     685                $expected_group_label = sprintf(
     686                        '%s-%s-%s-%s-%s-%s',
     687                        self::$exporter,
     688                        self::$page,
     689                        self::$request_email,
     690                        self::$request_id,
     691                        self::$send_as_email,
     692                        self::$exporter_key
     693                );
     694
     695                $this->assertTrue( $this->_last_response_parsed['success'] );
     696                $this->assertSame( $expected_group_label, $this->_last_response_parsed['data']['group_label'] );
     697                $this->assertSame( 'filtered_group_id', $this->_last_response_parsed['data']['group_id'] );
     698                $this->assertSame( 'filtered_item_id', $this->_last_response_parsed['data']['item_id'] );
     699                $this->assertSame( 'filtered_name', $this->_last_response_parsed['data']['data'][0]['name'] );
     700                $this->assertSame( 'filtered_value', $this->_last_response_parsed['data']['data'][0]['value'] );
     701        }
     702
     703        /**
     704         * Filter exporter's data response.
     705         *
     706         * @since 5.2.0
     707         *
     708         * @param array  $response        The personal data for the given exporter and page.
     709         * @param int    $exporter_index  The index of the exporter that provided this data.
     710         * @param string $email_address   The email address associated with this personal data.
     711         * @param int    $page            The page for this response.
     712         * @param int    $request_id      The privacy request post ID associated with this request.
     713         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
     714         * @param string $exporter_key    The key (slug) of the exporter that provided this data.
     715         *
     716         * @return array $response The personal data for the given exporter and page.
     717         */
     718        public function filter_exporter_data_response( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) {
     719                $group_label                  = sprintf(
     720                        '%s-%s-%s-%s-%s-%s',
     721                        $exporter_index,
     722                        $page,
     723                        $email_address,
     724                        $request_id,
     725                        $send_as_email,
     726                        $exporter_key
     727                );
     728                $response['group_label']      = $group_label;
     729                $response['group_id']         = 'filtered_group_id';
     730                $response['item_id']          = 'filtered_item_id';
     731                $response['data'][0]['name']  = 'filtered_name';
     732                $response['data'][0]['value'] = 'filtered_value';
     733
     734                return $response;
     735        }
     736
     737        /**
     738         * Filter to register a custom personal data exporter.
     739         *
     740         * @since 5.2.0
     741         *
     742         * @param array $exporters An array of personal data exporters.
     743         *
     744         * @return array $exporters An array of personal data exporters.
     745         */
     746        public function filter_register_custom_personal_data_exporter( $exporters ) {
     747                $exporters[ self::$exporter_key ] = array(
     748                        'exporter_friendly_name' => self::$exporter_friendly_name,
     749                        'callback'               => array( $this, 'callback_custom_personal_data_exporter' ),
     750                );
     751                return $exporters;
     752        }
     753
     754        /**
     755         * Callback for a custom personal data exporter.
     756         *
     757         * @since 5.2.0
     758         *
     759         * @param string $email_address The requester's email address.
     760         * @param int    $page          Page number.
     761         *
     762         * @return array $response Export data response.
     763         */
     764        public function callback_custom_personal_data_exporter( $email_address, $page = 1 ) {
     765                $data_to_export = array();
     766
     767                if ( 1 === $page ) {
     768                        $data_to_export = array(
     769                                'group_id'    => self::$exporter_key . '-group-id',
     770                                'group_label' => self::$exporter_key . '-group-label',
     771                                'item_id'     => self::$exporter_key . '-item-id',
     772                                'data'        => array(
     773                                        array(
     774                                                'name'  => 'Email',
     775                                                'value' => $email_address,
     776                                        ),
     777                                ),
     778                        );
     779                }
     780
     781                return array(
     782                        'data' => $data_to_export,
     783                        'done' => true,
     784                );
     785        }
     786
     787        /**
     788         * Helper function for ajax handler.
     789         *
     790         * @since 5.2.0
     791         *
     792         * @param array $args Ajax request arguments.
     793         */
     794        protected function _make_ajax_call( $args = array() ) {
     795                $this->_last_response_parsed = null;
     796                $this->_last_response        = '';
     797
     798                $defaults = array(
     799                        'action'      => self::$action,
     800                        'security'    => wp_create_nonce( self::$action . '-' . self::$request_id ),
     801                        'exporter'    => self::$exporter,
     802                        'page'        => self::$page,
     803                        'sendAsEmail' => self::$send_as_email,
     804                        'id'          => self::$request_id,
     805                );
     806
     807                $_POST = wp_parse_args( $args, $defaults );
     808
     809                try {
     810                        $this->_handleAjax( self::$action );
     811                } catch ( WPAjaxDieContinueException $e ) {
     812                        unset( $e );
     813                }
     814
     815                if ( $this->_last_response ) {
     816                        $this->_last_response_parsed = json_decode( $this->_last_response, true );
     817                }
     818        }
     819}