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