From 5b676af8ff1ccc6dc941f27cb7bda3b251f4d968 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 9 Nov 2016 07:03:30 +0000 Subject: [PATCH] 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. Built from https://develop.svn.wordpress.org/trunk@39181 git-svn-id: http://core.svn.wordpress.org/trunk@39121 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-customize-manager.php | 114 ++++++++++++++++----- wp-includes/theme.php | 2 +- wp-includes/version.php | 2 +- 3 files changed, 92 insertions(+), 26 deletions(-) diff --git a/wp-includes/class-wp-customize-manager.php b/wp-includes/class-wp-customize-manager.php index 96acb450ec..ecb50a21ab 100644 --- a/wp-includes/class-wp-customize-manager.php +++ b/wp-includes/class-wp-customize-manager.php @@ -1827,6 +1827,7 @@ final class WP_Customize_Manager { * @type string $status Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed. * @type string $title Post title. Optional. * @type string $date_gmt Date in GMT. Optional. + * @type int $user_id ID for user who is saving the changeset. Optional, defaults to the current user ID. * } * * @return array|WP_Error Returns array on success and WP_Error with array data on error. @@ -1839,11 +1840,16 @@ final class WP_Customize_Manager { 'title' => null, 'data' => array(), 'date_gmt' => null, + 'user_id' => get_current_user_id(), ), $args ); $changeset_post_id = $this->changeset_post_id(); + $existing_changeset_data = array(); + if ( $changeset_post_id ) { + $existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); + } // The request was made via wp.customize.previewer.save(). $update_transactionally = (bool) $args['status']; @@ -1863,6 +1869,37 @@ final class WP_Customize_Manager { ) ); $this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value. + /* + * Get list of IDs for settings that have values different from what is currently + * saved in the changeset. By skipping any values that are already the same, the + * subset of changed settings can be passed into validate_setting_values to prevent + * an underprivileged modifying a single setting for which they have the capability + * from being blocked from saving. This also prevents a user from touching of the + * previous saved settings and overriding the associated user_id if they made no change. + */ + $changed_setting_ids = array(); + foreach ( $post_values as $setting_id => $setting_value ) { + $setting = $this->get_setting( $setting_id ); + + if ( $setting && 'theme_mod' === $setting->type ) { + $prefixed_setting_id = $this->get_stylesheet() . '::' . $setting->id; + } else { + $prefixed_setting_id = $setting_id; + } + + $is_value_changed = ( + ! isset( $existing_changeset_data[ $prefixed_setting_id ] ) + || + ! array_key_exists( 'value', $existing_changeset_data[ $prefixed_setting_id ] ) + || + $existing_changeset_data[ $prefixed_setting_id ]['value'] !== $setting_value + ); + if ( $is_value_changed ) { + $changed_setting_ids[] = $setting_id; + } + } + $post_values = wp_array_slice_assoc( $post_values, $changed_setting_ids ); + /** * Fires before save validation happens. * @@ -1943,7 +1980,10 @@ final class WP_Customize_Manager { $data[ $changeset_setting_id ] = array_merge( $data[ $changeset_setting_id ], $setting_params, - array( 'type' => $setting->type ) + array( + 'type' => $setting->type, + 'user_id' => $args['user_id'], + ) ); } } @@ -2121,29 +2161,38 @@ final class WP_Customize_Manager { $previous_changeset_data = $this->_changeset_data; $this->_changeset_data = $publishing_changeset_data; - // Ensure that other theme mods are stashed. - $other_theme_mod_settings = array(); - if ( did_action( 'switch_theme' ) ) { - $namespace_pattern = '/^(?P.+?)::(?P.+)$/'; - $matches = array(); - foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) { - $is_other_theme_mod = ( - isset( $setting_params['value'] ) - && - isset( $setting_params['type'] ) - && - 'theme_mod' === $setting_params['type'] - && - preg_match( $namespace_pattern, $raw_setting_id, $matches ) - && - $this->get_stylesheet() !== $matches['stylesheet'] - ); - if ( $is_other_theme_mod ) { - if ( ! isset( $other_theme_mod_settings[ $matches['stylesheet'] ] ) ) { - $other_theme_mod_settings[ $matches['stylesheet'] ] = array(); - } - $other_theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params; + // Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved. + $setting_user_ids = array(); + $theme_mod_settings = array(); + $namespace_pattern = '/^(?P.+?)::(?P.+)$/'; + $matches = array(); + foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) { + $actual_setting_id = null; + $is_theme_mod_setting = ( + isset( $setting_params['value'] ) + && + isset( $setting_params['type'] ) + && + 'theme_mod' === $setting_params['type'] + && + preg_match( $namespace_pattern, $raw_setting_id, $matches ) + ); + if ( $is_theme_mod_setting ) { + if ( ! isset( $theme_mod_settings[ $matches['stylesheet'] ] ) ) { + $theme_mod_settings[ $matches['stylesheet'] ] = array(); } + $theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params; + + if ( $this->get_stylesheet() === $matches['stylesheet'] ) { + $actual_setting_id = $matches['setting_id']; + } + } else { + $actual_setting_id = $raw_setting_id; + } + + // Keep track of the user IDs for settings actually for this theme. + if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) { + $setting_user_ids[ $actual_setting_id ] = $setting_params['user_id']; } } @@ -2173,21 +2222,38 @@ final class WP_Customize_Manager { $original_setting_capabilities = array(); foreach ( $changeset_setting_ids as $setting_id ) { $setting = $this->get_setting( $setting_id ); - if ( $setting ) { + if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) { $original_setting_capabilities[ $setting->id ] = $setting->capability; $setting->capability = 'exist'; } } + $original_user_id = get_current_user_id(); foreach ( $changeset_setting_ids as $setting_id ) { $setting = $this->get_setting( $setting_id ); if ( $setting ) { + /* + * Set the current user to match the user who saved the value into + * the changeset so that any filters that apply during the save + * process will respect the original user's capabilities. This + * will ensure, for example, that KSES won't strip unsafe HTML + * when a scheduled changeset publishes via WP Cron. + */ + if ( isset( $setting_user_ids[ $setting_id ] ) ) { + wp_set_current_user( $setting_user_ids[ $setting_id ] ); + } else { + wp_set_current_user( $original_user_id ); + } + $setting->save(); } } + wp_set_current_user( $original_user_id ); // Update the stashed theme mod settings, removing the active theme's stashed settings, if activated. if ( did_action( 'switch_theme' ) ) { + $other_theme_mod_settings = $theme_mod_settings; + unset( $other_theme_mod_settings[ $this->get_stylesheet() ] ); $this->update_stashed_theme_mod_settings( $other_theme_mod_settings ); } diff --git a/wp-includes/theme.php b/wp-includes/theme.php index 9c53441bf6..646b2e4553 100644 --- a/wp-includes/theme.php +++ b/wp-includes/theme.php @@ -2564,7 +2564,7 @@ function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_p if ( empty( $wp_customize ) ) { require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; - $wp_customize = new WP_Customize_Manager( $changeset_post->post_name ); + $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) ); } if ( ! did_action( 'customize_register' ) ) { diff --git a/wp-includes/version.php b/wp-includes/version.php index 2a21912921..25e90b33d4 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.7-beta2-39180'; +$wp_version = '4.7-beta2-39181'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.