Make WordPress Core

Changeset 56508


Ignore:
Timestamp:
09/01/2023 07:55:11 PM (8 months 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.