Changeset 39181
- Timestamp:
- 11/09/2016 07:02:53 AM (8 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-wp-customize-manager.php
r39175 r39181 1828 1828 * @type string $title Post title. Optional. 1829 1829 * @type string $date_gmt Date in GMT. Optional. 1830 * @type int $user_id ID for user who is saving the changeset. Optional, defaults to the current user ID. 1830 1831 * } 1831 1832 * … … 1840 1841 'data' => array(), 1841 1842 'date_gmt' => null, 1843 'user_id' => get_current_user_id(), 1842 1844 ), 1843 1845 $args … … 1845 1847 1846 1848 $changeset_post_id = $this->changeset_post_id(); 1849 $existing_changeset_data = array(); 1850 if ( $changeset_post_id ) { 1851 $existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); 1852 } 1847 1853 1848 1854 // The request was made via wp.customize.previewer.save(). … … 1863 1869 ) ); 1864 1870 $this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value. 1871 1872 /* 1873 * Get list of IDs for settings that have values different from what is currently 1874 * saved in the changeset. By skipping any values that are already the same, the 1875 * subset of changed settings can be passed into validate_setting_values to prevent 1876 * an underprivileged modifying a single setting for which they have the capability 1877 * from being blocked from saving. This also prevents a user from touching of the 1878 * previous saved settings and overriding the associated user_id if they made no change. 1879 */ 1880 $changed_setting_ids = array(); 1881 foreach ( $post_values as $setting_id => $setting_value ) { 1882 $setting = $this->get_setting( $setting_id ); 1883 1884 if ( $setting && 'theme_mod' === $setting->type ) { 1885 $prefixed_setting_id = $this->get_stylesheet() . '::' . $setting->id; 1886 } else { 1887 $prefixed_setting_id = $setting_id; 1888 } 1889 1890 $is_value_changed = ( 1891 ! isset( $existing_changeset_data[ $prefixed_setting_id ] ) 1892 || 1893 ! array_key_exists( 'value', $existing_changeset_data[ $prefixed_setting_id ] ) 1894 || 1895 $existing_changeset_data[ $prefixed_setting_id ]['value'] !== $setting_value 1896 ); 1897 if ( $is_value_changed ) { 1898 $changed_setting_ids[] = $setting_id; 1899 } 1900 } 1901 $post_values = wp_array_slice_assoc( $post_values, $changed_setting_ids ); 1865 1902 1866 1903 /** … … 1944 1981 $data[ $changeset_setting_id ], 1945 1982 $setting_params, 1946 array( 'type' => $setting->type ) 1983 array( 1984 'type' => $setting->type, 1985 'user_id' => $args['user_id'], 1986 ) 1947 1987 ); 1948 1988 } … … 2122 2162 $this->_changeset_data = $publishing_changeset_data; 2123 2163 2124 // Ensure that other theme mods are stashed. 2125 $other_theme_mod_settings = array(); 2126 if ( did_action( 'switch_theme' ) ) { 2127 $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/'; 2128 $matches = array(); 2129 foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) { 2130 $is_other_theme_mod = ( 2131 isset( $setting_params['value'] ) 2132 && 2133 isset( $setting_params['type'] ) 2134 && 2135 'theme_mod' === $setting_params['type'] 2136 && 2137 preg_match( $namespace_pattern, $raw_setting_id, $matches ) 2138 && 2139 $this->get_stylesheet() !== $matches['stylesheet'] 2140 ); 2141 if ( $is_other_theme_mod ) { 2142 if ( ! isset( $other_theme_mod_settings[ $matches['stylesheet'] ] ) ) { 2143 $other_theme_mod_settings[ $matches['stylesheet'] ] = array(); 2144 } 2145 $other_theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params; 2164 // Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved. 2165 $setting_user_ids = array(); 2166 $theme_mod_settings = array(); 2167 $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/'; 2168 $matches = array(); 2169 foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) { 2170 $actual_setting_id = null; 2171 $is_theme_mod_setting = ( 2172 isset( $setting_params['value'] ) 2173 && 2174 isset( $setting_params['type'] ) 2175 && 2176 'theme_mod' === $setting_params['type'] 2177 && 2178 preg_match( $namespace_pattern, $raw_setting_id, $matches ) 2179 ); 2180 if ( $is_theme_mod_setting ) { 2181 if ( ! isset( $theme_mod_settings[ $matches['stylesheet'] ] ) ) { 2182 $theme_mod_settings[ $matches['stylesheet'] ] = array(); 2146 2183 } 2184 $theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params; 2185 2186 if ( $this->get_stylesheet() === $matches['stylesheet'] ) { 2187 $actual_setting_id = $matches['setting_id']; 2188 } 2189 } else { 2190 $actual_setting_id = $raw_setting_id; 2191 } 2192 2193 // Keep track of the user IDs for settings actually for this theme. 2194 if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) { 2195 $setting_user_ids[ $actual_setting_id ] = $setting_params['user_id']; 2147 2196 } 2148 2197 } … … 2174 2223 foreach ( $changeset_setting_ids as $setting_id ) { 2175 2224 $setting = $this->get_setting( $setting_id ); 2176 if ( $setting ) {2225 if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) { 2177 2226 $original_setting_capabilities[ $setting->id ] = $setting->capability; 2178 2227 $setting->capability = 'exist'; … … 2180 2229 } 2181 2230 2231 $original_user_id = get_current_user_id(); 2182 2232 foreach ( $changeset_setting_ids as $setting_id ) { 2183 2233 $setting = $this->get_setting( $setting_id ); 2184 2234 if ( $setting ) { 2235 /* 2236 * Set the current user to match the user who saved the value into 2237 * the changeset so that any filters that apply during the save 2238 * process will respect the original user's capabilities. This 2239 * will ensure, for example, that KSES won't strip unsafe HTML 2240 * when a scheduled changeset publishes via WP Cron. 2241 */ 2242 if ( isset( $setting_user_ids[ $setting_id ] ) ) { 2243 wp_set_current_user( $setting_user_ids[ $setting_id ] ); 2244 } else { 2245 wp_set_current_user( $original_user_id ); 2246 } 2247 2185 2248 $setting->save(); 2186 2249 } 2187 2250 } 2251 wp_set_current_user( $original_user_id ); 2188 2252 2189 2253 // Update the stashed theme mod settings, removing the active theme's stashed settings, if activated. 2190 2254 if ( did_action( 'switch_theme' ) ) { 2255 $other_theme_mod_settings = $theme_mod_settings; 2256 unset( $other_theme_mod_settings[ $this->get_stylesheet() ] ); 2191 2257 $this->update_stashed_theme_mod_settings( $other_theme_mod_settings ); 2192 2258 } -
trunk/src/wp-includes/theme.php
r39180 r39181 2565 2565 if ( empty( $wp_customize ) ) { 2566 2566 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; 2567 $wp_customize = new WP_Customize_Manager( $changeset_post->post_name);2567 $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) ); 2568 2568 } 2569 2569 -
trunk/tests/phpunit/tests/customize/manager.php
r39180 r39181 430 430 'changeset_uuid' => $uuid, 431 431 ) ); 432 $wp_customize = $manager; 432 433 $manager->register_controls(); 433 434 $manager->set_post_value( 'blogname', 'Changeset Title' ); 434 435 $manager->set_post_value( 'blogdescription', 'Changeset Tagline' ); 435 436 437 $pre_saved_data = array( 438 'blogname' => array( 439 'value' => 'Overridden Changeset Title', 440 ), 441 'blogdescription' => array( 442 'custom' => 'something', 443 ), 444 ); 436 445 $r = $manager->save_changeset_post( array( 437 446 'status' => 'auto-draft', 438 447 'title' => 'Auto Draft', 439 448 '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, 448 450 ) ); 449 451 $this->assertInternalType( 'array', $r ); … … 455 457 $saved_data = json_decode( get_post( $post_id )->post_content, true ); 456 458 $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 } 459 467 $this->assertEquals( 'Auto Draft', get_post( $post_id )->post_title ); 460 468 $this->assertEquals( 'auto-draft', get_post( $post_id )->post_status ); … … 512 520 'changeset_uuid' => $uuid, 513 521 ) ); 522 $wp_customize = $manager; 514 523 $manager->register_controls(); // That is, register settings. 515 524 $r = $manager->save_changeset_post( array( … … 564 573 565 574 $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) ); 566 $manager->register_controls();575 do_action( 'customize_register', $wp_customize ); 567 576 $manager->add_setting( 'scratchpad', array( 568 577 'type' => 'option', … … 570 579 ) ); 571 580 $manager->get_setting( 'blogname' )->capability = 'exist'; 581 $original_capabilities = wp_list_pluck( $manager->settings(), 'capability' ); 572 582 wp_set_current_user( self::$subscriber_user_id ); 573 583 $r = $manager->save_changeset_post( array( … … 585 595 $this->assertEquals( 'Do it live \o/', get_option( 'blogname' ) ); 586 596 $this->assertEquals( 'trash', get_post_status( $post_id ) ); // Auto-trashed. 597 $this->assertEquals( $original_capabilities, wp_list_pluck( $manager->settings(), 'capability' ) ); 587 598 $this->assertContains( '<script>', get_post( $post_id )->post_content ); 588 599 $this->assertEquals( $manager->changeset_uuid(), get_post( $post_id )->post_name, 'Expected that the "__trashed" suffix to not be added.' ); … … 599 610 $uuid = wp_generate_uuid4(); 600 611 $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) ); 601 $manager->register_controls();612 do_action( 'customize_register', $manager ); 602 613 603 614 $manager->set_post_value( 'blogname', 'Hello Surface' ); … … 661 672 */ 662 673 function test_save_changeset_post_with_theme_activation() { 674 global $wp_customize; 663 675 wp_set_current_user( self::$admin_user_id ); 664 676 … … 677 689 'theme' => $preview_theme, 678 690 ) ); 679 $ manager->register_controls();680 $GLOBALS['wp_customize'] = $manager;691 $wp_customize = $manager; 692 do_action( 'customize_register', $manager ); 681 693 682 694 $manager->set_post_value( 'blogname', 'Hello Preview Theme' ); … … 687 699 $this->assertEquals( $preview_theme, get_stylesheet() ); 688 700 $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; 689 928 } 690 929
Note: See TracChangeset
for help on using the changeset viewer.