Make WordPress Core

Changeset 56508


Ignore:
Timestamp:
09/01/2023 07:55:11 PM (3 years ago)
Author:
flixos90
Message:

Options, Meta APIs: Introduce wp_set_option_autoload_values().

This function accepts an associative array of option names and their autoload values to set, and it will update those values in the database in bulk, only for those options where the autoload field is not already set to the given value.

Two wrapper functions for ease of use accompany the new main function:

  • wp_set_options_autoload( $options, $autoload ) can be used to set multiple options to the same autoload value.
  • wp_set_option_autoload( $option, $autoload ) can be used to set the autoload value for a single option.

All of these functions allow changing the autoload value of an option, which previously has only been possible in combination with updating the value. This limitation prevented some relevant use-cases: For example, a plugin deactivation hook could set all of its options to not autoload, as a cleanup routine, while not actually deleting any data. The plugin's activation hook could then implement the reverse, resetting those options' autoload values to the originally intended ones for when using the plugin.

Props boonebgorges, joemcgill, costdev, mukesh27, SergeyBiryukov, tabrisrp, flixos90.
Fixes #58964.

Location:
trunk
Files:
3 added
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/option.php

    r56445 r56508  
    363363
    364364        return $result;
     365}
     366
     367/**
     368 * Sets the autoload values for multiple options in the database.
     369 *
     370 * Autoloading too many options can lead to performance problems, especially if the options are not frequently used.
     371 * This function allows modifying the autoload value for multiple options without changing the actual option value.
     372 * This is for example recommended for plugin activation and deactivation hooks, to ensure any options exclusively used
     373 * by the plugin which are generally autoloaded can be set to not autoload when the plugin is inactive.
     374 *
     375 * @since 6.4.0
     376 *
     377 * @global wpdb $wpdb WordPress database abstraction object.
     378 *
     379 * @param array $options Associative array of option names and their autoload values to set. The option names are
     380 *                       expected to not be SQL-escaped. The autoload values accept 'yes'|true to enable or 'no'|false
     381 *                       to disable.
     382 * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value
     383 *               was updated.
     384 */
     385function wp_set_option_autoload_values( array $options ) {
     386        global $wpdb;
     387
     388        if ( ! $options ) {
     389                return array();
     390        }
     391
     392        $grouped_options = array(
     393                'yes' => array(),
     394                'no'  => array(),
     395        );
     396        $results         = array();
     397        foreach ( $options as $option => $autoload ) {
     398                wp_protect_special_option( $option ); // Ensure only valid options can be passed.
     399                if ( 'no' === $autoload || false === $autoload ) { // Sanitize autoload value and categorize accordingly.
     400                        $grouped_options['no'][] = $option;
     401                } else {
     402                        $grouped_options['yes'][] = $option;
     403                }
     404                $results[ $option ] = false; // Initialize result value.
     405        }
     406
     407        $where      = array();
     408        $where_args = array();
     409        foreach ( $grouped_options as $autoload => $options ) {
     410                if ( ! $options ) {
     411                        continue;
     412                }
     413                $placeholders = implode( ',', array_fill( 0, count( $options ), '%s' ) );
     414                $where[]      = "autoload != '%s' AND option_name IN ($placeholders)";
     415                $where_args[] = $autoload;
     416                foreach ( $options as $option ) {
     417                        $where_args[] = $option;
     418                }
     419        }
     420        $where = 'WHERE ' . implode( ' OR ', $where );
     421
     422        /*
     423         * Determine the relevant options that do not already use the given autoload value.
     424         * If no options are returned, no need to update.
     425         */
     426        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
     427        $options_to_update = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options $where", $where_args ) );
     428        if ( ! $options_to_update ) {
     429                return $results;
     430        }
     431
     432        // Run UPDATE queries as needed (maximum 2) to update the relevant options' autoload values to 'yes' or 'no'.
     433        foreach ( $grouped_options as $autoload => $options ) {
     434                if ( ! $options ) {
     435                        continue;
     436                }
     437                $options                      = array_intersect( $options, $options_to_update );
     438                $grouped_options[ $autoload ] = $options;
     439                if ( ! $grouped_options[ $autoload ] ) {
     440                        continue;
     441                }
     442
     443                // Run query to update autoload value for all the options where it is needed.
     444                $success = $wpdb->query(
     445                        $wpdb->prepare(
     446                                "UPDATE $wpdb->options SET autoload = %s WHERE option_name IN (" . implode( ',', array_fill( 0, count( $grouped_options[ $autoload ] ), '%s' ) ) . ')',
     447                                array_merge(
     448                                        array( $autoload ),
     449                                        $grouped_options[ $autoload ]
     450                                )
     451                        )
     452                );
     453                if ( ! $success ) {
     454                        // Set option list to an empty array to indicate no options were updated.
     455                        $grouped_options[ $autoload ] = array();
     456                        continue;
     457                }
     458
     459                // Assume that on success all options were updated, which should be the case given only new values are sent.
     460                foreach ( $grouped_options[ $autoload ] as $option ) {
     461                        $results[ $option ] = true;
     462                }
     463        }
     464
     465        /*
     466         * If any options were changed to 'yes', delete their individual caches, and delete 'alloptions' cache so that it
     467         * is refreshed as needed.
     468         * If no options were changed to 'yes' but any options were changed to 'no', delete them from the 'alloptions'
     469         * cache. This is not necessary when options were changed to 'yes', since in that situation the entire cache is
     470         * deleted anyway.
     471         */
     472        if ( $grouped_options['yes'] ) {
     473                wp_cache_delete_multiple( $grouped_options['yes'], 'options' );
     474                wp_cache_delete( 'alloptions', 'options' );
     475        } elseif ( $grouped_options['no'] ) {
     476                $alloptions = wp_load_alloptions( true );
     477                foreach ( $grouped_options['no'] as $option ) {
     478                        if ( isset( $alloptions[ $option ] ) ) {
     479                                unset( $alloptions[ $option ] );
     480                        }
     481                }
     482                wp_cache_set( 'alloptions', $alloptions, 'options' );
     483        }
     484
     485        return $results;
     486}
     487
     488/**
     489 * Sets the autoload value for multiple options in the database.
     490 *
     491 * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set different autoload values for
     492 * each option at once.
     493 *
     494 * @since 6.4.0
     495 *
     496 * @see wp_set_option_autoload_values()
     497 *
     498 * @param array       $options  List of option names. Expected to not be SQL-escaped.
     499 * @param string|bool $autoload Autoload value to control whether to load the options when WordPress starts up.
     500 *                              Accepts 'yes'|true to enable or 'no'|false to disable.
     501 * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value
     502 *               was updated.
     503 */
     504function wp_set_options_autoload( array $options, $autoload ) {
     505        return wp_set_option_autoload_values(
     506                array_fill_keys( $options, $autoload )
     507        );
     508}
     509
     510/**
     511 * Sets the autoload value for an option in the database.
     512 *
     513 * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set the autoload value for
     514 * multiple options at once.
     515 *
     516 * @since 6.4.0
     517 *
     518 * @see wp_set_option_autoload_values()
     519 *
     520 * @param string      $option   Name of the option. Expected to not be SQL-escaped.
     521 * @param string|bool $autoload Autoload value to control whether to load the option when WordPress starts up.
     522 *                              Accepts 'yes'|true to enable or 'no'|false to disable.
     523 * @return bool True if the autoload value was modified, false otherwise.
     524 */
     525function wp_set_option_autoload( $option, $autoload ) {
     526        $result = wp_set_option_autoload_values( array( $option => $autoload ) );
     527        if ( isset( $result[ $option ] ) ) {
     528                return $result[ $option ];
     529        }
     530        return false;
    365531}
    366532
Note: See TracChangeset for help on using the changeset viewer.