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