|
| 1 | +<?php |
| 2 | + |
| 3 | +// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery |
| 4 | + |
| 5 | +namespace Automattic\VIP\Core\OptionsAPI; |
| 6 | + |
| 7 | +/** |
| 8 | + * Add additional protections around the alloptions functionality. |
| 9 | + * |
| 10 | + * Note that there is one (unavoidable) core call to get_option() before this filter is registered (in wp_plugin_directory_constants()), |
| 11 | + * So by the time this starts filtering, there's already been one occurrence of wp_load_alloptions(). |
| 12 | + * |
| 13 | + * Here we re-implement most of what core does in wp_load_alloptions(), with some notable adjustments: |
| 14 | + * - 1) Prevent spamming memcached & the DB if memcached is unable to add() the key to cache. |
| 15 | + * - 2) Kill the request if options cannot be retrieved from the database (or cache). |
| 16 | + */ |
| 17 | +add_filter( 'pre_wp_load_alloptions', __NAMESPACE__ . '\pre_wp_load_alloptions_protections', 2, 999 ); |
| 18 | +function pre_wp_load_alloptions_protections( $pre_loaded_alloptions, $force_cache ) { |
| 19 | + global $wpdb; |
| 20 | + static $fallback_cache = []; |
| 21 | + |
| 22 | + // Allow other filters the chance to early return (before priority 999). |
| 23 | + // And abort from this special handling during installations. |
| 24 | + if ( is_array( $pre_loaded_alloptions ) || wp_installing() ) { |
| 25 | + return $pre_loaded_alloptions; |
| 26 | + } |
| 27 | + |
| 28 | + // 1) If successfully fetched from cache, return right away. |
| 29 | + $alloptions_from_cache = wp_cache_get( 'alloptions', 'options', $force_cache ); |
| 30 | + if ( ! empty( $alloptions_from_cache ) && is_array( $alloptions_from_cache ) ) { |
| 31 | + return apply_filters( 'alloptions', $alloptions_from_cache ); |
| 32 | + } |
| 33 | + |
| 34 | + // 2) Return from the local fallback cache if available, helping prevent excess queries in cases where memcached is unable to save the results. |
| 35 | + $blog_id = get_current_blog_id(); |
| 36 | + if ( ! $force_cache && isset( $fallback_cache[ $blog_id ] ) ) { |
| 37 | + return apply_filters( 'alloptions', $fallback_cache[ $blog_id ] ); |
| 38 | + } |
| 39 | + |
| 40 | + // 3) Otherwise query the DB for fresh results. |
| 41 | + $suppress = $wpdb->suppress_errors(); |
| 42 | + $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'" ); |
| 43 | + $wpdb->suppress_errors( $suppress ); |
| 44 | + |
| 45 | + $alloptions = []; |
| 46 | + foreach ( (array) $alloptions_db as $o ) { |
| 47 | + $alloptions[ $o->option_name ] = $o->option_value; |
| 48 | + } |
| 49 | + |
| 50 | + if ( empty( $alloptions ) ) { |
| 51 | + trigger_error( 'VIP: Unable to query alloptions from database.', E_USER_WARNING ); |
| 52 | + |
| 53 | + if ( defined( '_VIP_DIE_ON_ALLOPTIONS_FAILURE' ) && true === constant( '_VIP_DIE_ON_ALLOPTIONS_FAILURE' ) ) { |
| 54 | + http_response_code( 503 ); |
| 55 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- no need to escape the premade HTML file |
| 56 | + echo file_get_contents( dirname( __DIR__ ) . '/errors/alloptions-limit.html' ); |
| 57 | + exit; |
| 58 | + } |
| 59 | + |
| 60 | + return apply_filters( 'alloptions', apply_filters( 'pre_cache_alloptions', $alloptions ) ); |
| 61 | + } |
| 62 | + |
| 63 | + $alloptions = apply_filters( 'pre_cache_alloptions', $alloptions ); |
| 64 | + $add_result = wp_cache_add( 'alloptions', $alloptions, 'options' ); |
| 65 | + |
| 66 | + if ( false === $add_result && false === wp_cache_get( 'alloptions', 'options', true ) ) { |
| 67 | + // Prevent memory issues in case something is looping over thousands of subsites. |
| 68 | + if ( count( $fallback_cache ) > 10 ) { |
| 69 | + $fallback_cache = []; |
| 70 | + } |
| 71 | + |
| 72 | + // Start using the fallback cache if this request both failed to add() to cache, and there is |
| 73 | + // nothing currently there - indicating there is likely something wrong with the ability to cache alloptions. |
| 74 | + // Note that this is already the second time the request would have tried. |
| 75 | + $fallback_cache[ $blog_id ] = $alloptions; |
| 76 | + } |
| 77 | + |
| 78 | + return apply_filters( 'alloptions', $alloptions ); |
| 79 | +} |
0 commit comments