Make WordPress Core

Ticket #27504: 27504.2.patch

File 27504.2.patch, 10.1 KB (added by westonruter, 12 years ago)

Replace Options_Transaction with option update capturing with methods merged into class WP_Customize_Widgets. Patches, commits, and conflict resolutions also pushed to https://github.com/x-team/wordpress-develop/pull/1/files

  • 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 { 
    10881088        static function call_widget_update( $widget_id ) {
    10891089                global $wp_registered_widget_updates, $wp_registered_widget_controls;
    10901090
    1091                 $options_transaction = new Options_Transaction();
    1092 
    1093                 $options_transaction->start();
     1091                self::start_capturing_option_updates();
    10941092                $parsed_id   = self::parse_widget_id( $widget_id );
    10951093                $option_name = 'widget_' . $parsed_id['id_base'];
    10961094
    class WP_Customize_Widgets { 
    11021100                if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
    11031101                        $sanitized_widget_setting = json_decode( self::get_post_value( 'sanitized_widget_setting' ), true );
    11041102                        if ( empty( $sanitized_widget_setting ) ) {
    1105                                 $options_transaction->rollback();
     1103                                self::stop_capturing_option_updates();
    11061104                                return new WP_Error( 'malformed_data', 'Malformed sanitized_widget_setting' );
    11071105                        }
    11081106
    11091107                        $instance = self::sanitize_widget_instance( $sanitized_widget_setting );
    11101108                        if ( is_null( $instance ) ) {
    1111                                 $options_transaction->rollback();
     1109                                self::stop_capturing_option_updates();
    11121110                                return new WP_Error( 'unsanitary_data', 'Unsanitary sanitized_widget_setting' );
    11131111                        }
    11141112
    class WP_Customize_Widgets { 
    11431141                }
    11441142
    11451143                // 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();
    11491147                                return new WP_Error( 'unexpected_update', 'Widget unexpectedly updated more than one option.' );
    11501148                        }
    11511149
    1152                         $updated_option_name = key( $options_transaction->options );
     1150                        $updated_option_name = key( self::get_captured_options() );
    11531151                        if ( $updated_option_name !== $option_name ) {
    1154                                 $options_transaction->rollback();
     1152                                self::stop_capturing_option_updates();
    11551153                                return new WP_Error( 'wrong_option', sprintf( 'Widget updated option "%1$s", but expected "%2$s".', $updated_option_name, $option_name ) );
    11561154                        }
    11571155                }
    class WP_Customize_Widgets { 
    11721170                        $instance = $option;
    11731171                }
    11741172
    1175                 $options_transaction->rollback();
     1173                self::stop_capturing_option_updates();
    11761174                return compact( 'instance', 'form' );
    11771175        }
    11781176
    class WP_Customize_Widgets { 
    12291227
    12301228                wp_send_json_success( compact( 'form', 'instance' ) );
    12311229        }
    1232 }
    12331230
    1234 class Options_Transaction {
     1231        /***************************************************************************
     1232         * Option Update Capturing
     1233         ***************************************************************************/
    12351234
    12361235        /**
    1237          * @var array $options values updated while transaction is open
     1236         * @var array $_captured_options values updated while capturing is happening
    12381237         */
    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();
    12481239
    12491240        /**
    1250          * Determine whether or not the transaction is open
    1251          * @return bool
     1241         * @var bool $_is_current whether capturing is currently happening or not
    12521242         */
    1253         function is_current() {
    1254                 return $this->_is_current;
    1255         }
     1243        protected static $_is_capturing_option_updates = false;
    12561244
    12571245        /**
    12581246         * @param $option_name
    12591247         * @return boolean
    12601248         */
    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_' ) );
    12631251        }
    12641252
    12651253        /**
    1266          * Get the number of operations performed in the transaction
    1267          * @return bool
     1254         * Get options updated
     1255         * @return array
    12681256         */
    1269         function count() {
    1270                 return count( $this->_operations );
     1257        protected static function get_captured_options() {
     1258                return self::$_captured_options;
    12711259        }
    12721260
    12731261        /**
    1274          * Start keeping track of changes to options, and cache their new values
     1262         * Get the number of options updated
     1263         * @return bool
    12751264         */
    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 );
    12821267        }
    12831268
    12841269        /**
    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
    12881271         */
    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 ) {
    12911274                        return;
    12921275                }
    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 );
    12961279        }
    12971280
    12981281        /**
    1299          * @action updated_option
     1282         * @access private
     1283         * @param mixed $new_value
    13001284         * @param string $option_name
    13011285         * @param mixed $old_value
    1302          * @param mixed $new_value
     1286         * @return mixed
    13031287         */
    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 ) ) {
    13061290                        return;
    13071291                }
    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;
    13151292
    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' ) );
    13241295                }
    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 ok
    1327                 $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;
    13291300        }
    13301301
    13311302        /**
    1332          * @action deleted_option
    1333          * @param string $option_name
     1303         * @access private
     1304         * @param mixed $value
     1305         * @return mixed
    13341306         */
    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 );
    13381312                }
    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;
    13441315        }
    13451316
    13461317        /**
    13471318         * Undo any changes to the options since start() was called
    13481319         */
    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' ) );
    13651328                }
    1366                 $this->_is_current = false;
     1329
     1330                self::$_captured_options = array();
     1331                self::$_is_capturing_option_updates = false;
    13671332        }
    13681333}
  • 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 ) { 
    255255         */
    256256        $value = apply_filters( 'pre_update_option_' . $option, $value, $old_value );
    257257
     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
    258269        // If the new and old values are the same, no need to update.
    259270        if ( $value === $old_value )
    260271                return false;