PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-sync / src / class-dedicated-sender.php
jetpack / jetpack_vendor / automattic / jetpack-sync / src Last commit date
modules 1 month ago replicastore 4 months ago sync-queue 6 months ago class-actions.php 3 months ago class-activity-log-event.php 1 month ago class-data-settings.php 2 months ago class-dedicated-sender.php 3 days ago class-default-filter-settings.php 4 years ago class-defaults.php 1 month ago class-functions.php 2 months ago class-health.php 5 months ago class-json-deflate-array-codec.php 8 months ago class-listener.php 2 months ago class-lock.php 4 years ago class-main.php 3 months ago class-modules.php 2 years ago class-package-version.php 3 days ago class-queue-buffer.php 4 years ago class-queue.php 2 months ago class-replicastore.php 2 months ago class-rest-endpoints.php 5 months ago class-rest-sender.php 2 months ago class-sender.php 2 months ago class-server.php 5 months ago class-settings.php 4 months ago class-simple-codec.php 8 months ago class-users.php 4 months ago class-utils.php 4 years ago interface-codec.php 4 years ago interface-replicastore.php 2 months ago
class-dedicated-sender.php
435 lines
1 <?php
2 /**
3 * Dedicated Sender.
4 *
5 * The class is responsible for spawning dedicated Sync requests.
6 *
7 * @package automattic/jetpack-sync
8 */
9
10 namespace Automattic\Jetpack\Sync;
11
12 use WP_Error;
13 /**
14 * Class to manage Sync spawning.
15 * The purpose of this class is to provide the means to unblock Sync
16 * from running in the shutdown hook of regular requests by spawning a
17 * dedicated Sync request instead which will trigger Sync to run.
18 */
19 class Dedicated_Sender {
20
21 /**
22 * The transient name for storing the response code
23 * after spawning a dedicated sync test request.
24 */
25 const DEDICATED_SYNC_CHECK_TRANSIENT = 'jetpack_sync_dedicated_sync_spawn_check';
26
27 /**
28 * Validation string to check if the endpoint is working correctly.
29 *
30 * This is extracted and not hardcoded, as we might want to change it in the future.
31 */
32 const DEDICATED_SYNC_VALIDATION_STRING = 'DEDICATED SYNC OK';
33
34 /**
35 * Option name to use to keep the current request lock.
36 *
37 * The option format is `microtime(true)`.
38 */
39 const DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME = 'jetpack_sync_dedicated_spawn_lock';
40
41 /**
42 * What's the timeout for the request lock in seconds.
43 *
44 * 5 seconds as default value seems sane, but we might want to adjust that in the future.
45 */
46 const DEDICATED_SYNC_REQUEST_LOCK_TIMEOUT = 60;
47
48 /**
49 * The query parameter name to use when passing the current lock id.
50 */
51 const DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME = 'request_lock_id';
52
53 /**
54 * The name of the transient to use to temporarily disable enabling of Dedicated sync.
55 */
56 const DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG = 'jetpack_sync_dedicated_sync_temp_disable';
57
58 /**
59 * Filter a URL to check if Dedicated Sync is enabled.
60 * We need to remove slashes and then run it through `urldecode` as sometimes the
61 * URL is in an encoded form, depending on server configuration.
62 *
63 * @param string $url The URL to filter.
64 *
65 * @return string
66 */
67 public static function prepare_url_for_dedicated_request_check( $url ) {
68 return urldecode( $url );
69 }
70 /**
71 * Check if this request should trigger Sync to run.
72 *
73 * @access public
74 *
75 * @return boolean True if this is a 'jetpack/v4/sync/spawn-sync', false otherwise.
76 */
77 public static function is_dedicated_sync_request() {
78 /**
79 * Check $_SERVER['REQUEST_URI'] first, to see if we're in the right context.
80 * This is done to make sure we can hook in very early in the initialization of WordPress to
81 * be able to send sync requests to the backend as fast as possible, without needing to continue
82 * loading things for the request.
83 */
84 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
85 return false;
86 }
87
88 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- Only substring-matched against a fixed literal; never output or stored.
89 $check_url = self::prepare_url_for_dedicated_request_check( wp_unslash( $_SERVER['REQUEST_URI'] ) );
90 if ( strpos( $check_url, 'jetpack/v4/sync/spawn-sync' ) !== false ) {
91 return true;
92 }
93
94 /**
95 * If the above check failed, we might have an issue with detecting calls to the REST endpoint early on.
96 * Sometimes, like when permalinks are disabled, the REST path is sent via the `rest_route` GET parameter.
97 * We want to check it too, to make sure we managed to cover more cases and be more certain we actually
98 * catch calls to the endpoint.
99 */
100 if ( ! isset( $_GET['rest_route'] ) || ! is_string( $_GET['rest_route'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
101 return false;
102 }
103
104 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- Only substring-matched against a fixed literal; never output or stored.
105 $check_url = self::prepare_url_for_dedicated_request_check( wp_unslash( $_GET['rest_route'] ) );
106 if ( strpos( $check_url, 'jetpack/v4/sync/spawn-sync' ) !== false ) {
107 return true;
108 }
109
110 return false;
111 }
112
113 /**
114 * Send a request to run Sync for a certain sync queue
115 * through HTTP request that doesn't halt page loading.
116 *
117 * @access public
118 *
119 * @param \Automattic\Jetpack\Sync\Queue $queue Queue object.
120 *
121 * @return boolean|WP_Error True if spawned, WP_Error otherwise.
122 */
123 public static function spawn_sync( $queue ) {
124 if ( ! Settings::is_dedicated_sync_enabled() ) {
125 return new WP_Error( 'dedicated_sync_disabled', 'Dedicated Sync flow is disabled.' );
126 }
127
128 if ( $queue->is_locked() ) {
129 return new WP_Error( 'locked_queue_' . $queue->id );
130 }
131
132 if ( $queue->size() === 0 ) {
133 return new WP_Error( 'empty_queue_' . $queue->id );
134 }
135
136 if ( get_transient( Sender::TEMP_SYNC_DISABLE_TRANSIENT_NAME ) ) {
137 return new WP_Error( 'sender_temporarily_disabled_while_pulling' );
138 }
139
140 // Return early if we've gotten a retry-after header response that is not expired.
141 $retry_time = get_option( Actions::RETRY_AFTER_PREFIX . $queue->id );
142 if ( $retry_time && $retry_time >= microtime( true ) ) {
143 return new WP_Error( 'retry_after_' . $queue->id );
144 }
145
146 // Don't sync if we are throttled.
147 $sync_next_time = Sender::get_instance()->get_next_sync_time( $queue->id );
148 if ( $sync_next_time > microtime( true ) ) {
149 return new WP_Error( 'sync_throttled_' . $queue->id );
150 }
151 /**
152 * How much time to wait before we start suspecting Dedicated Sync is in trouble.
153 */
154 $queue_send_time_threshold = 30 * MINUTE_IN_SECONDS;
155
156 $queue_lag = $queue->lag();
157
158 /**
159 * Try to acquire a request lock, so we don't spawn multiple requests at the same time.
160 * This should prevent cases where sites might have limits on the amount of simultaneous requests.
161 */
162 $request_lock = self::try_lock_spawn_request();
163 if ( ! $request_lock ) {
164 return new WP_Error( 'dedicated_request_lock', 'Unable to acquire request lock' );
165 }
166
167 /**
168 * If the queue lag is bigger than the threshold, we want to check if Dedicated Sync is working correctly.
169 * We will do by sending a test request and disabling Dedicated Sync if it's not working. We will also exit early
170 * in case we send the test request since it is a blocking request.
171 */
172 if ( $queue_lag > $queue_send_time_threshold ) {
173 if ( false === get_transient( self::DEDICATED_SYNC_CHECK_TRANSIENT ) ) {
174 if ( ! self::can_spawn_dedicated_sync_request() ) {
175 self::on_dedicated_sync_lag_not_sending_threshold_reached();
176 return new WP_Error( 'dedicated_sync_not_sending', 'Dedicated Sync is not successfully sending events' );
177 }
178 return true;
179 }
180 }
181
182 $url = rest_url( 'jetpack/v4/sync/spawn-sync' );
183 $url = add_query_arg( 'time', time(), $url ); // Enforce Cache busting.
184 $url = add_query_arg( self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME, $request_lock, $url );
185
186 $args = array(
187 'cookies' => $_COOKIE,
188 'blocking' => false,
189 'timeout' => 0.01,
190 /** This filter is documented in wp-includes/class-wp-http-streams.php */
191 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
192 );
193
194 $result = wp_remote_get( $url, $args );
195 if ( is_wp_error( $result ) ) {
196 return $result;
197 }
198
199 return true;
200 }
201
202 /**
203 * Attempt to acquire a request lock.
204 *
205 * To avoid spawning multiple requests at the same time, we need to have a quick lock that will
206 * allow only a single request to continue if we try to spawn multiple at the same time.
207 *
208 * @return string|false
209 */
210 public static function try_lock_spawn_request() {
211 $option_name = self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME;
212 $expires_name = $option_name . '_expires';
213 $ttl = self::DEDICATED_SYNC_REQUEST_LOCK_TIMEOUT;
214 $lock_id = wp_generate_uuid4();
215 $now = microtime( true );
216
217 // Fast path: external object cache is atomic.
218 if ( wp_using_ext_object_cache() ) {
219 if ( wp_cache_add( $option_name, $lock_id, 'jetpack', $ttl ) ) {
220 return $lock_id;
221 }
222 return false; // Worker already active
223 }
224
225 global $wpdb;
226
227 // 1) Check & clear expired lock (best effort; failure here is harmless)
228 $expiry = (float) \Jetpack_Options::get_raw_option( $expires_name, 0 );
229 if ( ! $expiry || $expiry < $now ) {
230 // Either missing (edge case) or expired → clean up
231 \Jetpack_Options::delete_raw_option( $option_name );
232 \Jetpack_Options::delete_raw_option( $expires_name );
233 }
234
235 // 2) Atomic acquisition: INSERT IGNORE (succeeds only if the lock doesn't exist)
236 $inserted = $wpdb->query( // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching --- Ensure atomicity.
237 $wpdb->prepare(
238 "INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload )
239 VALUES ( %s, %s, 'no' )",
240 $option_name,
241 maybe_serialize( $lock_id )
242 )
243 );
244
245 if ( $inserted ) {
246 // 3) We own the lock — store expiry separately
247 \Jetpack_Options::update_raw_option( $expires_name, $now + $ttl, false );
248 return $lock_id; // Success
249 }
250
251 // Lock already present → normal state → do not spawn
252 return false;
253 }
254
255 /**
256 * Attempt to release the request lock.
257 *
258 * @param string $lock_id The request lock that's currently being held.
259 *
260 * @return bool|WP_Error
261 */
262 public static function try_release_lock_spawn_request( $lock_id = '' ) {
263 // Try to get the lock_id from the current request if it's not supplied.
264 if ( empty( $lock_id ) ) {
265 $lock_id = self::get_request_lock_id_from_request();
266 }
267
268 // If it's still not a valid lock_id, throw an error and let the lock process figure it out.
269 if ( empty( $lock_id ) ) {
270 return new WP_Error( 'dedicated_request_lock_invalid', 'Invalid lock_id supplied for unlock' );
271 }
272
273 if ( wp_using_ext_object_cache() ) {
274 $cached = wp_cache_get( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, 'jetpack', true );
275 if ( (string) $lock_id === $cached ) {
276 wp_cache_delete( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, 'jetpack' );
277
278 return true;
279 }
280
281 return false;
282 }
283
284 // If this is the flow that has the lock, let's release it so we can spawn other requests afterwards
285 $current_lock_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
286
287 if ( (string) $lock_id === $current_lock_value ) {
288 \Jetpack_Options::delete_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME );
289 return true;
290 }
291
292 return false;
293 }
294
295 /**
296 * Try to get the request lock id from the current request.
297 *
298 * @return array|string|string[]|null
299 */
300 public static function get_request_lock_id_from_request() {
301 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
302 if ( ! isset( $_GET[ self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME ] ) ) {
303 return null;
304 }
305
306 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
307 return wp_unslash( $_GET[ self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME ] );
308 }
309
310 /**
311 * Test Sync spawning functionality by making a request to the
312 * Sync spawning endpoint and storing the result (status code) in a transient.
313 *
314 * @since 1.34.0
315 *
316 * @return bool True if we got a successful response, false otherwise.
317 */
318 public static function can_spawn_dedicated_sync_request() {
319 $dedicated_sync_check_transient = self::DEDICATED_SYNC_CHECK_TRANSIENT;
320
321 $dedicated_sync_response_body = get_transient( $dedicated_sync_check_transient );
322
323 if ( false === $dedicated_sync_response_body ) {
324 $url = rest_url( 'jetpack/v4/sync/spawn-sync' );
325 $url = add_query_arg( 'time', time(), $url ); // Enforce Cache busting.
326 $args = array(
327 'cookies' => $_COOKIE,
328 'timeout' => 30,
329 /** This filter is documented in wp-includes/class-wp-http-streams.php */
330 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
331 );
332
333 $response = wp_remote_get( $url, $args );
334 $dedicated_sync_response_code = wp_remote_retrieve_response_code( $response );
335 $dedicated_sync_response_body = trim( wp_remote_retrieve_body( $response ) );
336
337 /**
338 * Limit the size of the body that we save in the transient to avoid cases where an error
339 * occurs and a whole generated HTML page is returned. We don't need to store the whole thing.
340 *
341 * The regexp check is done to make sure we can detect the string even if the body returns some additional
342 * output, like some caching plugins do when they try to pad the request.
343 */
344 $regexp = '!' . preg_quote( self::DEDICATED_SYNC_VALIDATION_STRING, '!' ) . '!uis';
345 if ( preg_match( $regexp, $dedicated_sync_response_body ) ) {
346 $saved_response_body = self::DEDICATED_SYNC_VALIDATION_STRING;
347 } else {
348 $saved_response_body = time();
349 }
350
351 set_transient( $dedicated_sync_check_transient, $saved_response_body, HOUR_IN_SECONDS );
352
353 // Send a bit more information to WordPress.com to help debugging issues.
354 if ( $saved_response_body !== self::DEDICATED_SYNC_VALIDATION_STRING ) {
355 $data = array(
356 'timestamp' => microtime( true ),
357 'response_code' => $dedicated_sync_response_code,
358 'response_body' => $dedicated_sync_response_body,
359
360 // Send the flow type that was attempted.
361 'sync_flow_type' => 'dedicated',
362 );
363
364 $sender = Sender::get_instance();
365
366 $sender->send_action( 'jetpack_sync_flow_error_enable', $data );
367 }
368 }
369 return self::DEDICATED_SYNC_VALIDATION_STRING === $dedicated_sync_response_body;
370 }
371
372 /**
373 * Disable dedicated sync and set a transient to prevent re-enabling it for some time.
374 *
375 * @return void
376 */
377 public static function on_dedicated_sync_lag_not_sending_threshold_reached() {
378 set_transient( self::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG, true, 6 * HOUR_IN_SECONDS );
379
380 Settings::update_settings(
381 array(
382 'dedicated_sync_enabled' => 0,
383 )
384 );
385
386 // Inform that we had to temporarily disable Dedicated Sync
387 $data = array(
388 'timestamp' => microtime( true ),
389
390 // Send the flow type that was attempted.
391 'sync_flow_type' => 'dedicated',
392 );
393
394 $sender = Sender::get_instance();
395
396 $sender->send_action( 'jetpack_sync_flow_error_temp_disable', $data );
397 }
398
399 /**
400 * Disable or enable Dedicated Sync sender based on the header value returned from WordPress.com
401 *
402 * @param string $dedicated_sync_header The Dedicated Sync header value - `on` or `off`.
403 *
404 * @return bool Whether Dedicated Sync is going to be enabled or not.
405 */
406 public static function maybe_change_dedicated_sync_status_from_wpcom_header( $dedicated_sync_header ) {
407 $dedicated_sync_enabled = 'on' === $dedicated_sync_header ? 1 : 0;
408
409 // Prevent enabling of Dedicated sync via header flag if we're in an autoheal timeout.
410 if ( $dedicated_sync_enabled ) {
411 $check_transient = get_transient( self::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG );
412
413 if ( $check_transient ) {
414 // Something happened and Dedicated Sync should not be automatically re-enabled.
415 return false;
416 }
417 }
418
419 $current_setting = Settings::is_dedicated_sync_enabled();
420
421 // No need to update if current setting matches header value.
422 if ( $current_setting === (bool) $dedicated_sync_enabled ) {
423 return $current_setting;
424 }
425
426 Settings::update_settings(
427 array(
428 'dedicated_sync_enabled' => $dedicated_sync_enabled,
429 )
430 );
431
432 return Settings::is_dedicated_sync_enabled();
433 }
434 }
435