Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.25% covered (warning)
73.25%
230 / 314
50.00% covered (danger)
50.00%
11 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Options
73.25% covered (warning)
73.25%
230 / 314
50.00% covered (danger)
50.00%
11 / 22
219.09
0.00% covered (danger)
0.00%
0 / 1
 get_option_names
96.94% covered (success)
96.94%
95 / 98
0.00% covered (danger)
0.00%
0 / 1
5
 is_valid
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
10
 is_network_option
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 get_option
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 option_exists
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 get_option_from_database
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 should_use_external_storage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 get_option_and_ensure_autoload
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 update_grouped_option
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 update_option
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
5.68
 update_options
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 delete_option
69.23% covered (warning)
69.23%
9 / 13
0.00% covered (danger)
0.00%
0 / 1
7.05
 get_grouped_option
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 delete_grouped_option
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 delete_raw_option
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 update_raw_option
68.97% covered (warning)
68.97%
20 / 29
0.00% covered (danger)
0.00%
0 / 1
5.75
 get_raw_option
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 bypass_raw_option
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 get_all_jetpack_options
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 get_all_wp_options
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
2
 get_options_for_reset
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 delete_all_known_options
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Legacy Jetpack_Options class.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8use Automattic\Jetpack\Constants;
9
10/**
11 * Class Jetpack_Options
12 */
13class Jetpack_Options {
14
15    /**
16     * An array that maps a grouped option type to an option name.
17     *
18     * @var array
19     */
20    private static $grouped_options = array(
21        'compact' => 'jetpack_options',
22        'private' => 'jetpack_private_options',
23    );
24
25    /**
26     * Returns an array of option names for a given type.
27     *
28     * @param string $type The type of option to return. Defaults to 'compact'.
29     *
30     * @return array
31     */
32    public static function get_option_names( $type = 'compact' ) {
33        switch ( $type ) {
34            case 'non-compact':
35            case 'non_compact':
36                return array(
37                    'activated',
38                    'active_modules',
39                    'active_modules_initialized', // (bool) used to determine that all the default modules were activated, so we know how to act on a reconnection.
40                    'allowed_xsite_search_ids', // (array) Array of WP.com blog ids that are allowed to search the content of this site
41                    'available_modules',
42                    'do_activate',
43                    'log',
44                    'slideshow_background_color',
45                    'widget_twitter',
46                    'wpcc_options',
47                    'relatedposts',
48                    'file_data',
49                    'autoupdate_plugins',          // (array)  An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated
50                    'autoupdate_plugins_translations', // (array)  An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated translation files.
51                    'autoupdate_themes',           // (array)  An array of theme ids ( eg. twentyfourteen ) that should be autoupdated
52                    'autoupdate_themes_translations', // (array)  An array of theme ids ( eg. twentyfourteen ) that should autoupdated translation files.
53                    'autoupdate_core',             // (bool)   Whether or not to autoupdate core
54                    'autoupdate_translations',     // (bool)   Whether or not to autoupdate all translations
55                    'json_api_full_management',    // (bool)   Allow full management (eg. Activate, Upgrade plugins) of the site via the JSON API.
56                    'sync_non_public_post_stati',  // (bool)   Allow synchronisation of posts and pages with non-public status.
57                    'site_icon_url',               // (string) url to the full site icon
58                    'site_icon_id',                // (int)    Attachment id of the site icon file
59                    'dismissed_manage_banner',     // (bool) Dismiss Jetpack manage banner allows the user to dismiss the banner permanently
60                    'unique_connection',           // (array)  A flag to determine a unique connection to wordpress.com two values "connected" and "disconnected" with values for how many times each has occurred
61                    'unique_registrations',        // (integer) A counter of how many times the site was registered
62                    'protect_whitelist',           // (array) IP Address for the Protect module to ignore
63                    'sync_error_idc',              // (bool|array) false or array containing the site's home and siteurl at time of IDC error
64                    'sync_health_status',          // (bool|array) An array of data relating to Jetpack's sync health.
65                    'safe_mode_confirmed',         // (bool) True if someone confirms that this site was correctly put into safe mode automatically after an identity crisis is discovered.
66                    'migrate_for_idc',             // (bool) True if someone confirms that this site should migrate stats and subscribers from its previous URL
67                    'ab_connect_banner_green_bar', // (int) Version displayed of the A/B test for the green bar at the top of the connect banner.
68                    'tos_agreed',                  // (bool)   Whether or not the TOS for connection has been agreed upon.
69                    'static_asset_cdn_files',      // (array) An nested array of files that we can swap out for cdn versions.
70                    'mapbox_api_key',              // (string) Mapbox API Key, for use with Map block.
71                    'mailchimp',                   // (string) Mailchimp keyring data, for mailchimp block.
72                    'xmlrpc_errors',               // (array) Keys are XML-RPC signature error codes. Values are truthy.
73                    'dismissed_wizard_banner',     // (int) (DEPRECATED) True if the Wizard banner has been dismissed.
74                );
75
76            case 'private':
77                return array(
78                    'blog_token',  // (string) The Client Secret/Blog Token of this site.
79                    'user_token',  // (string) The User Token of this site. (deprecated)
80                    'user_tokens',  // (array)  User Tokens for each user of this site who has connected to jetpack.wordpress.com.
81                    'purchase_token', // (string) Token for logged out user purchases.
82                    'token_lock', // (string) Token lock in format `expiration_date|||site_url`.
83                );
84
85            case 'network':
86                return array(
87                    'file_data',                     // (array) List of absolute paths to all Jetpack modules
88                );
89        }
90
91        return array(
92            'id',                                  // (int)    The Client ID/WP.com Blog ID of this site.
93            'publicize_connections',               // (array)  An array of Publicize connections from WordPress.com.
94            'master_user',                         // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
95            'protected_owner',                     // (array)  Anchor identifying the locked connection owner. WordPress.com is authoritative; this is a local cache. Keys: wpcom_user_id, local_user_id, confirmed_at.
96            'version',                             // (string) Used during upgrade procedure to auto-activate new modules. version:time.
97            'old_version',                         // (string) Used to determine which modules are the most recently added. previous_version:time.
98            'fallback_no_verify_ssl_certs',        // (int)    Flag for determining if this host must skip SSL Certificate verification due to misconfigured SSL.
99            'time_diff',                           // (int)    Offset between Jetpack server's clocks and this server's clocks. Jetpack Server Time = time() + (int) Jetpack_Options::get_option( 'time_diff' )
100            'public',                              // (int|bool) If we think this site is public or not (1, 0), false if we haven't yet tried to figure it out.
101            'videopress',                          // (array)  VideoPress options array.
102            'is_network_site',                     // (int|bool) If we think this site is a network or a single blog (1, 0), false if we haven't yet tried to figue it out.
103            'social_links',                        // (array)  The specified links for each social networking site.
104            'identity_crisis_whitelist',           // (array)  An array of options, each having an array of the values whitelisted for it.
105            'gplus_authors',                       // (array)  The Google+ authorship information for connected users.
106            'last_heartbeat',                      // (int)    The timestamp of the last heartbeat that fired.
107            'hide_jitm',                           // (array)  A list of just in time messages that we should not show because they have been dismissed by the user.
108            'custom_css_4.7_migration',            // (bool)   Whether Custom CSS has scanned for and migrated any legacy CSS CPT entries to the new Core format.
109            'image_widget_migration',              // (bool)   Whether any legacy Image Widgets have been converted to the new Core widget.
110            'gallery_widget_migration',            // (bool)   Whether any legacy Gallery Widgets have been converted to the new Core widget.
111            'sso_first_login',                     // (bool)   Is this the first time the user logins via SSO.
112            'dismissed_hints',                     // (array)  Part of Plugin Search Hints. List of cards that have been dismissed.
113            'first_admin_view',                    // (bool)   Set to true the first time the user views the admin. Usually after the initial connection.
114            'setup_wizard_questionnaire',          // (array)  (DEPRECATED) List of user choices from the setup wizard.
115            'setup_wizard_status',                 // (string) (DEPRECATED) Status of the setup wizard.
116            'licensing_error',                     // (string) Last error message occurred while attaching licenses that is yet to be surfaced to the user.
117            'recommendations_data',                // (array)  (DEPRECATED) The user choice and other data for the recommendations.
118            'recommendations_step',                // (string) (DEPRECATED) The current step of the recommendations.
119            'recommendations_conditional',         // (array)  (DEPRECATED) An array of action-based recommendations.
120            'licensing_activation_notice_dismiss', // (array) The `last_detached_count` and the `last_dismissed_time` for the user-license activation notice.
121            'has_seen_wc_connection_modal',        // (bool) (DEPRECATED) Whether the site has displayed the WooCommerce Connection modal
122            'partner_coupon',                      // (string) A Jetpack partner issued coupon to promote a sale together with Jetpack.
123            'partner_coupon_added',                // (string) A date for when `partner_coupon` was added, so we can auto-purge after a certain time interval.
124            'dismissed_backup_review_restore',     // (bool) Determines if the component review request is dismissed for successful restore requests.
125            'dismissed_backup_review_backups',     // (bool) Determines if the component review request is dismissed for successful backup requests.
126            'identity_crisis_url_secret',          // (array) The IDC URL secret and its expiration date.
127            'identity_crisis_ip_requester',        // (array) The IDC IP address and its expiration date.
128            'dismissed_welcome_banner',            // (bool) Determines if the welcome banner has been dismissed or not.
129            'dismissed_a4a_banner',                // (bool) Determines if the Automattic for Agencies banner has been dismissed or not.
130            'recommendations_evaluation',          // (object) Catalog of recommended modules with corresponding score following successful site evaluation in Welcome Banner.
131            'dismissed_recommendations',           // (bool) Determines if the recommendations have been dismissed or not.
132            'recommendations_first_run',           // (bool) Determines if the current recommendations are the initial default auto-loaded ones (without user input).
133            'historically_active_modules',         // (array) List of installed plugins/enabled modules that have at one point in time been active and working
134            'activity_log_default_activated',      // (bool) Whether the Activity Log module has been switched on once on a site with no Jetpack plugin, which has nothing equivalent to `Auto Activate: Yes`. Recorded so a later opt-out is not undone on the next request.
135        );
136    }
137
138    /**
139     * Is the option name valid?
140     *
141     * @param string      $name  The name of the option.
142     * @param string|null $group The name of the group that the option is in. Default to null, which will search non_compact.
143     *
144     * @return bool Is the option name valid?
145     */
146    public static function is_valid( $name, $group = null ) {
147        if ( is_array( $name ) ) {
148            $compact_names = array();
149            foreach ( array_keys( self::$grouped_options ) as $_group ) {
150                $compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
151            }
152
153            $result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
154
155            return empty( $result );
156        }
157
158        if ( $group === null || 'non_compact' === $group ) {
159            if ( in_array( $name, self::get_option_names( $group ), true ) ) {
160                return true;
161            }
162        }
163
164        foreach ( array_keys( self::$grouped_options ) as $_group ) {
165            if ( $group === null || $group === $_group ) {
166                if ( in_array( $name, self::get_option_names( $_group ), true ) ) {
167                    return true;
168                }
169            }
170        }
171
172        return false;
173    }
174
175    /**
176     * Checks if an option must be saved for the whole network in WP Multisite
177     *
178     * @param string $option_name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
179     *
180     * @return bool
181     */
182    public static function is_network_option( $option_name ) {
183        if ( ! is_multisite() ) {
184            return false;
185        }
186        return in_array( $option_name, self::get_option_names( 'network' ), true );
187    }
188
189    /**
190     * Filters the requested option.
191     * This is a wrapper around `get_option_from_database` so that we can filter the option.
192     *
193     * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
194     * @param mixed  $default (optional).
195     *
196     * @return mixed
197     */
198    public static function get_option( $name, $default = false ) {
199        // Check if external storage should be used for this option
200        if ( self::should_use_external_storage( $name ) ) {
201            // Try external storage
202            if ( class_exists( 'Automattic\Jetpack\Connection\External_Storage' ) ) {
203                $external_value = \Automattic\Jetpack\Connection\External_Storage::get_value( $name );
204                if ( null !== $external_value ) {
205                    return $external_value;
206                }
207            }
208        }
209
210        /**
211         * Filter Jetpack Options.
212         * Can be useful in environments when Jetpack is running with a different setup
213         *
214         * @since 1.7.0
215         *
216         * @param string $value The value from the database.
217         * @param string $name Option name, _without_ `jetpack_%` prefix.
218         * @return string $value, unless the filters modify it.
219         */
220        return apply_filters( 'jetpack_options', self::get_option_from_database( $name, $default ), $name );
221    }
222
223    /**
224     * Checks whether an option has a stored value, distinguishing an absent option from one stored
225     * as a falsy value. Reads from the same storage as `get_option` (external storage or database)
226     * but does not apply the `jetpack_options` filter, so a filter override is not mistaken for a
227     * stored value.
228     *
229     * @since 8.11.0
230     *
231     * @param string $name Option name. It must come _without_ `jetpack_%` prefix.
232     *
233     * @return bool Whether the option is stored.
234     */
235    public static function option_exists( $name ) {
236        if ( self::should_use_external_storage( $name )
237            && class_exists( 'Automattic\Jetpack\Connection\External_Storage' )
238            && null !== \Automattic\Jetpack\Connection\External_Storage::get_value( $name )
239        ) {
240            return true;
241        }
242
243        // A value no caller would ever store, so getting it back means the option is absent.
244        $sentinel = '__jetpack_option_absent__';
245        return $sentinel !== self::get_option_from_database( $name, $sentinel );
246    }
247
248    /**
249     * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
250     *
251     * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
252     * @param mixed  $default (optional).
253     *
254     * @return mixed
255     */
256    private static function get_option_from_database( $name, $default = false ) {
257        if ( self::is_valid( $name, 'non_compact' ) ) {
258            if ( self::is_network_option( $name ) ) {
259                return get_site_option( "jetpack_$name", $default );
260            }
261
262            return get_option( "jetpack_$name", $default );
263        }
264
265        foreach ( array_keys( self::$grouped_options ) as $group ) {
266            if ( self::is_valid( $name, $group ) ) {
267                return self::get_grouped_option( $group, $name, $default );
268            }
269        }
270
271        return $default;
272    }
273
274    /**
275     * Options that can be stored in external storage.
276     *
277     * @since 6.18.0
278     *
279     * @var array
280     */
281    private static $external_storage_allowlist = array( 'blog_token', 'id', 'master_user', 'protected_owner', 'user_tokens' );
282
283    /**
284     * Determines if external storage should be used for a given option.
285     * Simple allowlist check with global killswitch.
286     *
287     * @since 6.17.0
288     *
289     * @param string $name Option name, _without_ `jetpack_%` prefix.
290     * @return bool True if external storage should be checked for this option.
291     */
292    private static function should_use_external_storage( $name ) {
293        // Check allowlist and global killswitch
294        if ( ! in_array( $name, self::$external_storage_allowlist, true ) ||
295            ( defined( 'JETPACK_EXTERNAL_STORAGE_DISABLED' ) && constant( 'JETPACK_EXTERNAL_STORAGE_DISABLED' ) ) ) {
296            return false;
297        }
298
299        return true;
300    }
301
302    /**
303     * Returns the requested option, and ensures it's autoloaded in the future.
304     * This does _not_ adjust the prefix in any way (does not prefix jetpack_%)
305     *
306     * @param string $name Option name.
307     * @param mixed  $default (optional).
308     *
309     * @return mixed
310     */
311    public static function get_option_and_ensure_autoload( $name, $default ) {
312        // In this function the name is not adjusted by prefixing jetpack_
313        // so if it has already prefixed, we'll replace it and then
314        // check if the option name is a network option or not.
315        $jetpack_name      = preg_replace( '/^jetpack_/', '', $name, 1 );
316        $is_network_option = self::is_network_option( $jetpack_name );
317        $value             = $is_network_option ? get_site_option( $name ) : get_option( $name );
318
319        if ( false === $value && false !== $default ) {
320            if ( $is_network_option ) {
321                add_site_option( $name, $default );
322            } else {
323                add_option( $name, $default );
324            }
325            $value = $default;
326        }
327
328        return $value;
329    }
330
331    /**
332     * Update grouped option
333     *
334     * @param string $group Options group.
335     * @param string $name Options name.
336     * @param mixed  $value Options value.
337     *
338     * @return bool Success or failure.
339     */
340    private static function update_grouped_option( $group, $name, $value ) {
341        $options = get_option( self::$grouped_options[ $group ] );
342        if ( ! is_array( $options ) ) {
343            $options = array();
344        }
345        $options[ $name ] = $value;
346
347        return update_option( self::$grouped_options[ $group ], $options );
348    }
349
350    /**
351     * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
352     *
353     * @param string    $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
354     * @param mixed     $value Option value.
355     * @param bool|null $autoload If not compact option, allows specifying whether to autoload or not.
356     *
357     * @return bool Was the option successfully updated?
358     */
359    public static function update_option( $name, $value, $autoload = null ) {
360        /**
361         * Fires before Jetpack updates a specific option.
362         *
363         * @since 1.1.2
364         * @since-jetpack 3.0.0
365         *
366         * @param string $name The name of the option being updated.
367         * @param mixed  $value The new value of the option.
368         */
369        do_action( 'pre_update_jetpack_option_' . $name, $name, $value );
370        if ( self::is_valid( $name, 'non_compact' ) ) {
371            if ( self::is_network_option( $name ) ) {
372                return update_site_option( "jetpack_$name", $value );
373            }
374
375            return update_option( "jetpack_$name", $value, $autoload );
376
377        }
378
379        foreach ( array_keys( self::$grouped_options ) as $group ) {
380            if ( self::is_valid( $name, $group ) ) {
381                return self::update_grouped_option( $group, $name, $value );
382            }
383        }
384
385        trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't want to change legacy behavior.
386
387        return false;
388    }
389
390    /**
391     * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
392     *
393     * @param array $array array( option name => option value, ... ).
394     */
395    public static function update_options( $array ) {
396        $names = array_keys( $array );
397
398        foreach ( array_diff( $names, self::get_option_names(), self::get_option_names( 'non_compact' ), self::get_option_names( 'private' ) ) as $unknown_name ) {
399            trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $unknown_name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't change legacy behavior.
400            unset( $array[ $unknown_name ] );
401        }
402
403        foreach ( $names as $name ) {
404            self::update_option( $name, $array[ $name ] );
405        }
406    }
407
408    /**
409     * Deletes the given option.  May be passed multiple option names as an array.
410     * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
411     *
412     * @param string|array $names Option names. They must come _without_ `jetpack_%` prefix. The method will prefix the option names.
413     *
414     * @return bool Was the option successfully deleted?
415     */
416    public static function delete_option( $names ) {
417        $result = true;
418        $names  = (array) $names;
419
420        if ( ! self::is_valid( $names ) ) {
421            // phpcs:disable -- This line triggers a handful of errors; ignoring to avoid changing legacy behavior.
422            trigger_error( sprintf( 'Invalid Jetpack option names: %s', print_r( $names, 1 ) ), E_USER_WARNING );
423            // phpcs:enable
424            return false;
425        }
426
427        foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
428            if ( self::is_network_option( $name ) ) {
429                $result = delete_site_option( "jetpack_$name" );
430            } else {
431                $result = delete_option( "jetpack_$name" );
432            }
433        }
434
435        foreach ( array_keys( self::$grouped_options ) as $group ) {
436            if ( ! self::delete_grouped_option( $group, $names ) ) {
437                $result = false;
438            }
439        }
440
441        return $result;
442    }
443
444    /**
445     * Get group option.
446     *
447     * @param string $group Option group name.
448     * @param string $name Option name.
449     * @param mixed  $default Default option value.
450     *
451     * @return mixed Option.
452     */
453    private static function get_grouped_option( $group, $name, $default ) {
454        $options = get_option( self::$grouped_options[ $group ] );
455        if ( is_array( $options ) && isset( $options[ $name ] ) ) {
456            return $options[ $name ];
457        }
458
459        return $default;
460    }
461
462    /**
463     * Delete grouped option.
464     *
465     * @param string $group Option group name.
466     * @param array  $names Option names.
467     *
468     * @return bool Success or failure.
469     */
470    private static function delete_grouped_option( $group, $names ) {
471        $options = get_option( self::$grouped_options[ $group ], array() );
472
473        $to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
474        if ( $to_delete ) {
475            foreach ( $to_delete as $name ) {
476                unset( $options[ $name ] );
477            }
478
479            return update_option( self::$grouped_options[ $group ], $options );
480        }
481
482        return true;
483    }
484
485    /*
486     * Raw option methods allow Jetpack to get / update / delete options via direct DB queries, including options
487     * that are not created by the Jetpack plugin. This is helpful only in rare cases when we need to bypass
488     * cache and filters.
489     */
490
491    /**
492     * Deletes an option via $wpdb query.
493     *
494     * @param string $name Option name.
495     *
496     * @return bool Is the option deleted?
497     */
498    public static function delete_raw_option( $name ) {
499        if ( self::bypass_raw_option( $name ) ) {
500            return delete_option( $name );
501        }
502        global $wpdb;
503        $result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $name ) );
504        return $result;
505    }
506
507    /**
508     * Updates an option via $wpdb query.
509     *
510     * @param string $name Option name.
511     * @param mixed  $value Option value.
512     * @param bool   $autoload Specifying whether to autoload or not.
513     *
514     * @return bool Is the option updated?
515     */
516    public static function update_raw_option( $name, $value, $autoload = false ) {
517        if ( self::bypass_raw_option( $name ) ) {
518            return update_option( $name, $value, $autoload );
519        }
520        global $wpdb;
521        $autoload_value = $autoload ? 'yes' : 'no';
522
523        $old_value = $wpdb->get_var(
524            $wpdb->prepare(
525                "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
526                $name
527            )
528        );
529        if ( $old_value === $value ) {
530            return false;
531        }
532
533        $serialized_value = maybe_serialize( $value );
534        // below we used "insert ignore" to at least suppress the resulting error.
535        $updated_num = $wpdb->query(
536            $wpdb->prepare(
537                "UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
538                $serialized_value,
539                $name
540            )
541        );
542
543        // Try inserting the option if the value doesn't exits.
544        if ( ! $updated_num ) {
545            $updated_num = $wpdb->query(
546                $wpdb->prepare(
547                    "INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, %s )",
548                    $name,
549                    $serialized_value,
550                    $autoload_value
551                )
552            );
553        }
554        return (bool) $updated_num;
555    }
556
557    /**
558     * Gets an option via $wpdb query.
559     *
560     * @since 1.1.2
561     * @since-jetpack 5.4.0
562     *
563     * @param string $name Option name.
564     * @param mixed  $default Default option value if option is not found.
565     *
566     * @return mixed Option value, or null if option is not found and default is not specified.
567     */
568    public static function get_raw_option( $name, $default = null ) {
569        if ( self::bypass_raw_option( $name ) ) {
570            return get_option( $name, $default );
571        }
572
573        global $wpdb;
574        $value = $wpdb->get_var(
575            $wpdb->prepare(
576                "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
577                $name
578            )
579        );
580        $value = maybe_unserialize( $value );
581
582        if ( null === $value && null !== $default ) {
583            return $default;
584        }
585
586        return $value;
587    }
588
589    /**
590     * This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
591     * Options can be selectively managed via a blocklist by filtering option names via the jetpack_disabled_raw_option filter.
592     *
593     * @param string $name Option name.
594     *
595     * @return bool
596     */
597    public static function bypass_raw_option( $name ) {
598
599        if ( Constants::get_constant( 'JETPACK_DISABLE_RAW_OPTIONS' ) ) {
600            return true;
601        }
602        /**
603         * Allows to disable particular raw options.
604         *
605         * @since 1.1.2
606         * @since-jetpack 5.5.0
607         *
608         * @param array $disabled_raw_options An array of option names that you can selectively blocklist from being managed via direct database queries.
609         */
610        $disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
611        return isset( $disabled_raw_options[ $name ] );
612    }
613
614    /**
615     * Gets all known options that are used by Jetpack and managed by Jetpack_Options.
616     *
617     * @since 1.1.2
618     * @since-jetpack 5.4.0
619     *
620     * @param boolean $strip_unsafe_options If true, and by default, will strip out options necessary for the connection to WordPress.com.
621     * @return array An array of all options managed via the Jetpack_Options class.
622     */
623    public static function get_all_jetpack_options( $strip_unsafe_options = true ) {
624        $jetpack_options            = self::get_option_names();
625        $jetpack_options_non_compat = self::get_option_names( 'non_compact' );
626        $jetpack_options_private    = self::get_option_names( 'private' );
627
628        $all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
629
630        if ( $strip_unsafe_options ) {
631            // Flag some Jetpack options as unsafe.
632            $unsafe_options = array(
633                'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
634                'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
635                'protected_owner',              // (array)  Anchor identifying the locked connection owner. Resetting it would unlock ownership while the connection survives.
636                'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
637
638                // non_compact.
639                'activated',
640
641                // private.
642                'register',
643                'blog_token',                  // (string) The Client Secret/Blog Token of this site.
644                'user_token',                  // (string) The User Token of this site. (deprecated)
645                'user_tokens',
646            );
647
648            // Remove the unsafe Jetpack options.
649            foreach ( $unsafe_options as $unsafe_option ) {
650                $key = array_search( $unsafe_option, $all_jp_options, true );
651                if ( false !== $key ) {
652                    unset( $all_jp_options[ $key ] );
653                }
654            }
655        }
656
657        return $all_jp_options;
658    }
659
660    /**
661     * Get all options that are not managed by the Jetpack_Options class that are used by Jetpack.
662     *
663     * @since 1.1.2
664     * @since-jetpack 5.4.0
665     *
666     * @return array
667     */
668    public static function get_all_wp_options() {
669        // A manual build of the wp options.
670        return array(
671            'sharing-options',
672            'disabled_likes',
673            'disabled_reblogs',
674            'jetpack_comments_likes_enabled',
675            'stats_options',
676            'stats_dashboard_widget',
677            'safecss_preview_rev',
678            'safecss_rev',
679            'safecss_revision_migrated',
680            'nova_menu_order',
681            'jetpack_portfolio',
682            'jetpack_portfolio_posts_per_page',
683            'jetpack_testimonial',
684            'jetpack_testimonial_posts_per_page',
685            'sharedaddy_disable_resources',
686            'sharing-options',
687            'sharing-services',
688            'site_icon_temp_data',
689            'featured-content',
690            'site_logo',
691            'jetpack_dismissed_notices',
692            'jetpack-twitter-cards-site-tag',
693            'jetpack-sitemap-state',
694            'jetpack_sitemap_post_types',
695            'jetpack_sitemap_location',
696            'jetpack_protect_key',
697            'jetpack_protect_blocked_attempts',
698            'jetpack_protect_activating',
699            'jetpack_active_plan',
700            'jetpack_activation_source',
701            'jetpack_site_products',
702            'jetpack_sso_match_by_email',
703            'jetpack_sso_require_two_step',
704            'jetpack_sso_remove_login_form',
705            'jetpack_last_connect_url_check',
706            'jetpack_excluded_extensions',
707        );
708    }
709
710    /**
711     * Gets all options that can be safely reset by CLI.
712     *
713     * @since 1.1.2
714     * @since-jetpack 5.4.0
715     *
716     * @return array array Associative array containing jp_options which are managed by the Jetpack_Options class and wp_options which are not.
717     */
718    public static function get_options_for_reset() {
719        $all_jp_options = self::get_all_jetpack_options();
720
721        $wp_options = self::get_all_wp_options();
722
723        $options = array(
724            'jp_options' => $all_jp_options,
725            'wp_options' => $wp_options,
726        );
727
728        return $options;
729    }
730
731    /**
732     * Delete all known options
733     *
734     * @since 1.1.2
735     * @since-jetpack 5.4.0
736     *
737     * @return void
738     */
739    public static function delete_all_known_options() {
740        // Delete all compact options.
741        foreach ( (array) self::$grouped_options as $option_name ) {
742            delete_option( $option_name );
743        }
744
745        // Delete all non-compact Jetpack options.
746        foreach ( (array) self::get_option_names( 'non-compact' ) as $option_name ) {
747            self::delete_option( $option_name );
748        }
749
750        // Delete all options that can be reset via CLI, that aren't Jetpack options.
751        foreach ( (array) self::get_all_wp_options() as $option_name ) {
752            delete_option( $option_name );
753        }
754    }
755}