WordPress.org

Make WordPress Core

Changeset 47278


Ignore:
Timestamp:
02/11/2020 09:26:25 PM (2 weeks ago)
Author:
SergeyBiryukov
Message:

Privacy: Add a table of contents to Personal Data Export report for easier navigation.

Props xkon, garrett-eclipse, birgire, karmatosed.
Fixes #46894.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/privacy-tools.php

    r47245 r47278  
    217217 *
    218218 * @since 4.9.6
     219 * @since 5.4.0 Added the `$group_id` and `$groups_count` parameters.
    219220 *
    220221 * @param array $group_data {
     
    233234 *     }
    234235 * }
    235  * @return string The HTML for this group and its items.
    236  */
    237 function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
    238     $group_html  = '<h2>';
     236 * @param string  $group_id     The group identifier.
     237 * @param int     $groups_count The number of all groups
     238 * @return string $group_html   The HTML for this group and its items.
     239 */
     240function wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id = '', $groups_count = 1 ) {
     241    $group_id_attr = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id );
     242
     243    $group_html  = '<h2 id="' . esc_attr( $group_id_attr ) . '">';
    239244    $group_html .= esc_html( $group_data['group_label'] );
    240245
     
    271276        $group_html .= '</tbody>';
    272277        $group_html .= '</table>';
     278    }
     279
     280    if ( 1 < $groups_count ) {
     281        $group_html .= '<div class="return_to_top">';
     282        $group_html .= '<a href="#top">' . esc_html__( '&uarr; Return to top' ) . '</a>';
     283        $group_html .= '</div>';
    273284    }
    274285
     
    374385    $groups = array_merge( array( 'about' => $about_group ), $groups );
    375386
     387    $groups_count = count( $groups );
     388
    376389    // Convert the groups to JSON format.
    377390    $groups_json = wp_json_encode( $groups );
     
    411424    fwrite( $file, 'td { padding: 5px; }' );
    412425    fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' );
     426    fwrite( $file, '.return_to_top { text-align:right; }' );
    413427    fwrite( $file, '</style>' );
    414428    fwrite( $file, '<title>' );
     
    417431    fwrite( $file, "</head>\n" );
    418432    fwrite( $file, "<body>\n" );
    419     fwrite( $file, '<h1>' . esc_html__( 'Personal Data Export' ) . '</h1>' );
     433    fwrite( $file, '<h1 id="top">' . esc_html__( 'Personal Data Export' ) . '</h1>' );
     434
     435    // Create TOC.
     436    if ( 1 < $groups_count ) {
     437        fwrite( $file, '<div id="table_of_contents">' );
     438        fwrite( $file, '<h2>' . esc_html__( 'Table of Contents' ) . '</h2>' );
     439        fwrite( $file, '<ul>' );
     440        foreach ( (array) $groups as $group_id => $group_data ) {
     441            $group_label       = esc_html( $group_data['group_label'] );
     442            $group_id_attr     = sanitize_title_with_dashes( $group_data['group_label'] . '-' . $group_id );
     443            $group_items_count = count( (array) $group_data['items'] );
     444            if ( $group_items_count > 1 ) {
     445                $group_label .= sprintf( ' <span class="count">(%d)</span>', $group_items_count );
     446            }
     447            fwrite( $file, '<li>' );
     448            fwrite( $file, '<a href="#' . esc_attr( $group_id_attr ) . '">' . $group_label . '</a>' );
     449            fwrite( $file, '</li>' );
     450        }
     451        fwrite( $file, '</ul>' );
     452        fwrite( $file, '</div>' );
     453    }
    420454
    421455    // Now, iterate over every group in $groups and have the formatter render it in HTML.
    422456    foreach ( (array) $groups as $group_id => $group_data ) {
    423         fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) );
     457        fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data, $group_id, $groups_count ) );
    424458    }
    425459
  • trunk/tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php

    r47245 r47278  
    261261        $request         = wp_get_user_request( self::$export_request_id );
    262262
    263         $this->assertContains( '<h1>Personal Data Export</h1>', $report_contents );
    264         $this->assertContains( '<h2>About</h2>', $report_contents );
     263        $this->assertContains( '<h1 id="top">Personal Data Export</h1>', $report_contents );
     264        $this->assertContains( '<h2 id="about-about">About</h2>', $report_contents );
    265265        $this->assertContains( $request->email, $report_contents );
    266266    }
     
    295295        $this->assertContains( '"about"', $report_contents_json );
    296296    }
     297
     298    /**
     299     * Test the export HTML file containing one export group has no table of contents.
     300     *
     301     * @ticket 46894
     302     */
     303    public function test_single_group_export_no_toc_or_return_to_top() {
     304        $this->expectOutputString( '' );
     305        wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     306        $this->assertTrue( file_exists( $this->export_file_name ) );
     307
     308        $report_dir = trailingslashit( self::$exports_dir . 'test_contents' );
     309        mkdir( $report_dir );
     310
     311        $zip        = new ZipArchive();
     312        $opened_zip = $zip->open( $this->export_file_name );
     313        $this->assertTrue( $opened_zip );
     314
     315        $zip->extractTo( $report_dir );
     316        $zip->close();
     317        $this->assertTrue( file_exists( $report_dir . 'index.html' ) );
     318
     319        $report_contents = file_get_contents( $report_dir . 'index.html' );
     320        $request         = wp_get_user_request( self::$export_request_id );
     321
     322        $this->assertNotContains( '<div id="table_of_contents">', $report_contents );
     323        $this->assertNotContains( '<div class="return_to_top">', $report_contents );
     324        $this->assertContains( $request->email, $report_contents );
     325    }
     326
     327    /**
     328     * Test the export HTML file containing ore than one export group has a table of contents.
     329     *
     330     * @ticket 46894
     331     */
     332    public function test_multiple_group_export_has_toc_and_return_to_top() {
     333        $this->expectOutputString( '' );
     334
     335        // Setup Export Data to contain multiple groups
     336        $export_data_grouped = array(
     337            'user' => array(
     338                'group_label'       => 'User',
     339                'group_description' => 'User&#8217;s profile data.',
     340                'items'             => array(
     341                    'user-1' => array(
     342                        array(
     343                            'name'  => 'User ID',
     344                            'value' => 1,
     345                        ),
     346                        array(
     347                            'name'  => 'User Login Name',
     348                            'value' => 'user_login',
     349                        ),
     350                        array(
     351                            'name'  => 'User Nice Name',
     352                            'value' => 'User Name',
     353                        ),
     354                        array(
     355                            'name'  => 'User Email',
     356                            'value' => 'export-requester@example.com',
     357                        ),
     358                        array(
     359                            'name'  => 'User Registration Date',
     360                            'value' => '2020-01-31 19:29:29',
     361                        ),
     362                        array(
     363                            'name'  => 'User Display Name',
     364                            'value' => 'User Name',
     365                        ),
     366                        array(
     367                            'name'  => 'User Nickname',
     368                            'value' => 'User',
     369                        ),
     370                    ),
     371                ),
     372            ),
     373        );
     374        update_post_meta( self::$export_request_id, '_export_data_grouped', $export_data_grouped );
     375
     376        // Generate Export File
     377        wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     378        $this->assertTrue( file_exists( $this->export_file_name ) );
     379
     380        // Cleam-up for subsequent tests
     381        update_post_meta( self::$export_request_id, '_export_data_grouped', array() );
     382
     383        $report_dir = trailingslashit( self::$exports_dir . 'test_contents' );
     384        mkdir( $report_dir );
     385
     386        $zip        = new ZipArchive();
     387        $opened_zip = $zip->open( $this->export_file_name );
     388        $this->assertTrue( $opened_zip );
     389
     390        $zip->extractTo( $report_dir );
     391        $zip->close();
     392        $this->assertTrue( file_exists( $report_dir . 'index.html' ) );
     393
     394        $report_contents = file_get_contents( $report_dir . 'index.html' );
     395        $request         = wp_get_user_request( self::$export_request_id );
     396
     397        $this->assertContains( '<div id="table_of_contents">', $report_contents );
     398        $this->assertContains( '<h2 id="user-user">User</h2>', $report_contents );
     399        $this->assertContains( '<div class="return_to_top">', $report_contents );
     400        $this->assertContains( $request->email, $report_contents );
     401    }
     402
     403    /**
     404     * Test the export HTML file containing multiple export groups with multiple group items
     405     * has a table of contents with group count.
     406     *
     407     * @ticket 46894
     408     */
     409    public function test_multiple_group_export_multiple_items_group_count_in_toc() {
     410        $this->expectOutputString( '' );
     411
     412        // Setup Export Data to contain multiple groups
     413        $export_data_grouped = array(
     414            'user'     => array(
     415                'group_label'       => 'User',
     416                'group_description' => 'User&#8217;s profile data.',
     417                'items'             => array(
     418                    'user-1' => array(
     419                        array(
     420                            'name'  => 'User ID',
     421                            'value' => 1,
     422                        ),
     423                        array(
     424                            'name'  => 'User Login Name',
     425                            'value' => 'user_login',
     426                        ),
     427                        array(
     428                            'name'  => 'User Nice Name',
     429                            'value' => 'User Name',
     430                        ),
     431                        array(
     432                            'name'  => 'User Email',
     433                            'value' => 'export-requester@example.com',
     434                        ),
     435                        array(
     436                            'name'  => 'User Registration Date',
     437                            'value' => '2020-01-31 19:29:29',
     438                        ),
     439                        array(
     440                            'name'  => 'User Display Name',
     441                            'value' => 'User Name',
     442                        ),
     443                        array(
     444                            'name'  => 'User Nickname',
     445                            'value' => 'User',
     446                        ),
     447                    ),
     448                ),
     449            ),
     450            'comments' => array(
     451                'group_label'       => 'Comments',
     452                'group_description' => 'User&#8217;s comment data.',
     453                'items'             => array(
     454                    'comment-2' => array(
     455                        array(
     456                            'name'  => 'Comment Author',
     457                            'value' => 'User Name',
     458                        ),
     459                        array(
     460                            'name'  => 'Comment Author Email',
     461                            'value' => 'export-requester@example.com',
     462                        ),
     463                        array(
     464                            'name'  => 'Comment Author IP',
     465                            'value' => '::1',
     466                        ),
     467                        array(
     468                            'name'  => 'Comment Author User Agent',
     469                            'value' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
     470                        ),
     471                        array(
     472                            'name'  => 'Comment Date',
     473                            'value' => '2020-01-31 19:55:19',
     474                        ),
     475                        array(
     476                            'name'  => 'Comment Content',
     477                            'value' => 'Test',
     478                        ),
     479                        array(
     480                            'name'  => 'Comment URL',
     481                            'value' => '<a href="http://localhost:8888/46894/2020/01/31/hello-world/#comment-2" target="_blank" rel="noreferrer noopener">http://localhost:8888/46894/2020/01/31/hello-world/#comment-2</a>',
     482                        ),
     483                    ),
     484                    'comment-3' => array(
     485                        array(
     486                            'name'  => 'Comment Author',
     487                            'value' => 'User Name',
     488                        ),
     489                        array(
     490                            'name'  => 'Comment Author Email',
     491                            'value' => 'export-requester@example.com',
     492                        ),
     493                        array(
     494                            'name'  => 'Comment Author IP',
     495                            'value' => '::1',
     496                        ),
     497                        array(
     498                            'name'  => 'Comment Author User Agent',
     499                            'value' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
     500                        ),
     501                        array(
     502                            'name'  => 'Comment Date',
     503                            'value' => '2020-01-31 20:55:19',
     504                        ),
     505                        array(
     506                            'name'  => 'Comment Content',
     507                            'value' => 'Test #2',
     508                        ),
     509                        array(
     510                            'name'  => 'Comment URL',
     511                            'value' => '<a href="http://localhost:8888/46894/2020/01/31/hello-world/#comment-3" target="_blank" rel="noreferrer noopener">http://localhost:8888/46894/2020/01/31/hello-world/#comment-3</a>',
     512                        ),
     513                    ),
     514                ),
     515            ),
     516        );
     517        update_post_meta( self::$export_request_id, '_export_data_grouped', $export_data_grouped );
     518
     519        // Generate Export File
     520        wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     521        $this->assertTrue( file_exists( $this->export_file_name ) );
     522
     523        // Cleam-up for subsequent tests
     524        update_post_meta( self::$export_request_id, '_export_data_grouped', array() );
     525
     526        $report_dir = trailingslashit( self::$exports_dir . 'test_contents' );
     527        mkdir( $report_dir );
     528
     529        $zip        = new ZipArchive();
     530        $opened_zip = $zip->open( $this->export_file_name );
     531        $this->assertTrue( $opened_zip );
     532
     533        $zip->extractTo( $report_dir );
     534        $zip->close();
     535        $this->assertTrue( file_exists( $report_dir . 'index.html' ) );
     536
     537        $report_contents = file_get_contents( $report_dir . 'index.html' );
     538        $request         = wp_get_user_request( self::$export_request_id );
     539
     540        $this->assertContains( '<div id="table_of_contents">', $report_contents );
     541        $this->assertContains( '<a href="#comments-comments">Comments <span class="count">(2)</span></a>', $report_contents );
     542        $this->assertContains( $request->email, $report_contents );
     543    }
     544
     545    /**
     546     * Test the export HTML file containing multiple export groups with no multiple group items
     547     * has a table of contents without group count.
     548     *
     549     * @ticket 46894
     550     */
     551    public function test_multiple_group_export_single_items_no_group_count_in_toc() {
     552        $this->expectOutputString( '' );
     553
     554        // Setup Export Data to contain multiple groups
     555        $export_data_grouped = array(
     556            'user'     => array(
     557                'group_label'       => 'User',
     558                'group_description' => 'User&#8217;s profile data.',
     559                'items'             => array(
     560                    'user-1' => array(
     561                        array(
     562                            'name'  => 'User ID',
     563                            'value' => 1,
     564                        ),
     565                        array(
     566                            'name'  => 'User Login Name',
     567                            'value' => 'user_login',
     568                        ),
     569                        array(
     570                            'name'  => 'User Nice Name',
     571                            'value' => 'User Name',
     572                        ),
     573                        array(
     574                            'name'  => 'User Email',
     575                            'value' => 'export-requester@example.com',
     576                        ),
     577                        array(
     578                            'name'  => 'User Registration Date',
     579                            'value' => '2020-01-31 19:29:29',
     580                        ),
     581                        array(
     582                            'name'  => 'User Display Name',
     583                            'value' => 'User Name',
     584                        ),
     585                        array(
     586                            'name'  => 'User Nickname',
     587                            'value' => 'User',
     588                        ),
     589                    ),
     590                ),
     591            ),
     592            'comments' => array(
     593                'group_label'       => 'Comments',
     594                'group_description' => 'User&#8217;s comment data.',
     595                'items'             => array(
     596                    'comment-2' => array(
     597                        array(
     598                            'name'  => 'Comment Author',
     599                            'value' => 'User Name',
     600                        ),
     601                        array(
     602                            'name'  => 'Comment Author Email',
     603                            'value' => 'export-requester@example.com',
     604                        ),
     605                        array(
     606                            'name'  => 'Comment Author IP',
     607                            'value' => '::1',
     608                        ),
     609                        array(
     610                            'name'  => 'Comment Author User Agent',
     611                            'value' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
     612                        ),
     613                        array(
     614                            'name'  => 'Comment Date',
     615                            'value' => '2020-01-31 19:55:19',
     616                        ),
     617                        array(
     618                            'name'  => 'Comment Content',
     619                            'value' => 'Test',
     620                        ),
     621                        array(
     622                            'name'  => 'Comment URL',
     623                            'value' => '<a href="http://localhost:8888/46894/2020/01/31/hello-world/#comment-2" target="_blank" rel="noreferrer noopener">http://localhost:8888/46894/2020/01/31/hello-world/#comment-2</a>',
     624                        ),
     625                    ),
     626                ),
     627            ),
     628        );
     629        update_post_meta( self::$export_request_id, '_export_data_grouped', $export_data_grouped );
     630
     631        // Generate Export File
     632        wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     633        $this->assertTrue( file_exists( $this->export_file_name ) );
     634
     635        // Cleam-up for subsequent tests
     636        update_post_meta( self::$export_request_id, '_export_data_grouped', array() );
     637
     638        $report_dir = trailingslashit( self::$exports_dir . 'test_contents' );
     639        mkdir( $report_dir );
     640
     641        $zip        = new ZipArchive();
     642        $opened_zip = $zip->open( $this->export_file_name );
     643        $this->assertTrue( $opened_zip );
     644
     645        $zip->extractTo( $report_dir );
     646        $zip->close();
     647        $this->assertTrue( file_exists( $report_dir . 'index.html' ) );
     648
     649        $report_contents = file_get_contents( $report_dir . 'index.html' );
     650        $request         = wp_get_user_request( self::$export_request_id );
     651
     652        $this->assertContains( '<div id="table_of_contents">', $report_contents );
     653        $this->assertNotContains( '<span class="count">', $report_contents );
     654        $this->assertContains( $request->email, $report_contents );
     655
     656    }
    297657}
  • trunk/tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportGroupHtml.php

    r46586 r47278  
    4040        );
    4141
    42         $actual                = wp_privacy_generate_personal_data_export_group_html( $data );
     42        $actual                = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
    4343        $expected_table_markup = '<table><tbody><tr><th>Field 1 Name</th><td>Field 1 Value</td></tr><tr><th>Field 2 Name</th><td>Field 2 Value</td></tr></tbody></table>';
    4444
    45         $this->assertContains( '<h2>Test Data Group</h2>', $actual );
     45        $this->assertContains( '<h2 id="test-data-group-test-data-group">Test Data Group</h2>', $actual );
    4646        $this->assertContains( $expected_table_markup, $actual );
    4747    }
     
    8080        );
    8181
    82         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
    83 
    84         $this->assertContains( '<h2>Test Data Group', $actual );
     82        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
     83
     84        $this->assertContains( '<h2 id="test-data-group-test-data-group">Test Data Group', $actual );
    8585        $this->assertContains( '<td>Field 1 Value', $actual );
    8686        $this->assertContains( '<td>Another Field 1 Value', $actual );
     
    118118        );
    119119
    120         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
     120        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
    121121
    122122        $this->assertContains( '<a href="http://wordpress.org">http://wordpress.org</a>', $actual );
     
    132132    public function test_group_labels_escaped() {
    133133        $data = array(
    134             'group_label' => '<div>Escape HTML in group lavels</div>',
     134            'group_label' => '<div>Escape HTML in group labels</div>',
    135135            'items'       => array(),
    136136        );
    137137
    138         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
    139 
    140         $this->assertContains( '<h2>&lt;div&gt;Escape HTML in group lavels&lt;/div&gt;</h2>', $actual );
     138        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'escape-html-in-group-labels', 2 );
     139
     140        $this->assertContains( '<h2 id="escape-html-in-group-labels-escape-html-in-group-labels">&lt;div&gt;Escape HTML in group labels&lt;/div&gt;</h2>', $actual );
    141141    }
    142142
     
    163163        );
    164164
    165         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
    166 
     165        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
    167166        $this->assertContains( $data['items'][0]['links']['value'], $actual );
    168167        $this->assertContains( $data['items'][0]['formatting']['value'], $actual );
     
    191190        );
    192191
    193         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
     192        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
    194193
    195194        $this->assertNotContains( $data['items'][0]['scripts']['value'], $actual );
     
    224223        );
    225224
    226         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
    227 
    228         $this->assertContains( '<h2>Test Data Group', $actual );
     225        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
     226
     227        $this->assertContains( '<h2 id="test-data-group-test-data-group">Test Data Group', $actual );
    229228        $this->assertContains( '<span class="count">(2)</span></h2>', $actual );
    230229        $this->assertSame( 2, substr_count( $actual, '<table>' ) );
     
    249248        );
    250249
    251         $actual = wp_privacy_generate_personal_data_export_group_html( $data );
    252 
    253         $this->assertContains( '<h2>Test Data Group</h2>', $actual );
     250        $actual = wp_privacy_generate_personal_data_export_group_html( $data, 'test-data-group', 2 );
     251
     252        $this->assertContains( '<h2 id="test-data-group-test-data-group">Test Data Group</h2>', $actual );
    254253        $this->assertNotContains( '<span class="count">', $actual );
    255254        $this->assertSame( 1, substr_count( $actual, '<table>' ) );
Note: See TracChangeset for help on using the changeset viewer.