Make WordPress Core


Ignore:
Timestamp:
11/09/2016 07:02:53 AM (10 years ago)
Author:
westonruter
Message:

Customize: Store modifying user ID with setting change written into changeset and restore current user when setting is being saved.

Restoring the current user context when saving a setting ensures filters apply as expected, such as Kses. When a user is not associated with a given setting change, continue to override capability to be exist when saving. Skip overwriting setting values in a changeset that have not changed, facilitating concurrent editing and amending a changeset by a user with fewer privileges.

See #30937.
Fixes #38705.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/customize/manager.php

    r39180 r39181  
    430430                        'changeset_uuid' => $uuid,
    431431                ) );
     432                $wp_customize = $manager;
    432433                $manager->register_controls();
    433434                $manager->set_post_value( 'blogname', 'Changeset Title' );
    434435                $manager->set_post_value( 'blogdescription', 'Changeset Tagline' );
    435436
     437                $pre_saved_data = array(
     438                        'blogname' => array(
     439                                'value' => 'Overridden Changeset Title',
     440                        ),
     441                        'blogdescription' => array(
     442                                'custom' => 'something',
     443                        ),
     444                );
    436445                $r = $manager->save_changeset_post( array(
    437446                        'status' => 'auto-draft',
    438447                        'title' => 'Auto Draft',
    439448                        'date_gmt' => '2010-01-01 00:00:00',
    440                         'data' => array(
    441                                 'blogname' => array(
    442                                         'value' => 'Overridden Changeset Title',
    443                                 ),
    444                                 'blogdescription' => array(
    445                                         'custom' => 'something',
    446                                 ),
    447                         ),
     449                        'data' => $pre_saved_data,
    448450                ) );
    449451                $this->assertInternalType( 'array', $r );
     
    455457                $saved_data = json_decode( get_post( $post_id )->post_content, true );
    456458                $this->assertEquals( $manager->unsanitized_post_values(), wp_list_pluck( $saved_data, 'value' ) );
    457                 $this->assertEquals( 'Overridden Changeset Title', $saved_data['blogname']['value'] );
    458                 $this->assertEquals( 'something', $saved_data['blogdescription']['custom'] );
     459                $this->assertEquals( $pre_saved_data['blogname']['value'], $saved_data['blogname']['value'] );
     460                $this->assertEquals( $pre_saved_data['blogdescription']['custom'], $saved_data['blogdescription']['custom'] );
     461                foreach ( $saved_data as $setting_id => $setting_params ) {
     462                        $this->assertArrayHasKey( 'type', $setting_params );
     463                        $this->assertEquals( 'option', $setting_params['type'] );
     464                        $this->assertArrayHasKey( 'user_id', $setting_params );
     465                        $this->assertEquals( self::$admin_user_id, $setting_params['user_id'] );
     466                }
    459467                $this->assertEquals( 'Auto Draft', get_post( $post_id )->post_title );
    460468                $this->assertEquals( 'auto-draft', get_post( $post_id )->post_status );
     
    512520                        'changeset_uuid' => $uuid,
    513521                ) );
     522                $wp_customize = $manager;
    514523                $manager->register_controls(); // That is, register settings.
    515524                $r = $manager->save_changeset_post( array(
     
    564573
    565574                $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
    566                 $manager->register_controls();
     575                do_action( 'customize_register', $wp_customize );
    567576                $manager->add_setting( 'scratchpad', array(
    568577                        'type' => 'option',
     
    570579                ) );
    571580                $manager->get_setting( 'blogname' )->capability = 'exist';
     581                $original_capabilities = wp_list_pluck( $manager->settings(), 'capability' );
    572582                wp_set_current_user( self::$subscriber_user_id );
    573583                $r = $manager->save_changeset_post( array(
     
    585595                $this->assertEquals( 'Do it live \o/', get_option( 'blogname' ) );
    586596                $this->assertEquals( 'trash', get_post_status( $post_id ) ); // Auto-trashed.
     597                $this->assertEquals( $original_capabilities, wp_list_pluck( $manager->settings(), 'capability' ) );
    587598                $this->assertContains( '<script>', get_post( $post_id )->post_content );
    588599                $this->assertEquals( $manager->changeset_uuid(), get_post( $post_id )->post_name, 'Expected that the "__trashed" suffix to not be added.' );
     
    599610                $uuid = wp_generate_uuid4();
    600611                $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
    601                 $manager->register_controls();
     612                do_action( 'customize_register', $manager );
    602613
    603614                $manager->set_post_value( 'blogname', 'Hello Surface' );
     
    661672         */
    662673        function test_save_changeset_post_with_theme_activation() {
     674                global $wp_customize;
    663675                wp_set_current_user( self::$admin_user_id );
    664676
     
    677689                        'theme' => $preview_theme,
    678690                ) );
    679                 $manager->register_controls();
    680                 $GLOBALS['wp_customize'] = $manager;
     691                $wp_customize = $manager;
     692                do_action( 'customize_register', $manager );
    681693
    682694                $manager->set_post_value( 'blogname', 'Hello Preview Theme' );
     
    687699                $this->assertEquals( $preview_theme, get_stylesheet() );
    688700                $this->assertEquals( 'Hello Preview Theme', get_option( 'blogname' ) );
     701        }
     702
     703        /**
     704         * Test saving changesets with varying users and capabilities.
     705         *
     706         * @ticket 38705
     707         * @covers WP_Customize_Manager::save_changeset_post()
     708         */
     709        function test_save_changeset_post_with_varying_users() {
     710                global $wp_customize;
     711
     712                add_theme_support( 'custom-background' );
     713                wp_set_current_user( self::$admin_user_id );
     714                $other_admin_user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
     715
     716                $uuid = wp_generate_uuid4();
     717                $manager = new WP_Customize_Manager( array(
     718                        'changeset_uuid' => $uuid,
     719                ) );
     720                $wp_customize = $manager;
     721                do_action( 'customize_register', $manager );
     722                $manager->add_setting( 'scratchpad', array(
     723                        'type' => 'option',
     724                        'capability' => 'exist',
     725                ) );
     726
     727                // Create initial set of
     728                $r = $manager->save_changeset_post( array(
     729                        'status' => 'auto-draft',
     730                        'data' => array(
     731                                'blogname' => array(
     732                                        'value' => 'Admin 1 Title',
     733                                ),
     734                                'scratchpad' => array(
     735                                        'value' => 'Admin 1 Scratch',
     736                                ),
     737                                'background_color' => array(
     738                                        'value' => '#000000',
     739                                ),
     740                        ),
     741                ) );
     742                $this->assertInternalType( 'array', $r );
     743                $this->assertEquals(
     744                        array_fill_keys( array( 'blogname', 'scratchpad', 'background_color' ), true ),
     745                        $r['setting_validities']
     746                );
     747                $post_id = $manager->find_changeset_post_id( $uuid );
     748                $data = json_decode( get_post( $post_id )->post_content, true );
     749                $this->assertEquals( self::$admin_user_id, $data['blogname']['user_id'] );
     750                $this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
     751                $this->assertEquals( self::$admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     752
     753                // Attempt to save just one setting under a different user.
     754                wp_set_current_user( $other_admin_user_id );
     755                $r = $manager->save_changeset_post( array(
     756                        'status' => 'auto-draft',
     757                        'data' => array(
     758                                'blogname' => array(
     759                                        'value' => 'Admin 2 Title',
     760                                ),
     761                                'background_color' => array(
     762                                        'value' => '#FFFFFF',
     763                                ),
     764                        ),
     765                ) );
     766                $this->assertInternalType( 'array', $r );
     767                $this->assertEquals(
     768                        array_fill_keys( array( 'blogname', 'background_color' ), true ),
     769                        $r['setting_validities']
     770                );
     771                $data = json_decode( get_post( $post_id )->post_content, true );
     772                $this->assertEquals( 'Admin 2 Title', $data['blogname']['value'] );
     773                $this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'] );
     774                $this->assertEquals( 'Admin 1 Scratch', $data['scratchpad']['value'] );
     775                $this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
     776                $this->assertEquals( '#FFFFFF', $data[ $this->manager->get_stylesheet() . '::background_color' ]['value'] );
     777                $this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     778
     779                // Attempt to save now as under-privileged user.
     780                $r = $manager->save_changeset_post( array(
     781                        'status' => 'auto-draft',
     782                        'data' => array(
     783                                'scratchpad' => array(
     784                                        'value' => 'Subscriber Scratch',
     785                                ),
     786                        ),
     787                        'user_id' => self::$subscriber_user_id,
     788                ) );
     789                $this->assertInternalType( 'array', $r );
     790                $this->assertEquals(
     791                        array_fill_keys( array( 'scratchpad' ), true ),
     792                        $r['setting_validities']
     793                );
     794                $data = json_decode( get_post( $post_id )->post_content, true );
     795                $this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'] );
     796                $this->assertEquals( self::$subscriber_user_id, $data['scratchpad']['user_id'] );
     797                $this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     798
     799                // Manually update the changeset so that the user_id context is not included.
     800                $data = json_decode( get_post( $post_id )->post_content, true );
     801                $data['blogdescription']['value'] = 'Programmatically-supplied Tagline';
     802                wp_update_post( wp_slash( array( 'ID' => $post_id, 'post_content' => wp_json_encode( $data ) ) ) );
     803
     804                // Ensure the modifying user set as the current user when each is saved, simulating WP Cron envronment.
     805                wp_set_current_user( 0 );
     806                $save_counts = array();
     807                foreach ( array_keys( $data ) as $setting_id ) {
     808                        $setting_id = preg_replace( '/^.+::/', '', $setting_id );
     809                        $save_counts[ $setting_id ] = did_action( sprintf( 'customize_save_%s', $setting_id ) );
     810                }
     811                $this->filtered_setting_current_user_ids = array();
     812                foreach ( $manager->settings() as $setting ) {
     813                        add_filter( sprintf( 'customize_sanitize_%s', $setting->id ), array( $this, 'filter_customize_setting_to_log_current_user' ), 10, 2 );
     814                }
     815                wp_update_post( array( 'ID' => $post_id, 'post_status' => 'publish' ) );
     816                foreach ( array_keys( $data ) as $setting_id ) {
     817                        $setting_id = preg_replace( '/^.+::/', '', $setting_id );
     818                        $this->assertEquals( $save_counts[ $setting_id ] + 1, did_action( sprintf( 'customize_save_%s', $setting_id ) ), $setting_id );
     819                }
     820                $this->assertEqualSets( array( 'blogname', 'blogdescription', 'background_color', 'scratchpad' ), array_keys( $this->filtered_setting_current_user_ids ) );
     821                $this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['blogname'] );
     822                $this->assertEquals( 0, $this->filtered_setting_current_user_ids['blogdescription'] );
     823                $this->assertEquals( self::$subscriber_user_id, $this->filtered_setting_current_user_ids['scratchpad'] );
     824                $this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['background_color'] );
     825                $this->assertEquals( 'Subscriber Scratch', get_option( 'scratchpad' ) );
     826        }
     827
     828        /**
     829         * Test writing changesets and publishing with users who can unfiltered_html and those who cannot.
     830         *
     831         * @ticket 38705
     832         * @covers WP_Customize_Manager::save_changeset_post()
     833         */
     834        function test_save_changeset_post_with_varying_unfiltered_html_cap() {
     835                global $wp_customize;
     836                grant_super_admin( self::$admin_user_id );
     837                $this->assertTrue( user_can( self::$admin_user_id, 'unfiltered_html' ) );
     838                $this->assertFalse( user_can( self::$subscriber_user_id, 'unfiltered_html' ) );
     839                wp_set_current_user( 0 );
     840                add_action( 'customize_register', array( $this, 'register_scratchpad_setting' ) );
     841
     842                // Attempt scratchpad with user who has unfiltered_html.
     843                update_option( 'scratchpad', '' );
     844                $wp_customize = new WP_Customize_Manager();
     845                do_action( 'customize_register', $wp_customize );
     846                $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     847                $wp_customize->save_changeset_post( array(
     848                        'status' => 'auto-draft',
     849                        'user_id' => self::$admin_user_id,
     850                ) );
     851                $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
     852                do_action( 'customize_register', $wp_customize );
     853                $wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
     854                $this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
     855
     856                // Attempt scratchpad with user who doesn't have unfiltered_html.
     857                update_option( 'scratchpad', '' );
     858                $wp_customize = new WP_Customize_Manager();
     859                do_action( 'customize_register', $wp_customize );
     860                $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     861                $wp_customize->save_changeset_post( array(
     862                        'status' => 'auto-draft',
     863                        'user_id' => self::$subscriber_user_id,
     864                ) );
     865                $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
     866                do_action( 'customize_register', $wp_customize );
     867                $wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
     868                $this->assertEquals( 'Unfilteredevil', get_option( 'scratchpad' ) );
     869
     870                // Attempt publishing scratchpad as anonymous user when changeset was set by privileged user.
     871                update_option( 'scratchpad', '' );
     872                $wp_customize = new WP_Customize_Manager();
     873                do_action( 'customize_register', $wp_customize );
     874                $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     875                $wp_customize->save_changeset_post( array(
     876                        'status' => 'auto-draft',
     877                        'user_id' => self::$admin_user_id,
     878                ) );
     879                $changeset_post_id = $wp_customize->changeset_post_id();
     880                wp_set_current_user( 0 );
     881                $wp_customize = null;
     882                unset( $GLOBALS['wp_actions']['customize_register'] );
     883                $this->assertEquals( 'Unfilteredevil', apply_filters( 'content_save_pre', 'Unfiltered<script>evil</script>' ) );
     884                wp_publish_post( $changeset_post_id ); // @todo If wp_update_post() is used here, then kses will corrupt the post_content.
     885                $this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
     886        }
     887
     888        /**
     889         * Register scratchpad setting.
     890         *
     891         * @param WP_Customize_Manager $wp_customize Manager.
     892         */
     893        function register_scratchpad_setting( WP_Customize_Manager $wp_customize ) {
     894                $wp_customize->add_setting( 'scratchpad', array(
     895                        'type' => 'option',
     896                        'capability' => 'exist',
     897                        'sanitize_callback' => array( $this, 'filter_sanitize_scratchpad' ),
     898                ) );
     899        }
     900
     901        /**
     902         * Sanitize scratchpad as if it is post_content so kses filters apply.
     903         *
     904         * @param string $value Value.
     905         * @return string Value.
     906         */
     907        function filter_sanitize_scratchpad( $value ) {
     908                return apply_filters( 'content_save_pre', $value );
     909        }
     910
     911        /**
     912         * Current user when settings are filtered.
     913         *
     914         * @var array
     915         */
     916        protected $filtered_setting_current_user_ids = array();
     917
     918        /**
     919         * Filter setting to capture the current user when the filter applies.
     920         *
     921         * @param mixed                $value   Setting value.
     922         * @param WP_Customize_Setting $setting Setting.
     923         * @return mixed Value.
     924         */
     925        function filter_customize_setting_to_log_current_user( $value, $setting ) {
     926                $this->filtered_setting_current_user_ids[ $setting->id ] = get_current_user_id();
     927                return $value;
    689928        }
    690929
Note: See TracChangeset for help on using the changeset viewer.