Ticket #27504: 27504.2.patch
| File 27504.2.patch, 10.1 KB (added by , 12 years ago) |
|---|
-
src/wp-includes/class-wp-customize-widgets.php
diff --git src/wp-includes/class-wp-customize-widgets.php src/wp-includes/class-wp-customize-widgets.php index 055afa2..2b8228d 100644
class WP_Customize_Widgets { 1088 1088 static function call_widget_update( $widget_id ) { 1089 1089 global $wp_registered_widget_updates, $wp_registered_widget_controls; 1090 1090 1091 $options_transaction = new Options_Transaction(); 1092 1093 $options_transaction->start(); 1091 self::start_capturing_option_updates(); 1094 1092 $parsed_id = self::parse_widget_id( $widget_id ); 1095 1093 $option_name = 'widget_' . $parsed_id['id_base']; 1096 1094 … … class WP_Customize_Widgets { 1102 1100 if ( ! empty( $_POST['sanitized_widget_setting'] ) ) { 1103 1101 $sanitized_widget_setting = json_decode( self::get_post_value( 'sanitized_widget_setting' ), true ); 1104 1102 if ( empty( $sanitized_widget_setting ) ) { 1105 $options_transaction->rollback();1103 self::stop_capturing_option_updates(); 1106 1104 return new WP_Error( 'malformed_data', 'Malformed sanitized_widget_setting' ); 1107 1105 } 1108 1106 1109 1107 $instance = self::sanitize_widget_instance( $sanitized_widget_setting ); 1110 1108 if ( is_null( $instance ) ) { 1111 $options_transaction->rollback();1109 self::stop_capturing_option_updates(); 1112 1110 return new WP_Error( 'unsanitary_data', 'Unsanitary sanitized_widget_setting' ); 1113 1111 } 1114 1112 … … class WP_Customize_Widgets { 1143 1141 } 1144 1142 1145 1143 // Make sure the expected option was updated. 1146 if ( 0 !== $options_transaction->count() ) {1147 if ( count( $options_transaction->options) > 1 ) {1148 $options_transaction->rollback();1144 if ( 0 !== self::count_captured_options() ) { 1145 if ( self::count_captured_options() > 1 ) { 1146 self::stop_capturing_option_updates(); 1149 1147 return new WP_Error( 'unexpected_update', 'Widget unexpectedly updated more than one option.' ); 1150 1148 } 1151 1149 1152 $updated_option_name = key( $options_transaction->options);1150 $updated_option_name = key( self::get_captured_options() ); 1153 1151 if ( $updated_option_name !== $option_name ) { 1154 $options_transaction->rollback();1152 self::stop_capturing_option_updates(); 1155 1153 return new WP_Error( 'wrong_option', sprintf( 'Widget updated option "%1$s", but expected "%2$s".', $updated_option_name, $option_name ) ); 1156 1154 } 1157 1155 } … … class WP_Customize_Widgets { 1172 1170 $instance = $option; 1173 1171 } 1174 1172 1175 $options_transaction->rollback();1173 self::stop_capturing_option_updates(); 1176 1174 return compact( 'instance', 'form' ); 1177 1175 } 1178 1176 … … class WP_Customize_Widgets { 1229 1227 1230 1228 wp_send_json_success( compact( 'form', 'instance' ) ); 1231 1229 } 1232 }1233 1230 1234 class Options_Transaction { 1231 /*************************************************************************** 1232 * Option Update Capturing 1233 ***************************************************************************/ 1235 1234 1236 1235 /** 1237 * @var array $ options values updated while transaction is open1236 * @var array $_captured_options values updated while capturing is happening 1238 1237 */ 1239 public $options = array(); 1240 1241 protected $_ignore_transients = true; 1242 protected $_is_current = false; 1243 protected $_operations = array(); 1244 1245 function __construct( $ignore_transients = true ) { 1246 $this->_ignore_transients = $ignore_transients; 1247 } 1238 protected static $_captured_options = array(); 1248 1239 1249 1240 /** 1250 * Determine whether or not the transaction is open 1251 * @return bool 1241 * @var bool $_is_current whether capturing is currently happening or not 1252 1242 */ 1253 function is_current() { 1254 return $this->_is_current; 1255 } 1243 protected static $_is_capturing_option_updates = false; 1256 1244 1257 1245 /** 1258 1246 * @param $option_name 1259 1247 * @return boolean 1260 1248 */ 1261 function is_option_ignored( $option_name ) {1262 return ( $this->_ignore_transients &&0 === strpos( $option_name, '_transient_' ) );1249 protected static function is_option_capture_ignored( $option_name ) { 1250 return ( 0 === strpos( $option_name, '_transient_' ) ); 1263 1251 } 1264 1252 1265 1253 /** 1266 * Get the number of operations performed in the transaction1267 * @return bool1254 * Get options updated 1255 * @return array 1268 1256 */ 1269 function count() {1270 return count( $this->_operations );1257 protected static function get_captured_options() { 1258 return self::$_captured_options; 1271 1259 } 1272 1260 1273 1261 /** 1274 * Start keeping track of changes to options, and cache their new values 1262 * Get the number of options updated 1263 * @return bool 1275 1264 */ 1276 function start() { 1277 $this->_is_current = true; 1278 add_action( 'added_option', array( $this, '_capture_added_option' ), 10, 2 ); 1279 add_action( 'updated_option', array( $this, '_capture_updated_option' ), 10, 3 ); 1280 add_action( 'delete_option', array( $this, '_capture_pre_deleted_option' ), 10, 1 ); 1281 add_action( 'deleted_option', array( $this, '_capture_deleted_option' ), 10, 1 ); 1265 protected static function count_captured_options() { 1266 return count( self::$_captured_options ); 1282 1267 } 1283 1268 1284 1269 /** 1285 * @action added_option 1286 * @param $option_name 1287 * @param $new_value 1270 * Start keeping track of changes to options, and cache their new values 1288 1271 */ 1289 function _capture_added_option( $option_name, $new_value) {1290 if ( $this->is_option_ignored( $option_name )) {1272 protected static function start_capturing_option_updates() { 1273 if ( self::$_is_capturing_option_updates ) { 1291 1274 return; 1292 1275 } 1293 $this->options[$option_name] = $new_value; 1294 $operation = 'add';1295 $this->_operations[] = compact( 'operation', 'option_name', 'new_value');1276 1277 self::$_is_capturing_option_updates = true; 1278 add_filter( 'pre_update_option', array( __CLASS__, '_capture_filter_pre_update_option' ), 10, 3 ); 1296 1279 } 1297 1280 1298 1281 /** 1299 * @action updated_option 1282 * @access private 1283 * @param mixed $new_value 1300 1284 * @param string $option_name 1301 1285 * @param mixed $old_value 1302 * @ param mixed $new_value1286 * @return mixed 1303 1287 */ 1304 function _capture_updated_option( $option_name, $old_value, $new_value ) {1305 if ( $this->is_option_ignored( $option_name ) ) {1288 static function _capture_filter_pre_update_option( $new_value, $option_name, $old_value ) { 1289 if ( self::is_option_capture_ignored( $option_name ) ) { 1306 1290 return; 1307 1291 } 1308 $this->options[$option_name] = $new_value;1309 $operation = 'update';1310 $this->_operations[] = compact( 'operation', 'option_name', 'old_value', 'new_value' );1311 }1312 1313 protected $_pending_delete_option_autoload;1314 protected $_pending_delete_option_value;1315 1292 1316 /** 1317 * It's too bad the old_value and autoload aren't passed into the deleted_option action 1318 * @action delete_option 1319 * @param string $option_name 1320 */ 1321 function _capture_pre_deleted_option( $option_name ) { 1322 if ( $this->is_option_ignored( $option_name ) ) { 1323 return; 1293 if ( ! isset( self::$_captured_options[$option_name] ) ) { 1294 add_filter( "pre_option_{$option_name}", array( __CLASS__, '_capture_filter_pre_get_option' ) ); 1324 1295 } 1325 global $wpdb; 1326 $autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option_name ) ); // db call ok; no-cache ok1327 $this->_pending_delete_option_autoload = $autoload; 1328 $this->_pending_delete_option_value = get_option( $option_name );1296 1297 self::$_captured_options[$option_name] = $new_value; 1298 1299 return $old_value; 1329 1300 } 1330 1301 1331 1302 /** 1332 * @action deleted_option 1333 * @param string $option_name 1303 * @access private 1304 * @param mixed $value 1305 * @return mixed 1334 1306 */ 1335 function _capture_deleted_option( $option_name ) { 1336 if ( $this->is_option_ignored( $option_name ) ) { 1337 return; 1307 static function _capture_filter_pre_get_option( $value ) { 1308 $option_name = preg_replace( '/^pre_option_/', '', current_filter() ); 1309 if ( isset( self::$_captured_options[$option_name] ) ) { 1310 $value = self::$_captured_options[$option_name]; 1311 $value = apply_filters( 'option_' . $option_name, $value ); 1338 1312 } 1339 unset( $this->options[$option_name] ); 1340 $operation = 'delete'; 1341 $old_value = $this->_pending_delete_option_value; 1342 $autoload = $this->_pending_delete_option_autoload; 1343 $this->_operations[] = compact( 'operation', 'option_name', 'old_value', 'autoload' ); 1313 1314 return $value; 1344 1315 } 1345 1316 1346 1317 /** 1347 1318 * Undo any changes to the options since start() was called 1348 1319 */ 1349 function rollback() { 1350 remove_action( 'updated_option', array( $this, '_capture_updated_option' ), 10, 3 ); 1351 remove_action( 'added_option', array( $this, '_capture_added_option' ), 10, 2 ); 1352 remove_action( 'delete_option', array( $this, '_capture_pre_deleted_option' ), 10, 1 ); 1353 remove_action( 'deleted_option', array( $this, '_capture_deleted_option' ), 10, 1 ); 1354 while ( 0 !== count( $this->_operations ) ) { 1355 $option_operation = array_pop( $this->_operations ); 1356 if ( 'add' === $option_operation['operation'] ) { 1357 delete_option( $option_operation['option_name'] ); 1358 } 1359 else if ( 'delete' === $option_operation['operation'] ) { 1360 add_option( $option_operation['option_name'], $option_operation['old_value'], null, $option_operation['autoload'] ); 1361 } 1362 else if ( 'update' === $option_operation['operation'] ) { 1363 update_option( $option_operation['option_name'], $option_operation['old_value'] ); 1364 } 1320 protected static function stop_capturing_option_updates() { 1321 if ( ! self::$_is_capturing_option_updates ) { 1322 return; 1323 } 1324 1325 remove_filter( '_capture_filter_pre_update_option', array( __CLASS__, '_capture_filter_pre_update_option' ), 10, 3 ); 1326 foreach ( array_keys( self::$_captured_options ) as $option_name ) { 1327 remove_filter( "pre_option_{$option_name}", array( __CLASS__, '_capture_filter_pre_get_option' ) ); 1365 1328 } 1366 $this->_is_current = false; 1329 1330 self::$_captured_options = array(); 1331 self::$_is_capturing_option_updates = false; 1367 1332 } 1368 1333 } -
src/wp-includes/option.php
diff --git src/wp-includes/option.php src/wp-includes/option.php index 0091c12..4cc8c28 100644
function update_option( $option, $value ) { 255 255 */ 256 256 $value = apply_filters( 'pre_update_option_' . $option, $value, $old_value ); 257 257 258 /** 259 * Filter an option before its value is (maybe) serialized and updated. 260 * 261 * @since 3.9.0 262 * 263 * @param mixed $value The new, unserialized option value. 264 * @param string $option Name of the option. 265 * @param mixed $old_value The old option value. 266 */ 267 $value = apply_filters( 'pre_update_option', $value, $option, $old_value ); 268 258 269 // If the new and old values are the same, no need to update. 259 270 if ( $value === $old_value ) 260 271 return false;