Completed
Push — master-stable ( 53f101...a82972 )
by
unknown
86:26 queued 76:28
created

WPCOM_JSON_API_Site_Settings_Endpoint::callback()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 11
nop 2
dl 0
loc 35
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
4
5
	public static $site_format = array(
6
 		'ID'                => '(int) Site ID',
7
 		'name'              => '(string) Title of site',
8
 		'description'       => '(string) Tagline or description of site',
9
 		'URL'               => '(string) Full URL to the site',
10
		'lang'              => '(string) Primary language code of the site',
11
		'locale_variant'    => '(string) Locale variant code for the site, if set',
12
		'settings'          => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
13
	);
14
15
	// GET /sites/%s/settings
16
	// POST /sites/%s/settings
17
	function callback( $path = '', $blog_id = 0 ) {
18
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
19
		if ( is_wp_error( $blog_id ) ) {
20
			return $blog_id;
21
		}
22
23
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
24
			$this->load_theme_functions();
25
		}
26
27
		if ( ! is_user_logged_in() ) {
28
			return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
29
		} else if ( ! current_user_can( 'manage_options' ) ) {
30
			return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
31
		}
32
33
		if ( 'GET' === $this->api->method ) {
34
			/**
35
			 * Fires on each GET request to a specific endpoint.
36
			 *
37
			 * @module json-api
38
			 *
39
			 * @since 3.2.0
40
			 *
41
			 * @param string sites.
42
			 */
43
			do_action( 'wpcom_json_api_objects', 'sites' );
44
			return $this->get_settings_response();
45
		} else if ( 'POST' === $this->api->method ) {
46
			return $this->update_settings();
47
		} else {
48
			return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
49
		}
50
51
	}
52
53
	/**
54
	 * Determines whether jetpack_relatedposts is supported
55
	 *
56
	 * @return (bool)
57
	 */
58
	public function jetpack_relatedposts_supported() {
59
		$wpcom_related_posts_theme_blacklist = array(
60
			'Expound',
61
			'Traveler',
62
			'Opti',
63
			'Currents',
64
		);
65
		return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist ) );
66
	}
67
68
	/**
69
	 * Returns category details
70
	 *
71
	 * @return (array)
72
	 */
73
	public function get_category_details( $category ) {
74
		return array(
75
			'value' => $category->term_id,
76
			'name' => $category->name
77
		);
78
	}
79
80
	/**
81
	 * Returns an option value as the result of the callable being applied to
82
	 * it if a value is set, otherwise null.
83
	 *
84
	 * @param (string) $option_name Option name
85
	 * @param (callable) $cast_callable Callable to invoke on option value
86
	 * @return (int|null) Numeric option value or null
87
	 */
88
	protected function get_cast_option_value_or_null( $option_name, $cast_callable ) {
89
		$option_value = get_option( $option_name, null );
90
		if ( is_null( $option_value ) ) {
91
			return $option_value;
92
		}
93
94
		return call_user_func( $cast_callable, $option_value );
95
	}
96
97
	/**
98
	 * Collects the necessary information to return for a get settings response.
99
	 *
100
	 * @return (array)
101
	 */
102
	public function get_settings_response() {
103
104
		// Allow update in later versions
105
		/**
106
		 * Filter the structure of site settings to return.
107
		 *
108
		 * @module json-api
109
		 *
110
		 * @since 3.9.3
111
		 *
112
		 * @param array $site_format Data structure.
113
		 */
114
		$response_format = apply_filters( 'site_settings_site_format', self::$site_format );
115
116
		$blog_id = (int) $this->api->get_blog_id_for_output();
117
		/** This filter is documented in class.json-api-endpoints.php */
118
		$is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
119
120
		foreach ( array_keys( $response_format ) as $key ) {
121
122
			// refactoring to change lang parameter to locale in 1.2
123
			if ( $lang_or_locale = $this->get_locale( $key ) ) {
124
				$response[$key] = $lang_or_locale;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
125
				continue;
126
			}
127
128
			switch ( $key ) {
129
			case 'ID' :
130
				$response[$key] = $blog_id;
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
131
				break;
132
			case 'name' :
133
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
134
				break;
135
			case 'description' :
136
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
137
				break;
138
			case 'URL' :
139
				$response[$key] = (string) home_url();
140
				break;
141
			case 'locale_variant':
142
				if ( function_exists( 'wpcom_l10n_get_blog_locale_variant' ) ) {
143
					$blog_locale_variant = wpcom_l10n_get_blog_locale_variant();
144
					if ( $blog_locale_variant ) {
145
						$response[$key] = $blog_locale_variant;
146
					}
147
				}
148
				break;
149
			case 'settings':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
150
151
				$jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
152
153
				if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
154
					$jetpack_relatedposts_options[ 'enabled' ] = Jetpack::is_module_active( 'related-posts' );
155
				}
156
157
				// array_values() is necessary to ensure the array starts at index 0.
158
				$post_categories = array_values(
159
					array_map(
160
						array( $this, 'get_category_details' ),
161
						get_categories( array( 'hide_empty' => false ) )
162
					)
163
				);
164
165
				$holiday_snow = false;
166
				if ( function_exists( 'jetpack_holiday_snow_option_name' ) ) {
167
					$holiday_snow = (bool) get_option( jetpack_holiday_snow_option_name() );
168
				}
169
170
				$api_cache = $is_jetpack ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;
171
172
				$response[ $key ] = array(
173
174
					// also exists as "options"
175
					'admin_url'               => get_admin_url(),
176
					'default_ping_status'     => (bool) ( 'closed' != get_option( 'default_ping_status' ) ),
177
					'default_comment_status'  => (bool) ( 'closed' != get_option( 'default_comment_status' ) ),
178
179
					// new stuff starts here
180
					'blog_public'             => (int) get_option( 'blog_public' ),
181
					'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
182
					'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
183
					'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options[ 'enabled' ],
184
					'jetpack_relatedposts_show_headline' => (bool) isset( $jetpack_relatedposts_options[ 'show_headline' ] ) ? $jetpack_relatedposts_options[ 'show_headline' ] : false,
185
					'jetpack_relatedposts_show_thumbnails' => (bool) isset( $jetpack_relatedposts_options[ 'show_thumbnails' ] ) ? $jetpack_relatedposts_options[ 'show_thumbnails' ] : false,
186
					'default_category'        => (int) get_option('default_category'),
187
					'post_categories'         => (array) $post_categories,
188
					'default_post_format'     => get_option( 'default_post_format' ),
189
					'default_pingback_flag'   => (bool) get_option( 'default_pingback_flag' ),
190
					'require_name_email'      => (bool) get_option( 'require_name_email' ),
191
					'comment_registration'    => (bool) get_option( 'comment_registration' ),
192
					'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
193
					'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
194
					'thread_comments'         => (bool) get_option( 'thread_comments' ),
195
					'thread_comments_depth'   => (int) get_option( 'thread_comments_depth' ),
196
					'page_comments'           => (bool) get_option( 'page_comments' ),
197
					'comments_per_page'       => (int) get_option( 'comments_per_page' ),
198
					'default_comments_page'   => get_option( 'default_comments_page' ),
199
					'comment_order'           => get_option( 'comment_order' ),
200
					'comments_notify'         => (bool) get_option( 'comments_notify' ),
201
					'moderation_notify'       => (bool) get_option( 'moderation_notify' ),
202
					'social_notifications_like' => ( "on" == get_option( 'social_notifications_like' ) ),
203
					'social_notifications_reblog' => ( "on" == get_option( 'social_notifications_reblog' ) ),
204
					'social_notifications_subscribe' => ( "on" == get_option( 'social_notifications_subscribe' ) ),
205
					'comment_moderation'      => (bool) get_option( 'comment_moderation' ),
206
					'comment_whitelist'       => (bool) get_option( 'comment_whitelist' ),
207
					'comment_max_links'       => (int) get_option( 'comment_max_links' ),
208
					'moderation_keys'         => get_option( 'moderation_keys' ),
209
					'blacklist_keys'          => get_option( 'blacklist_keys' ),
210
					'lang_id'                 => defined( 'IS_WPCOM' ) && IS_WPCOM
211
						? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) )
212
						: get_option( 'lang_id' ),
213
					'wga'                     => defined( 'IS_WPCOM' ) && IS_WPCOM
214
						? get_option( 'wga' )
215
						: $this->get_google_analytics(),
216
					'disabled_likes'          => (bool) get_option( 'disabled_likes' ),
217
					'disabled_reblogs'        => (bool) get_option( 'disabled_reblogs' ),
218
					'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
219
					'twitter_via'             => (string) get_option( 'twitter_via' ),
220
					'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
221
					'eventbrite_api_token'    => $this->get_cast_option_value_or_null( 'eventbrite_api_token', 'intval' ),
222
					'holidaysnow'             => $holiday_snow,
223
					'gmt_offset'              => get_option( 'gmt_offset' ),
224
					'timezone_string'         => get_option( 'timezone_string' ),
225
					'date_format'             => get_option( 'date_format' ),
226
					'time_format'             => get_option( 'time_format' ),
227
					'start_of_week'           => get_option( 'start_of_week' ),
228
					'jetpack_testimonial'     => (bool) get_option( 'jetpack_testimonial', '0' ),
229
					'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
230
					'jetpack_portfolio'       => (bool) get_option( 'jetpack_portfolio', '0' ),
231
					'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
232
					'markdown_supported'      => true,
233
					'site_icon'               => $this->get_cast_option_value_or_null( 'site_icon', 'intval' ),
234
					Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => get_option( Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION, '' ),
235
					Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => get_option( Jetpack_SEO_Titles::TITLE_FORMATS_OPTION, array() ),
236
					'amp_is_supported'        => (bool) function_exists( 'wpcom_is_amp_supported' ) && wpcom_is_amp_supported( $blog_id ),
237
					'amp_is_enabled'          => (bool) function_exists( 'wpcom_is_amp_enabled' ) && wpcom_is_amp_enabled( $blog_id ),
238
					'api_cache'               => $api_cache,
239
					'posts_per_page'          => (int) get_option( 'posts_per_page' ),
240
				);
241
242
				if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
243
					$response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::is_posting_enabled();
244
					$response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::is_commenting_enabled();
245
				}
246
247
				//allow future versions of this endpoint to support additional settings keys
248
				/**
249
				 * Filter the current site setting in the returned response.
250
				 *
251
				 * @module json-api
252
				 *
253
				 * @since 3.9.3
254
				 *
255
				 * @param mixed $response_item A single site setting.
256
				 */
257
				$response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ] );
258
259
				if ( class_exists( 'Sharing_Service' ) ) {
260
					$ss = new Sharing_Service();
261
					$sharing = $ss->get_global_options();
262
					$response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
263
					$response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
264
					$response[ $key ]['sharing_show'] = (array) $sharing['show'];
265
					$response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
266
				}
267
268
				if ( function_exists( 'jetpack_protect_format_whitelist' ) ) {
269
					$response[ $key ]['jetpack_protect_whitelist'] = jetpack_protect_format_whitelist();
270
				}
271
272
				if ( ! current_user_can( 'edit_posts' ) )
273
					unset( $response[$key] );
274
				break;
275
			}
276
		}
277
278
		return $response;
279
280
	}
281
282 View Code Duplication
	protected function get_locale( $key ) {
283
		if ( 'lang' == $key ) {
284
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
285
				return (string) get_blog_lang_code();
286
			} else {
287
				return get_locale();
288
			}
289
		}
290
291
		return false;
292
	}
293
294
	protected function get_google_analytics () {
295
		$option_name = defined( 'IS_WPCOM' ) && IS_WPCOM ? 'wga' : 'jetpack_wga';
296
		return get_option( $option_name );
297
	}
298
299
	/**
300
	 * Updates site settings for authorized users
301
	 *
302
	 * @return (array)
303
	 */
304
	public function update_settings() {
305
		// $this->input() retrieves posted arguments whitelisted and casted to the $request_format
306
		// specs that get passed in when this class is instantiated
307
		/**
308
		 * Filters the settings to be updated on the site.
309
		 *
310
		 * @module json-api
311
		 *
312
		 * @since 3.6.0
313
		 *
314
		 * @param array $input Associative array of site settings to be updated.
315
		 */
316
		$input = apply_filters( 'rest_api_update_site_settings', $this->input() );
317
318
		$blog_id = get_current_blog_id();
319
320
		$jetpack_relatedposts_options = array();
321
		$sharing_options = array();
322
		$updated = array();
323
324
		foreach ( $input as $key => $value ) {
325
326
			if ( ! is_array( $value ) ) {
327
				$value = trim( $value );
328
			}
329
			$value = wp_unslash( $value );
330
331
			switch ( $key ) {
332
333
				case 'default_ping_status':
334
				case 'default_comment_status':
335
					// settings are stored as closed|open
336
					$coerce_value = ( $value ) ? 'open' : 'closed';
337
					if ( update_option( $key, $coerce_value ) ) {
338
						$updated[ $key ] = $value;
339
					};
340
					break;
341
				case 'jetpack_protect_whitelist':
342
					if ( function_exists( 'jetpack_protect_save_whitelist' ) ) {
343
						$result = jetpack_protect_save_whitelist( $value );
344
						if ( is_wp_error( $result ) ) {
345
							return $result;
346
						}
347
						$updated[ $key ] = jetpack_protect_format_whitelist();
348
					}
349
					break;
350
				case 'jetpack_sync_non_public_post_stati':
351
					Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
352
					break;
353
				case 'jetpack_relatedposts_enabled':
354
				case 'jetpack_relatedposts_show_thumbnails':
355
				case 'jetpack_relatedposts_show_headline':
356
					if ( ! $this->jetpack_relatedposts_supported() ) {
357
						break;
358
					}
359
					if ( 'jetpack_relatedposts_enabled' === $key && method_exists( 'Jetpack', 'is_module_active' ) && $this->jetpack_relatedposts_supported() ) {
360
						$before_action = Jetpack::is_module_active('related-posts');
361
						if ( $value ) {
362
							Jetpack::activate_module( 'related-posts', false, false );
363
						} else {
364
							Jetpack::deactivate_module( 'related-posts' );
365
						}
366
						$after_action = Jetpack::is_module_active('related-posts');
367
						if ( $after_action == $before_action ) {
368
							break;
369
						}
370
					}
371
					$just_the_key = substr( $key, 21 );
372
					$jetpack_relatedposts_options[ $just_the_key ] = $value;
373
				break;
374
375
				case 'social_notifications_like':
376
				case 'social_notifications_reblog':
377
				case 'social_notifications_subscribe':
378
					// settings are stored as on|off
379
					$coerce_value = ( $value ) ? 'on' : 'off';
380
					if ( update_option( $key, $coerce_value ) ) {
381
						$updated[ $key ] = $value;
382
					}
383
					break;
384
				case 'wga':
385
				case 'jetpack_wga':
386
					if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
387
						return new WP_Error( 'invalid_code', 'Invalid UA ID' );
388
					}
389
390
					$is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
391
					$option_name = $is_wpcom ? 'wga' : 'jetpack_wga';
392
393
					$wga = get_option( $option_name, array() );
394
					$wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
395
396
					if ( update_option( $option_name, $wga ) ) {
397
						$updated[ $key ] = $value;
398
					}
399
400
					$enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
401
402
					/** This action is documented in modules/widgets/social-media-icons.php */
403
					do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
404
405
					if ( $is_wpcom ) {
406
						$business_plugins = WPCOM_Business_Plugins::instance();
407
						$business_plugins->activate_plugin( 'wp-google-analytics' );
408
					}
409
					break;
410
411
				case 'jetpack_testimonial':
412
				case 'jetpack_portfolio':
413 View Code Duplication
				case 'jetpack_comment_likes_enabled':
414
					// settings are stored as 1|0
415
					$coerce_value = (int) $value;
416
					if ( update_option( $key, $coerce_value ) ) {
417
						$updated[ $key ] = (bool) $value;
418
					}
419
					break;
420
421
				case 'jetpack_testimonial_posts_per_page':
422 View Code Duplication
				case 'jetpack_portfolio_posts_per_page':
423
					// settings are stored as numeric
424
					$coerce_value = (int) $value;
425
					if ( update_option( $key, $coerce_value ) ) {
426
						$updated[ $key ] = $coerce_value;
427
					}
428
					break;
429
430
				// Sharing options
431
				case 'sharing_button_style':
432
				case 'sharing_show':
433
				case 'sharing_open_links':
434
					$sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
435
					break;
436
				case 'sharing_label':
437
					$sharing_options[ $key ] = $value;
438
					break;
439
440
				// Keyring token option
441
				case 'eventbrite_api_token':
442
					// These options can only be updated for sites hosted on WordPress.com
443
					if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
444
						if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
445
							if ( delete_option( $key ) ) {
446
								$updated[ $key ] = null;
447
							}
448
						} else if ( update_option( $key, $value ) ) {
449
							$updated[ $key ] = (int) $value;
450
						}
451
					}
452
					break;
453
454
				case 'holidaysnow':
455
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
456
						if ( function_exists( 'jetpack_holiday_snow_option_name' ) && delete_option( jetpack_holiday_snow_option_name() ) ) {
457
							$updated[ $key ] = false;
458
						}
459
					} else if ( function_exists( 'jetpack_holiday_snow_option_name' ) && update_option( jetpack_holiday_snow_option_name(), 'letitsnow' ) ) {
460
						$updated[ $key ] = true;
461
					}
462
					break;
463
464
				case 'api_cache':
465
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
466
						if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
467
							$updated[ $key ] = false;
468
						}
469
					} else if ( update_option( 'jetpack_api_cache_enabled', true ) ) {
470
						$updated[ $key ] = true;
471
					}
472
					break;
473
474
				case 'timezone_string':
475
					// Map UTC+- timezones to gmt_offsets and set timezone_string to empty
476
					// https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
477
					if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
478
						$gmt_offset = preg_replace( '/UTC\+?/', '', $value );
479
						if ( update_option( 'gmt_offset', $gmt_offset ) ) {
480
							$updated[ 'gmt_offset' ] = $gmt_offset;
481
						}
482
483
						$value = '';
484
					}
485
486
					// Always set timezone_string either with the given value or with an
487
					// empty string
488
					if ( update_option( $key, $value ) ) {
489
						$updated[ $key ] = $value;
490
					}
491
					break;
492
493
				case 'date_format':
494
				case 'time_format':
495
					// settings are stored as strings
496
					if ( update_option( $key, sanitize_text_field( $value ) ) ) {
497
						$updated[ $key ] = $value;
498
					}
499
					break;
500
501
				case 'start_of_week':
502
					// setting is stored as int in 0-6 range (days of week)
503
					$coerce_value = (int) $value;
504
					$limit_value  = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
505
					if ( update_option( $key, $limit_value ) ) {
506
						$updated[ $key ] = $limit_value;
507
					}
508
					break;
509
510
				case 'site_icon':
511
					// settings are stored as deletable numeric (all empty
512
					// values as delete intent), validated as media image
513
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
514
						/**
515
						 * Fallback mechanism to clear a third party site icon setting. Can be used
516
						 * to unset the option when an API request instructs the site to remove the site icon.
517
						 *
518
						 * @module json-api
519
						 *
520
						 * @since 4.10
521
						 */
522
						if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false )  ) {
523
							$updated[ $key ] = null;
524
						}
525
					} else if ( is_numeric( $value ) ) {
526
						$coerce_value = (int) $value;
527
						if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
528
							$updated[ $key ] = $coerce_value;
529
						}
530
					}
531
					break;
532
533 View Code Duplication
				case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
534
					if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_grandfathered_front_page_meta() ) {
535
						return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
536
					}
537
538
					if ( ! is_string( $value ) ) {
539
						return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
540
					}
541
542
					$new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
543
544
					if ( ! empty( $new_description ) ) {
545
						$updated[ $key ] = $new_description;
546
					}
547
					break;
548
549 View Code Duplication
				case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
550
					if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
551
						return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
552
					}
553
554
					if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
555
						return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
556
					}
557
558
					$new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
559
560
					if ( ! empty( $new_title_formats ) ) {
561
						$updated[ $key ] = $new_title_formats;
562
					}
563
					break;
564
565
				case 'verification_services_codes':
566
					$verification_codes = jetpack_verification_validate( $value );
567
568
					if ( update_option( 'verification_services_codes', $verification_codes ) ) {
569
						$updated[ $key ] = $verification_codes;
570
					}
571
					break;
572
573
				case 'wpcom_publish_posts_with_markdown':
574
				case 'wpcom_publish_comments_with_markdown':
575
					$coerce_value = (bool) $value;
576
					if ( update_option( $key, $coerce_value ) ) {
577
						$updated[ $key ] = $coerce_value;
578
					}
579
					break;
580
581
				case 'amp_is_enabled':
582
					if ( function_exists( 'wpcom_update_amp_enabled' ) ) {
583
						$saved = wpcom_update_amp_enabled( $blog_id, $value );
584
						if ( $saved ) {
585
							$updated[ $key ] = (bool) $value;
586
						}
587
					}
588
					break;
589
590
				default:
591
					//allow future versions of this endpoint to support additional settings keys
592
					if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
593
						/**
594
						 * Filter current site setting value to be updated.
595
						 *
596
						 * @module json-api
597
						 *
598
						 * @since 3.9.3
599
						 *
600
						 * @param mixed $response_item A single site setting value.
601
						 */
602
						$value = apply_filters( 'site_settings_endpoint_update_' . $key, $value );
603
						$updated[ $key ] = $value;
604
						continue;
605
					}
606
607
					// no worries, we've already whitelisted and casted arguments above
608
					if ( update_option( $key, $value ) ) {
609
						$updated[ $key ] = $value;
610
					}
611
			}
612
		}
613
614
		if ( count( $jetpack_relatedposts_options ) ) {
615
			// track new jetpack_relatedposts options against old
616
			$old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
617
			if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options ) ) {
618
				foreach ( $jetpack_relatedposts_options as $key => $value ) {
619
					if ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
620
						$updated[ 'jetpack_relatedposts_' . $key ] = $value;
621
					}
622
				}
623
			}
624
		}
625
626
		if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
627
			$ss = new Sharing_Service();
628
629
			// Merge current values with updated, since Sharing_Service expects
630
			// all values to be included when updating
631
			$current_sharing_options = $ss->get_global_options();
632
			foreach ( $current_sharing_options as $key => $val ) {
633
				if ( ! isset( $sharing_options[ $key ] ) ) {
634
					$sharing_options[ $key ] = $val;
635
				}
636
			}
637
638
			$updated_social_options = $ss->set_global_options( $sharing_options );
639
640
			if ( isset( $input['sharing_button_style'] ) ) {
641
				$updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
642
			}
643
			if ( isset( $input['sharing_label'] ) ) {
644
				// Sharing_Service won't report label as updated if set to default
645
				$updated['sharing_label'] = (string) $sharing_options['sharing_label'];
646
			}
647
			if ( isset( $input['sharing_show'] ) ) {
648
				$updated['sharing_show'] = (array) $updated_social_options['show'];
649
			}
650
			if ( isset( $input['sharing_open_links'] ) ) {
651
				$updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
652
			}
653
		}
654
655
		return array(
656
			'updated' => $updated
657
		);
658
659
	}
660
}
661