Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.55% covered (warning)
72.55%
222 / 306
47.62% covered (danger)
47.62%
10 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Options
72.55% covered (warning)
72.55%
222 / 306
47.62% covered (danger)
47.62%
10 / 21
212.39
0.00% covered (danger)
0.00%
0 / 1
 get_option_names
96.88% covered (success)
96.88%
93 / 96
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
 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, email, local_user_id, locked, confirmed_at, confirmed_by.
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)  The user choice and other data for the recommendations.
118            'recommendations_step',                // (string) The current step of the recommendations.
119            'recommendations_conditional',         // (array)  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) 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            'recommendations_evaluation',          // (object) Catalog of recommended modules with corresponding score following successful site evaluation in Welcome Banner.
130            'dismissed_recommendations',           // (bool) Determines if the recommendations have been dismissed or not.
131            'recommendations_first_run',           // (bool) Determines if the current recommendations are the initial default auto-loaded ones (without user input).
132            'historically_active_modules',         // (array) List of installed plugins/enabled modules that have at one point in time been active and working
133        );
134    }
135
136    /**
137     * Is the option name valid?
138     *
139     * @param string      $name  The name of the option.
140     * @param string|null $group The name of the group that the option is in. Default to null, which will search non_compact.
141     *
142     * @return bool Is the option name valid?
143     */
144    public static function is_valid( $name, $group = null ) {
145        if ( is_array( $name ) ) {
146            $compact_names = array();
147            foreach ( array_keys( self::$grouped_options ) as $_group ) {
148                $compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
149            }
150
151            $result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
152
153            return empty( $result );
154        }
155
156        if ( $group === null || 'non_compact' === $group ) {
157            if ( in_array( $name, self::get_option_names( $group ), true ) ) {
158                return true;
159            }
160        }
161
162        foreach ( array_keys( self::$grouped_options ) as $_group ) {
163            if ( $group === null || $group === $_group ) {
164                if ( in_array( $name, self::get_option_names( $_group ), true ) ) {
165                    return true;
166                }
167            }
168        }
169
170        return false;
171    }
172
173    /**
174     * Checks if an option must be saved for the whole network in WP Multisite
175     *
176     * @param string $option_name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
177     *
178     * @return bool
179     */
180    public static function is_network_option( $option_name ) {
181        if ( ! is_multisite() ) {
182            return false;
183        }
184        return in_array( $option_name, self::get_option_names( 'network' ), true );
185    }
186
187    /**
188     * Filters the requested option.
189     * This is a wrapper around `get_option_from_database` so that we can filter the option.
190     *
191     * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
192     * @param mixed  $default (optional).
193     *
194     * @return mixed
195     */
196    public static function get_option( $name, $default = false ) {
197        // Check if external storage should be used for this option
198        if ( self::should_use_external_storage( $name ) ) {
199            // Try external storage
200            if ( class_exists( 'Automattic\Jetpack\Connection\External_Storage' ) ) {
201                $external_value = \Automattic\Jetpack\Connection\External_Storage::get_value( $name );
202                if ( null !== $external_value ) {
203                    return $external_value;
204                }
205            }
206        }
207
208        /**
209         * Filter Jetpack Options.
210         * Can be useful in environments when Jetpack is running with a different setup
211         *
212         * @since 1.7.0
213         *
214         * @param string $value The value from the database.
215         * @param string $name Option name, _without_ `jetpack_%` prefix.
216         * @return string $value, unless the filters modify it.
217         */
218        return apply_filters( 'jetpack_options', self::get_option_from_database( $name, $default ), $name );
219    }
220
221    /**
222     * Returns the requested option.  Looks in jetpack_options or jetpack_$name as appropriate.
223     *
224     * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
225     * @param mixed  $default (optional).
226     *
227     * @return mixed
228     */
229    private static function get_option_from_database( $name, $default = false ) {
230        if ( self::is_valid( $name, 'non_compact' ) ) {
231            if ( self::is_network_option( $name ) ) {
232                return get_site_option( "jetpack_$name", $default );
233            }
234
235            return get_option( "jetpack_$name", $default );
236        }
237
238        foreach ( array_keys( self::$grouped_options ) as $group ) {
239            if ( self::is_valid( $name, $group ) ) {
240                return self::get_grouped_option( $group, $name, $default );
241            }
242        }
243
244        return $default;
245    }
246
247    /**
248     * Options that can be stored in external storage.
249     *
250     * @since 6.18.0
251     *
252     * @var array
253     */
254    private static $external_storage_allowlist = array( 'blog_token', 'id', 'master_user', 'protected_owner', 'user_tokens' );
255
256    /**
257     * Determines if external storage should be used for a given option.
258     * Simple allowlist check with global killswitch.
259     *
260     * @since 6.17.0
261     *
262     * @param string $name Option name, _without_ `jetpack_%` prefix.
263     * @return bool True if external storage should be checked for this option.
264     */
265    private static function should_use_external_storage( $name ) {
266        // Check allowlist and global killswitch
267        if ( ! in_array( $name, self::$external_storage_allowlist, true ) ||
268            ( defined( 'JETPACK_EXTERNAL_STORAGE_DISABLED' ) && constant( 'JETPACK_EXTERNAL_STORAGE_DISABLED' ) ) ) {
269            return false;
270        }
271
272        return true;
273    }
274
275    /**
276     * Returns the requested option, and ensures it's autoloaded in the future.
277     * This does _not_ adjust the prefix in any way (does not prefix jetpack_%)
278     *
279     * @param string $name Option name.
280     * @param mixed  $default (optional).
281     *
282     * @return mixed
283     */
284    public static function get_option_and_ensure_autoload( $name, $default ) {
285        // In this function the name is not adjusted by prefixing jetpack_
286        // so if it has already prefixed, we'll replace it and then
287        // check if the option name is a network option or not.
288        $jetpack_name      = preg_replace( '/^jetpack_/', '', $name, 1 );
289        $is_network_option = self::is_network_option( $jetpack_name );
290        $value             = $is_network_option ? get_site_option( $name ) : get_option( $name );
291
292        if ( false === $value && false !== $default ) {
293            if ( $is_network_option ) {
294                add_site_option( $name, $default );
295            } else {
296                add_option( $name, $default );
297            }
298            $value = $default;
299        }
300
301        return $value;
302    }
303
304    /**
305     * Update grouped option
306     *
307     * @param string $group Options group.
308     * @param string $name Options name.
309     * @param mixed  $value Options value.
310     *
311     * @return bool Success or failure.
312     */
313    private static function update_grouped_option( $group, $name, $value ) {
314        $options = get_option( self::$grouped_options[ $group ] );
315        if ( ! is_array( $options ) ) {
316            $options = array();
317        }
318        $options[ $name ] = $value;
319
320        return update_option( self::$grouped_options[ $group ], $options );
321    }
322
323    /**
324     * Updates the single given option.  Updates jetpack_options or jetpack_$name as appropriate.
325     *
326     * @param string    $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
327     * @param mixed     $value Option value.
328     * @param bool|null $autoload If not compact option, allows specifying whether to autoload or not.
329     *
330     * @return bool Was the option successfully updated?
331     */
332    public static function update_option( $name, $value, $autoload = null ) {
333        /**
334         * Fires before Jetpack updates a specific option.
335         *
336         * @since 1.1.2
337         * @since-jetpack 3.0.0
338         *
339         * @param string $name The name of the option being updated.
340         * @param mixed  $value The new value of the option.
341         */
342        do_action( 'pre_update_jetpack_option_' . $name, $name, $value );
343        if ( self::is_valid( $name, 'non_compact' ) ) {
344            if ( self::is_network_option( $name ) ) {
345                return update_site_option( "jetpack_$name", $value );
346            }
347
348            return update_option( "jetpack_$name", $value, $autoload );
349
350        }
351
352        foreach ( array_keys( self::$grouped_options ) as $group ) {
353            if ( self::is_valid( $name, $group ) ) {
354                return self::update_grouped_option( $group, $name, $value );
355            }
356        }
357
358        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.
359
360        return false;
361    }
362
363    /**
364     * Updates the multiple given options.  Updates jetpack_options and/or jetpack_$name as appropriate.
365     *
366     * @param array $array array( option name => option value, ... ).
367     */
368    public static function update_options( $array ) {
369        $names = array_keys( $array );
370
371        foreach ( array_diff( $names, self::get_option_names(), self::get_option_names( 'non_compact' ), self::get_option_names( 'private' ) ) as $unknown_name ) {
372            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.
373            unset( $array[ $unknown_name ] );
374        }
375
376        foreach ( $names as $name ) {
377            self::update_option( $name, $array[ $name ] );
378        }
379    }
380
381    /**
382     * Deletes the given option.  May be passed multiple option names as an array.
383     * Updates jetpack_options and/or deletes jetpack_$name as appropriate.
384     *
385     * @param string|array $names Option names. They must come _without_ `jetpack_%` prefix. The method will prefix the option names.
386     *
387     * @return bool Was the option successfully deleted?
388     */
389    public static function delete_option( $names ) {
390        $result = true;
391        $names  = (array) $names;
392
393        if ( ! self::is_valid( $names ) ) {
394            // phpcs:disable -- This line triggers a handful of errors; ignoring to avoid changing legacy behavior.
395            trigger_error( sprintf( 'Invalid Jetpack option names: %s', print_r( $names, 1 ) ), E_USER_WARNING );
396            // phpcs:enable
397            return false;
398        }
399
400        foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
401            if ( self::is_network_option( $name ) ) {
402                $result = delete_site_option( "jetpack_$name" );
403            } else {
404                $result = delete_option( "jetpack_$name" );
405            }
406        }
407
408        foreach ( array_keys( self::$grouped_options ) as $group ) {
409            if ( ! self::delete_grouped_option( $group, $names ) ) {
410                $result = false;
411            }
412        }
413
414        return $result;
415    }
416
417    /**
418     * Get group option.
419     *
420     * @param string $group Option group name.
421     * @param string $name Option name.
422     * @param mixed  $default Default option value.
423     *
424     * @return mixed Option.
425     */
426    private static function get_grouped_option( $group, $name, $default ) {
427        $options = get_option( self::$grouped_options[ $group ] );
428        if ( is_array( $options ) && isset( $options[ $name ] ) ) {
429            return $options[ $name ];
430        }
431
432        return $default;
433    }
434
435    /**
436     * Delete grouped option.
437     *
438     * @param string $group Option group name.
439     * @param array  $names Option names.
440     *
441     * @return bool Success or failure.
442     */
443    private static function delete_grouped_option( $group, $names ) {
444        $options = get_option( self::$grouped_options[ $group ], array() );
445
446        $to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
447        if ( $to_delete ) {
448            foreach ( $to_delete as $name ) {
449                unset( $options[ $name ] );
450            }
451
452            return update_option( self::$grouped_options[ $group ], $options );
453        }
454
455        return true;
456    }
457
458    /*
459     * Raw option methods allow Jetpack to get / update / delete options via direct DB queries, including options
460     * that are not created by the Jetpack plugin. This is helpful only in rare cases when we need to bypass
461     * cache and filters.
462     */
463
464    /**
465     * Deletes an option via $wpdb query.
466     *
467     * @param string $name Option name.
468     *
469     * @return bool Is the option deleted?
470     */
471    public static function delete_raw_option( $name ) {
472        if ( self::bypass_raw_option( $name ) ) {
473            return delete_option( $name );
474        }
475        global $wpdb;
476        $result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $name ) );
477        return $result;
478    }
479
480    /**
481     * Updates an option via $wpdb query.
482     *
483     * @param string $name Option name.
484     * @param mixed  $value Option value.
485     * @param bool   $autoload Specifying whether to autoload or not.
486     *
487     * @return bool Is the option updated?
488     */
489    public static function update_raw_option( $name, $value, $autoload = false ) {
490        if ( self::bypass_raw_option( $name ) ) {
491            return update_option( $name, $value, $autoload );
492        }
493        global $wpdb;
494        $autoload_value = $autoload ? 'yes' : 'no';
495
496        $old_value = $wpdb->get_var(
497            $wpdb->prepare(
498                "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
499                $name
500            )
501        );
502        if ( $old_value === $value ) {
503            return false;
504        }
505
506        $serialized_value = maybe_serialize( $value );
507        // below we used "insert ignore" to at least suppress the resulting error.
508        $updated_num = $wpdb->query(
509            $wpdb->prepare(
510                "UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
511                $serialized_value,
512                $name
513            )
514        );
515
516        // Try inserting the option if the value doesn't exits.
517        if ( ! $updated_num ) {
518            $updated_num = $wpdb->query(
519                $wpdb->prepare(
520                    "INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, %s )",
521                    $name,
522                    $serialized_value,
523                    $autoload_value
524                )
525            );
526        }
527        return (bool) $updated_num;
528    }
529
530    /**
531     * Gets an option via $wpdb query.
532     *
533     * @since 1.1.2
534     * @since-jetpack 5.4.0
535     *
536     * @param string $name Option name.
537     * @param mixed  $default Default option value if option is not found.
538     *
539     * @return mixed Option value, or null if option is not found and default is not specified.
540     */
541    public static function get_raw_option( $name, $default = null ) {
542        if ( self::bypass_raw_option( $name ) ) {
543            return get_option( $name, $default );
544        }
545
546        global $wpdb;
547        $value = $wpdb->get_var(
548            $wpdb->prepare(
549                "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
550                $name
551            )
552        );
553        $value = maybe_unserialize( $value );
554
555        if ( null === $value && null !== $default ) {
556            return $default;
557        }
558
559        return $value;
560    }
561
562    /**
563     * 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.
564     * Options can be selectively managed via a blocklist by filtering option names via the jetpack_disabled_raw_option filter.
565     *
566     * @param string $name Option name.
567     *
568     * @return bool
569     */
570    public static function bypass_raw_option( $name ) {
571
572        if ( Constants::get_constant( 'JETPACK_DISABLE_RAW_OPTIONS' ) ) {
573            return true;
574        }
575        /**
576         * Allows to disable particular raw options.
577         *
578         * @since 1.1.2
579         * @since-jetpack 5.5.0
580         *
581         * @param array $disabled_raw_options An array of option names that you can selectively blocklist from being managed via direct database queries.
582         */
583        $disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
584        return isset( $disabled_raw_options[ $name ] );
585    }
586
587    /**
588     * Gets all known options that are used by Jetpack and managed by Jetpack_Options.
589     *
590     * @since 1.1.2
591     * @since-jetpack 5.4.0
592     *
593     * @param boolean $strip_unsafe_options If true, and by default, will strip out options necessary for the connection to WordPress.com.
594     * @return array An array of all options managed via the Jetpack_Options class.
595     */
596    public static function get_all_jetpack_options( $strip_unsafe_options = true ) {
597        $jetpack_options            = self::get_option_names();
598        $jetpack_options_non_compat = self::get_option_names( 'non_compact' );
599        $jetpack_options_private    = self::get_option_names( 'private' );
600
601        $all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
602
603        if ( $strip_unsafe_options ) {
604            // Flag some Jetpack options as unsafe.
605            $unsafe_options = array(
606                'id',                           // (int)    The Client ID/WP.com Blog ID of this site.
607                'master_user',                  // (int)    The local User ID of the user who connected this site to jetpack.wordpress.com.
608                'protected_owner',              // (array)  Anchor identifying the locked connection owner. Resetting it would unlock ownership while the connection survives.
609                'version',                      // (string) Used during upgrade procedure to auto-activate new modules. version:time
610
611                // non_compact.
612                'activated',
613
614                // private.
615                'register',
616                'blog_token',                  // (string) The Client Secret/Blog Token of this site.
617                'user_token',                  // (string) The User Token of this site. (deprecated)
618                'user_tokens',
619            );
620
621            // Remove the unsafe Jetpack options.
622            foreach ( $unsafe_options as $unsafe_option ) {
623                $key = array_search( $unsafe_option, $all_jp_options, true );
624                if ( false !== $key ) {
625                    unset( $all_jp_options[ $key ] );
626                }
627            }
628        }
629
630        return $all_jp_options;
631    }
632
633    /**
634     * Get all options that are not managed by the Jetpack_Options class that are used by Jetpack.
635     *
636     * @since 1.1.2
637     * @since-jetpack 5.4.0
638     *
639     * @return array
640     */
641    public static function get_all_wp_options() {
642        // A manual build of the wp options.
643        return array(
644            'sharing-options',
645            'disabled_likes',
646            'disabled_reblogs',
647            'jetpack_comments_likes_enabled',
648            'stats_options',
649            'stats_dashboard_widget',
650            'safecss_preview_rev',
651            'safecss_rev',
652            'safecss_revision_migrated',
653            'nova_menu_order',
654            'jetpack_portfolio',
655            'jetpack_portfolio_posts_per_page',
656            'jetpack_testimonial',
657            'jetpack_testimonial_posts_per_page',
658            'sharedaddy_disable_resources',
659            'sharing-options',
660            'sharing-services',
661            'site_icon_temp_data',
662            'featured-content',
663            'site_logo',
664            'jetpack_dismissed_notices',
665            'jetpack-twitter-cards-site-tag',
666            'jetpack-sitemap-state',
667            'jetpack_sitemap_post_types',
668            'jetpack_sitemap_location',
669            'jetpack_protect_key',
670            'jetpack_protect_blocked_attempts',
671            'jetpack_protect_activating',
672            'jetpack_active_plan',
673            'jetpack_activation_source',
674            'jetpack_site_products',
675            'jetpack_sso_match_by_email',
676            'jetpack_sso_require_two_step',
677            'jetpack_sso_remove_login_form',
678            'jetpack_last_connect_url_check',
679            'jetpack_excluded_extensions',
680        );
681    }
682
683    /**
684     * Gets all options that can be safely reset by CLI.
685     *
686     * @since 1.1.2
687     * @since-jetpack 5.4.0
688     *
689     * @return array array Associative array containing jp_options which are managed by the Jetpack_Options class and wp_options which are not.
690     */
691    public static function get_options_for_reset() {
692        $all_jp_options = self::get_all_jetpack_options();
693
694        $wp_options = self::get_all_wp_options();
695
696        $options = array(
697            'jp_options' => $all_jp_options,
698            'wp_options' => $wp_options,
699        );
700
701        return $options;
702    }
703
704    /**
705     * Delete all known options
706     *
707     * @since 1.1.2
708     * @since-jetpack 5.4.0
709     *
710     * @return void
711     */
712    public static function delete_all_known_options() {
713        // Delete all compact options.
714        foreach ( (array) self::$grouped_options as $option_name ) {
715            delete_option( $option_name );
716        }
717
718        // Delete all non-compact Jetpack options.
719        foreach ( (array) self::get_option_names( 'non-compact' ) as $option_name ) {
720            self::delete_option( $option_name );
721        }
722
723        // Delete all options that can be reset via CLI, that aren't Jetpack options.
724        foreach ( (array) self::get_all_wp_options() as $option_name ) {
725            delete_option( $option_name );
726        }
727    }
728}