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
This commit is contained in:
Weston Ruter 2016-11-09 07:03:30 +00:00
parent 5b633c548e
commit 5b676af8ff
3 changed files with 92 additions and 26 deletions

View File

@ -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<stylesheet>.+?)::(?P<setting_id>.+)$/';
$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<stylesheet>.+?)::(?P<setting_id>.+)$/';
$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 );
}

View File

@ -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' ) ) {

View File

@ -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.