Completed
Push — fix/block-vr ( dd4d13...b6c320 )
by Bernhard
196:37 queued 183:38
created

WPCOM_JSON_API_Site_Settings_Endpoint::callback()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 11
nop 2
dl 0
loc 37
rs 8.0835
c 0
b 0
f 0
1
<?php
2
3
new WPCOM_JSON_API_Site_Settings_Endpoint( array(
4
	'description' => 'Get detailed settings information about a site.',
5
	'group'       => '__do_not_document',
6
	'stat'        => 'sites:X',
7
	'max_version'   => '1.1',
8
	'new_version' => '1.2',
9
	'method'      => 'GET',
10
	'path'        => '/sites/%s/settings',
11
	'path_labels' => array(
12
		'$site' => '(int|string) Site ID or domain',
13
	),
14
15
	'query_parameters' => array(
16
		'context' => false,
17
	),
18
19
	'response_format' => WPCOM_JSON_API_Site_Settings_Endpoint::$site_format,
20
21
	'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
22
) );
23
24
new WPCOM_JSON_API_Site_Settings_Endpoint( array(
25
	'description' => 'Update settings for a site.',
26
	'group'       => '__do_not_document',
27
	'stat'        => 'sites:X',
28
	'max_version' => '1.1',
29
	'new_version' => '1.2',
30
	'method'      => 'POST',
31
	'path'        => '/sites/%s/settings',
32
	'path_labels' => array(
33
		'$site' => '(int|string) Site ID or domain',
34
	),
35
36
	'request_format'  => array(
37
		'blogname'                     => '(string) Blog name',
38
		'blogdescription'              => '(string) Blog description',
39
		'default_pingback_flag'        => '(bool) Notify blogs linked from article?',
40
		'default_ping_status'          => '(bool) Allow link notifications from other blogs?',
41
		'default_comment_status'       => '(bool) Allow comments on new articles?',
42
		'blog_public'                  => '(string) Site visibility; -1: private, 0: discourage search engines, 1: allow search engines',
43
		'jetpack_sync_non_public_post_stati' => '(bool) allow sync of post and pages with non-public posts stati',
44
		'jetpack_relatedposts_enabled' => '(bool) Enable related posts?',
45
		'jetpack_relatedposts_show_headline' => '(bool) Show headline in related posts?',
46
		'jetpack_relatedposts_show_thumbnails' => '(bool) Show thumbnails in related posts?',
47
		'jetpack_protect_whitelist'    => '(array) List of IP addresses to whitelist',
48
		'jetpack_search_enabled'       => '(bool) Enable Jetpack Search',
49
		'jetpack_search_supported'     => '(bool) Jetpack Search is supported',
50
		'infinite_scroll'              => '(bool) Support infinite scroll of posts?',
51
		'default_category'             => '(int) Default post category',
52
		'default_post_format'          => '(string) Default post format',
53
		'require_name_email'           => '(bool) Require comment authors to fill out name and email?',
54
		'comment_registration'         => '(bool) Require users to be registered and logged in to comment?',
55
		'close_comments_for_old_posts' => '(bool) Automatically close comments on old posts?',
56
		'close_comments_days_old'      => '(int) Age at which to close comments',
57
		'thread_comments'              => '(bool) Enable threaded comments?',
58
		'thread_comments_depth'        => '(int) Depth to thread comments',
59
		'page_comments'                => '(bool) Break comments into pages?',
60
		'comments_per_page'            => '(int) Number of comments to display per page',
61
		'default_comments_page'        => '(string) newest|oldest Which page of comments to display first',
62
		'comment_order'                => '(string) asc|desc Order to display comments within page',
63
		'comments_notify'              => '(bool) Email me when someone comments?',
64
		'moderation_notify'            => '(bool) Email me when a comment is helf for moderation?',
65
		'social_notifications_like'    => '(bool) Email me when someone likes my post?',
66
		'social_notifications_reblog'  => '(bool) Email me when someone reblogs my post?',
67
		'social_notifications_subscribe' => '(bool) Email me when someone follows my blog?',
68
		'comment_moderation'           => '(bool) Moderate comments for manual approval?',
69
		'comment_whitelist'            => '(bool) Moderate comments unless author has a previously-approved comment?',
70
		'comment_max_links'            => '(int) Moderate comments that contain X or more links',
71
		'moderation_keys'              => '(string) Words or phrases that trigger comment moderation, one per line',
72
		'blacklist_keys'               => '(string) Words or phrases that mark comment spam, one per line',
73
		'lang_id'                      => '(int) ID for language blog is written in',
74
		'wga'                          => '(array) Google Analytics Settings',
75
		'disabled_likes'               => '(bool) Are likes globally disabled (they can still be turned on per post)?',
76
		'disabled_reblogs'             => '(bool) Are reblogs disabled on posts?',
77
		'jetpack_comment_likes_enabled' => '(bool) Are comment likes enabled for all comments?',
78
		'sharing_button_style'         => '(string) Style to use for sharing buttons (icon-text, icon, text, or official)',
79
		'sharing_label'                => '(string) Label to use for sharing buttons, e.g. "Share this:"',
80
		'sharing_show'                 => '(string|array:string) Post type or array of types where sharing buttons are to be displayed',
81
		'sharing_open_links'           => '(string) Link target for sharing buttons (same or new)',
82
		'twitter_via'                  => '(string) Twitter username to include in tweets when people share using the Twitter button',
83
		'jetpack-twitter-cards-site-tag' => '(string) The Twitter username of the owner of the site\'s domain.',
84
		'eventbrite_api_token'         => '(int) The Keyring token ID for an Eventbrite token to associate with the site',
85
		'timezone_string'              => '(string) PHP-compatible timezone string like \'UTC-5\'',
86
		'gmt_offset'                   => '(int) Site offset from UTC in hours',
87
		'date_format'                  => '(string) PHP Date-compatible date format',
88
		'time_format'                  => '(string) PHP Date-compatible time format',
89
		'start_of_week'                => '(int) Starting day of week (0 = Sunday, 6 = Saturday)',
90
		'jetpack_testimonial'          => '(bool) Whether testimonial custom post type is enabled for the site',
91
		'jetpack_testimonial_posts_per_page' => '(int) Number of testimonials to show per page',
92
		'jetpack_portfolio'            => '(bool) Whether portfolio custom post type is enabled for the site',
93
		'jetpack_portfolio_posts_per_page' => '(int) Number of portfolio projects to show per page',
94
		Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => '(string) The seo meta description for the site.',
95
		Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => '(array) SEO meta title formats. Allowed keys: front_page, posts, pages, groups, archives',
96
		'verification_services_codes'  => '(array) Website verification codes. Allowed keys: google, pinterest, bing, yandex',
97
		'markdown_supported'            => '(bool) Whether markdown is supported for this site',
98
		'wpcom_publish_posts_with_markdown' => '(bool) Whether markdown is enabled for posts',
99
		'wpcom_publish_comments_with_markdown' => '(bool) Whether markdown is enabled for comments',
100
		'amp_is_enabled'   => '(bool) Whether AMP is enabled for this site',
101
		'site_icon'                    => '(int) Media attachment ID to use as site icon. Set to zero or an otherwise empty value to clear',
102
		'api_cache'                    => '(bool) Turn on/off the Jetpack JSON API cache',
103
		'posts_per_page'               => '(int) Number of posts to show on blog pages',
104
		'net_neutrality'               => '(bool) Whether to show the net neutrality modal for a site',
105
		'posts_per_rss'                => '(int) Number of posts to show in the RSS feed',
106
		'rss_use_excerpt'              => '(bool) Whether the RSS feed will use post excerpts',
107
	),
108
109
	'response_format' => array(
110
		'updated' => '(array)'
111
	),
112
113
	'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
114
) );
115
116
class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
117
118
	public static $site_format = array(
119
		'ID'                => '(int) Site ID',
120
		'name'              => '(string) Title of site',
121
		'description'       => '(string) Tagline or description of site',
122
		'URL'               => '(string) Full URL to the site',
123
		'lang'              => '(string) Primary language code of the site',
124
		'locale_variant'    => '(string) Locale variant code for the site, if set',
125
		'settings'          => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
126
	);
127
128
	// GET /sites/%s/settings
129
	// POST /sites/%s/settings
130
	function callback( $path = '', $blog_id = 0 ) {
131
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
132
		if ( is_wp_error( $blog_id ) ) {
133
			return $blog_id;
134
		}
135
136
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
137
			// Source & include the infinite scroll compatibility files prior to loading theme functions
138
			add_filter( 'restapi_theme_action_copy_dirs', array( 'WPCOM_JSON_API_Site_Settings_Endpoint', 'wpcom_restapi_copy_theme_plugin_actions' ) );
139
			$this->load_theme_functions();
140
		}
141
142
		if ( ! is_user_logged_in() ) {
143
			return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
144
		} else if ( ! current_user_can( 'manage_options' ) ) {
145
			return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
146
		}
147
148
		if ( 'GET' === $this->api->method ) {
149
			/**
150
			 * Fires on each GET request to a specific endpoint.
151
			 *
152
			 * @module json-api
153
			 *
154
			 * @since 3.2.0
155
			 *
156
			 * @param string sites.
157
			 */
158
			do_action( 'wpcom_json_api_objects', 'sites' );
159
			return $this->get_settings_response();
160
		} else if ( 'POST' === $this->api->method ) {
161
			return $this->update_settings();
162
		} else {
163
			return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
164
		}
165
166
	}
167
168
	/**
169
	 * Includes additional theme-specific files to be included in REST API theme
170
	 * context loading action copying.
171
	 *
172
	 * @see WPCOM_JSON_API_Endpoint#load_theme_functions
173
	 * @see the_neverending_home_page_theme_support
174
	 */
175
	function wpcom_restapi_copy_theme_plugin_actions( $copy_dirs ) {
176
		$theme_name = get_stylesheet();
177
		$default_file_name = WP_CONTENT_DIR . "/mu-plugins/infinity/themes/{$theme_name}.php";
178
179
		/**
180
		 * Filter the path to the Infinite Scroll compatibility file.
181
		 *
182
		 * @module infinite-scroll
183
		 *
184
		 * @since 2.0.0
185
		 *
186
		 * @param string $str IS compatibility file path.
187
		 * @param string $theme_name Theme name.
188
		 */
189
		$customization_file = apply_filters( 'infinite_scroll_customization_file', $default_file_name, $theme_name );
190
191
		if ( is_readable( $customization_file ) ) {
192
			require_once $customization_file;
193
			$copy_dirs[] = $customization_file;
194
		}
195
196
		return $copy_dirs;
197
	}
198
199
	/**
200
	 * Determines whether jetpack_relatedposts is supported
201
	 *
202
	 * @return bool
203
	 */
204
	public function jetpack_relatedposts_supported() {
205
		$wpcom_related_posts_theme_blacklist = array(
206
			'Expound',
207
			'Traveler',
208
			'Opti',
209
			'Currents',
210
		);
211
		return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist ) );
212
	}
213
214
	/**
215
	 * Returns category details
216
	 *
217
	 * @return array
218
	 */
219
	public function get_category_details( $category ) {
220
		return array(
221
			'value' => $category->term_id,
222
			'name' => $category->name
223
		);
224
	}
225
226
	/**
227
	 * Returns an option value as the result of the callable being applied to
228
	 * it if a value is set, otherwise null.
229
	 *
230
	 * @param string $option_name Option name
231
	 * @param callable $cast_callable Callable to invoke on option value
232
	 * @return int|null Numeric option value or null
233
	 */
234
	protected function get_cast_option_value_or_null( $option_name, $cast_callable ) {
235
		$option_value = get_option( $option_name, null );
236
		if ( is_null( $option_value ) ) {
237
			return $option_value;
238
		}
239
240
		return call_user_func( $cast_callable, $option_value );
241
	}
242
243
	/**
244
	 * Collects the necessary information to return for a get settings response.
245
	 *
246
	 * @return array
247
	 */
248
	public function get_settings_response() {
249
250
		// Allow update in later versions
251
		/**
252
		 * Filter the structure of site settings to return.
253
		 *
254
		 * @module json-api
255
		 *
256
		 * @since 3.9.3
257
		 *
258
		 * @param array $site_format Data structure.
259
		 */
260
		$response_format = apply_filters( 'site_settings_site_format', self::$site_format );
261
262
		$blog_id = (int) $this->api->get_blog_id_for_output();
263
		/** This filter is documented in class.json-api-endpoints.php */
264
		$is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
265
266
		foreach ( array_keys( $response_format ) as $key ) {
267
268
			// refactoring to change lang parameter to locale in 1.2
269
			if ( $lang_or_locale = $this->get_locale( $key ) ) {
270
				$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...
271
				continue;
272
			}
273
274
			switch ( $key ) {
275
			case 'ID' :
276
				$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...
277
				break;
278
			case 'name' :
279
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
280
				break;
281
			case 'description' :
282
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
283
				break;
284
			case 'URL' :
285
				$response[$key] = (string) home_url();
286
				break;
287
			case 'locale_variant':
288
				if ( function_exists( 'wpcom_l10n_get_blog_locale_variant' ) ) {
289
					$blog_locale_variant = wpcom_l10n_get_blog_locale_variant();
290
					if ( $blog_locale_variant ) {
291
						$response[$key] = $blog_locale_variant;
292
					}
293
				}
294
				break;
295
			case 'settings':
296
297
				$jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts', array() );
298
				// If the option's enabled key is NOT SET, it is considered enabled by the plugin
299
				if ( ! isset( $jetpack_relatedposts_options['enabled'] ) ) {
300
					$jetpack_relatedposts_options['enabled'] = true;
301
				}
302
303
				if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
304
					$jetpack_relatedposts_options[ 'enabled' ] = Jetpack::is_module_active( 'related-posts' );
305
				}
306
307
				$jetpack_search_supported = false;
308
				if ( function_exists( 'wpcom_is_jetpack_search_supported' ) ) {
309
					$jetpack_search_supported = wpcom_is_jetpack_search_supported( $blog_id );
310
				}
311
312
				$jetpack_search_active = false;
313
				if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
314
					$jetpack_search_active = Jetpack::is_module_active( 'search' );
315
				}
316
				if ( function_exists( 'is_jetpack_module_active' ) ) {
317
					$jetpack_search_active = is_jetpack_module_active( 'search', $blog_id );
318
				}
319
320
				// array_values() is necessary to ensure the array starts at index 0.
321
				$post_categories = array_values(
322
					array_map(
323
						array( $this, 'get_category_details' ),
324
						get_categories( array( 'hide_empty' => false ) )
325
					)
326
				);
327
328
				$api_cache = $is_jetpack ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;
329
330
				$net_neutrality_options = get_option( 'net_neutrality_options_2017' );
331
				$net_neutrality = ( $net_neutrality_options && ! empty( $net_neutrality_options['enabled'] ) )
332
					? true
333
					: false;
334
335
				$response[ $key ] = array(
336
337
					// also exists as "options"
338
					'admin_url'               => get_admin_url(),
339
					'default_ping_status'     => (bool) ( 'closed' != get_option( 'default_ping_status' ) ),
340
					'default_comment_status'  => (bool) ( 'closed' != get_option( 'default_comment_status' ) ),
341
342
					// new stuff starts here
343
					'blog_public'             => (int) get_option( 'blog_public' ),
344
					'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
345
					'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
346
					'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options[ 'enabled' ],
347
					'jetpack_relatedposts_show_headline' => (bool) isset( $jetpack_relatedposts_options[ 'show_headline' ] ) ? $jetpack_relatedposts_options[ 'show_headline' ] : false,
348
					'jetpack_relatedposts_show_thumbnails' => (bool) isset( $jetpack_relatedposts_options[ 'show_thumbnails' ] ) ? $jetpack_relatedposts_options[ 'show_thumbnails' ] : false,
349
					'jetpack_search_enabled'  => (bool) $jetpack_search_active,
350
					'jetpack_search_supported'=> (bool) $jetpack_search_supported,
351
					'default_category'        => (int) get_option('default_category'),
352
					'post_categories'         => (array) $post_categories,
353
					'default_post_format'     => get_option( 'default_post_format' ),
354
					'default_pingback_flag'   => (bool) get_option( 'default_pingback_flag' ),
355
					'require_name_email'      => (bool) get_option( 'require_name_email' ),
356
					'comment_registration'    => (bool) get_option( 'comment_registration' ),
357
					'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
358
					'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
359
					'thread_comments'         => (bool) get_option( 'thread_comments' ),
360
					'thread_comments_depth'   => (int) get_option( 'thread_comments_depth' ),
361
					'page_comments'           => (bool) get_option( 'page_comments' ),
362
					'comments_per_page'       => (int) get_option( 'comments_per_page' ),
363
					'default_comments_page'   => get_option( 'default_comments_page' ),
364
					'comment_order'           => get_option( 'comment_order' ),
365
					'comments_notify'         => (bool) get_option( 'comments_notify' ),
366
					'moderation_notify'       => (bool) get_option( 'moderation_notify' ),
367
					'social_notifications_like' => ( "on" == get_option( 'social_notifications_like' ) ),
368
					'social_notifications_reblog' => ( "on" == get_option( 'social_notifications_reblog' ) ),
369
					'social_notifications_subscribe' => ( "on" == get_option( 'social_notifications_subscribe' ) ),
370
					'comment_moderation'      => (bool) get_option( 'comment_moderation' ),
371
					'comment_whitelist'       => (bool) get_option( 'comment_whitelist' ),
372
					'comment_max_links'       => (int) get_option( 'comment_max_links' ),
373
					'moderation_keys'         => get_option( 'moderation_keys' ),
374
					'blacklist_keys'          => get_option( 'blacklist_keys' ),
375
					'lang_id'                 => defined( 'IS_WPCOM' ) && IS_WPCOM
376
						? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) )
377
						: get_option( 'lang_id' ),
378
					'wga'                     => $this->get_google_analytics(),
379
					'disabled_likes'          => (bool) get_option( 'disabled_likes' ),
380
					'disabled_reblogs'        => (bool) get_option( 'disabled_reblogs' ),
381
					'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
382
					'twitter_via'             => (string) get_option( 'twitter_via' ),
383
					'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
384
					'eventbrite_api_token'    => $this->get_cast_option_value_or_null( 'eventbrite_api_token', 'intval' ),
385
					'gmt_offset'              => get_option( 'gmt_offset' ),
386
					'timezone_string'         => get_option( 'timezone_string' ),
387
					'date_format'             => get_option( 'date_format' ),
388
					'time_format'             => get_option( 'time_format' ),
389
					'start_of_week'           => get_option( 'start_of_week' ),
390
					'jetpack_testimonial'     => (bool) get_option( 'jetpack_testimonial', '0' ),
391
					'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
392
					'jetpack_portfolio'       => (bool) get_option( 'jetpack_portfolio', '0' ),
393
					'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
394
					'markdown_supported'      => true,
395
					'site_icon'               => $this->get_cast_option_value_or_null( 'site_icon', 'intval' ),
396
					Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => get_option( Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION, '' ),
397
					Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => get_option( Jetpack_SEO_Titles::TITLE_FORMATS_OPTION, array() ),
398
					'amp_is_supported'        => (bool) function_exists( 'wpcom_is_amp_supported' ) && wpcom_is_amp_supported( $blog_id ),
399
					'amp_is_enabled'          => (bool) function_exists( 'wpcom_is_amp_enabled' ) && wpcom_is_amp_enabled( $blog_id ),
400
					'api_cache'               => $api_cache,
401
					'posts_per_page'          => (int) get_option( 'posts_per_page' ),
402
					'net_neutrality'          => $net_neutrality,
403
					'posts_per_rss'           => (int) get_option( 'posts_per_rss' ),
404
					'rss_use_excerpt'         => (bool) get_option( 'rss_use_excerpt' ),
405
				);
406
407
				if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
408
					$response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::is_posting_enabled();
409
					$response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::is_commenting_enabled();
410
411
					// WPCOM-specific Infinite Scroll Settings
412
					if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
413
						/**
414
						 * Clear the cached copy of widget info so it's pulled fresh from blog options.
415
						 * It was primed during the initial load under the __REST API site__'s context.
416
						 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
417
						 */
418
						$GLOBALS['_wp_sidebars_widgets'] = array();
419
420
						$infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
421
						$response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && $infinite_scroll_settings->type === 'scroll';
422
						if ( $infinite_scroll_settings->footer_widgets || 'click' == $infinite_scroll_settings->requested_type ) {
423
							// The blog has footer widgets -- infinite scroll is blocked
424
							$response[ $key ]['infinite_scroll_blocked'] = 'footer';
425
						} else {
426
							$response[ $key ]['infinite_scroll_blocked'] = false;
427
						}
428
					}
429
				}
430
431
				//allow future versions of this endpoint to support additional settings keys
432
				/**
433
				 * Filter the current site setting in the returned response.
434
				 *
435
				 * @module json-api
436
				 *
437
				 * @since 3.9.3
438
				 *
439
				 * @param mixed $response_item A single site setting.
440
				 */
441
				$response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ] );
442
443
				if ( class_exists( 'Sharing_Service' ) ) {
444
					$ss = new Sharing_Service();
445
					$sharing = $ss->get_global_options();
446
					$response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
447
					$response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
448
					$response[ $key ]['sharing_show'] = (array) $sharing['show'];
449
					$response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
450
				}
451
452
				if ( function_exists( 'jetpack_protect_format_whitelist' ) ) {
453
					$response[ $key ]['jetpack_protect_whitelist'] = jetpack_protect_format_whitelist();
454
				}
455
456
				if ( ! current_user_can( 'edit_posts' ) )
457
					unset( $response[$key] );
458
				break;
459
			}
460
		}
461
462
		return $response;
463
464
	}
465
466 View Code Duplication
	protected function get_locale( $key ) {
467
		if ( 'lang' == $key ) {
468
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
469
				return (string) get_blog_lang_code();
470
			} else {
471
				return get_locale();
472
			}
473
		}
474
475
		return false;
476
	}
477
478
	protected function get_google_analytics () {
479
		$option_name = defined( 'IS_WPCOM' ) && IS_WPCOM ? 'wga' : 'jetpack_wga';
480
		return get_option( $option_name );
481
	}
482
483
	/**
484
	 * Updates site settings for authorized users
485
	 *
486
	 * @return array
487
	 */
488
	public function update_settings() {
489
		// $this->input() retrieves posted arguments whitelisted and casted to the $request_format
490
		// specs that get passed in when this class is instantiated
491
		$input = $this->input();
492
		$unfiltered_input = $this->input( false, false );
493
		/**
494
		 * Filters the settings to be updated on the site.
495
		 *
496
		 * @module json-api
497
		 *
498
		 * @since 3.6.0
499
		 * @since 6.1.1 Added $unfiltered_input parameter.
500
		 *
501
		 * @param array $input              Associative array of site settings to be updated.
502
		 *                                  Cast and filtered based on documentation.
503
		 * @param array $unfiltered_input   Associative array of site settings to be updated.
504
		 *                                  Neither cast nor filtered. Contains raw input.
505
		 */
506
		$input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
507
508
		$blog_id = get_current_blog_id();
509
510
		$jetpack_relatedposts_options = array();
511
		$sharing_options = array();
512
		$updated = array();
513
514
		foreach ( $input as $key => $value ) {
515
516
			if ( ! is_array( $value ) ) {
517
				$value = trim( $value );
518
			}
519
			$value = wp_unslash( $value );
520
521
			switch ( $key ) {
522
523
				case 'default_ping_status':
524
				case 'default_comment_status':
525
					// settings are stored as closed|open
526
					$coerce_value = ( $value ) ? 'open' : 'closed';
527
					if ( update_option( $key, $coerce_value ) ) {
528
						$updated[ $key ] = $value;
529
					};
530
					break;
531
				case 'jetpack_protect_whitelist':
532
					if ( function_exists( 'jetpack_protect_save_whitelist' ) ) {
533
						$result = jetpack_protect_save_whitelist( $value );
534
						if ( is_wp_error( $result ) ) {
535
							return $result;
536
						}
537
						$updated[ $key ] = jetpack_protect_format_whitelist();
538
					}
539
					break;
540
				case 'jetpack_sync_non_public_post_stati':
541
					Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
542
					break;
543
				case 'jetpack_search_enabled':
544
					if ( ! method_exists( 'Jetpack', 'activate_module' ) ) {
545
						break;
546
					}
547
					$is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
548
					if ( $value ) {
549
						$jetpack_search_update_success = $is_wpcom
0 ignored issues
show
Unused Code introduced by
$jetpack_search_update_success is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
550
							? Jetpack::activate_module( $blog_id, 'search' )
0 ignored issues
show
Documentation introduced by
'search' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
551
							: Jetpack::activate_module( 'search', false, false );
552
					} else {
553
						$jetpack_search_update_success = $is_wpcom
0 ignored issues
show
Unused Code introduced by
$jetpack_search_update_success is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
554
							? Jetpack::deactivate_module( $blog_id, 'search' )
0 ignored issues
show
Unused Code introduced by
The call to Jetpack::deactivate_module() has too many arguments starting with 'search'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
555
							: Jetpack::deactivate_module( 'search' );
556
					}
557
					$updated[ $key ] = (bool) $value;
558
					break;
559
				case 'jetpack_relatedposts_enabled':
560
				case 'jetpack_relatedposts_show_thumbnails':
561
				case 'jetpack_relatedposts_show_headline':
562
					if ( ! $this->jetpack_relatedposts_supported() ) {
563
						break;
564
					}
565
					if ( 'jetpack_relatedposts_enabled' === $key && method_exists( 'Jetpack', 'is_module_active' ) && $this->jetpack_relatedposts_supported() ) {
566
						$before_action = Jetpack::is_module_active('related-posts');
567
						if ( $value ) {
568
							Jetpack::activate_module( 'related-posts', false, false );
569
						} else {
570
							Jetpack::deactivate_module( 'related-posts' );
571
						}
572
						$after_action = Jetpack::is_module_active('related-posts');
573
						if ( $after_action == $before_action ) {
574
							break;
575
						}
576
					}
577
					$just_the_key = substr( $key, 21 );
578
					$jetpack_relatedposts_options[ $just_the_key ] = $value;
579
				break;
580
581
				case 'social_notifications_like':
582
				case 'social_notifications_reblog':
583
				case 'social_notifications_subscribe':
584
					// settings are stored as on|off
585
					$coerce_value = ( $value ) ? 'on' : 'off';
586
					if ( update_option( $key, $coerce_value ) ) {
587
						$updated[ $key ] = $value;
588
					}
589
					break;
590
				case 'wga':
591
				case 'jetpack_wga':
592
					if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
593
						return new WP_Error( 'invalid_code', 'Invalid UA ID' );
594
					}
595
596
					$is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
597
					$option_name = $is_wpcom ? 'wga' : 'jetpack_wga';
598
599
					$wga = get_option( $option_name, array() );
600
					$wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
601
602
					/**
603
					 * Allow newer versions of this endpoint to filter in additional fields for Google Analytics
604
					 *
605
					 * @since 5.4.0
606
					 *
607
					 * @param array $wga Associative array of existing Google Analytics settings.
608
					 * @param array $value Associative array of new Google Analytics settings passed to the endpoint.
609
					 */
610
					$wga = apply_filters( 'site_settings_update_wga', $wga, $value );
611
612
					if ( update_option( $option_name, $wga ) ) {
613
						$updated[ $key ] = $value;
614
					}
615
616
					$enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
617
618
					/** This action is documented in modules/widgets/social-media-icons.php */
619
					do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
620
621
					if ( $is_wpcom ) {
622
						$business_plugins = WPCOM_Business_Plugins::instance();
623
						$business_plugins->activate_plugin( 'wp-google-analytics' );
624
					}
625
					break;
626
627
				case 'jetpack_testimonial':
628
				case 'jetpack_portfolio':
629 View Code Duplication
				case 'jetpack_comment_likes_enabled':
630
					// settings are stored as 1|0
631
					$coerce_value = (int) $value;
632
					if ( update_option( $key, $coerce_value ) ) {
633
						$updated[ $key ] = (bool) $value;
634
					}
635
					break;
636
637
				case 'jetpack_testimonial_posts_per_page':
638 View Code Duplication
				case 'jetpack_portfolio_posts_per_page':
639
					// settings are stored as numeric
640
					$coerce_value = (int) $value;
641
					if ( update_option( $key, $coerce_value ) ) {
642
						$updated[ $key ] = $coerce_value;
643
					}
644
					break;
645
646
				// Sharing options
647
				case 'sharing_button_style':
648
				case 'sharing_show':
649
				case 'sharing_open_links':
650
					$sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
651
					break;
652
				case 'sharing_label':
653
					$sharing_options[ $key ] = $value;
654
					break;
655
656
				// Keyring token option
657
				case 'eventbrite_api_token':
658
					// These options can only be updated for sites hosted on WordPress.com
659
					if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
660
						if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
661
							if ( delete_option( $key ) ) {
662
								$updated[ $key ] = null;
663
							}
664
						} else if ( update_option( $key, $value ) ) {
665
							$updated[ $key ] = (int) $value;
666
						}
667
					}
668
					break;
669
670
				case 'api_cache':
671
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
672
						if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
673
							$updated[ $key ] = false;
674
						}
675
					} else if ( update_option( 'jetpack_api_cache_enabled', true ) ) {
676
						$updated[ $key ] = true;
677
					}
678
					break;
679
680
				case 'timezone_string':
681
					// Map UTC+- timezones to gmt_offsets and set timezone_string to empty
682
					// https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
683
					if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
684
						$gmt_offset = preg_replace( '/UTC\+?/', '', $value );
685
						if ( update_option( 'gmt_offset', $gmt_offset ) ) {
686
							$updated[ 'gmt_offset' ] = $gmt_offset;
687
						}
688
689
						$value = '';
690
					}
691
692
					// Always set timezone_string either with the given value or with an
693
					// empty string
694
					if ( update_option( $key, $value ) ) {
695
						$updated[ $key ] = $value;
696
					}
697
					break;
698
699
				case 'date_format':
700
				case 'time_format':
701
					// settings are stored as strings
702
					if ( update_option( $key, sanitize_text_field( $value ) ) ) {
703
						$updated[ $key ] = $value;
704
					}
705
					break;
706
707
				case 'start_of_week':
708
					// setting is stored as int in 0-6 range (days of week)
709
					$coerce_value = (int) $value;
710
					$limit_value  = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
711
					if ( update_option( $key, $limit_value ) ) {
712
						$updated[ $key ] = $limit_value;
713
					}
714
					break;
715
716
				case 'site_icon':
717
					// settings are stored as deletable numeric (all empty
718
					// values as delete intent), validated as media image
719
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
720
						/**
721
						 * Fallback mechanism to clear a third party site icon setting. Can be used
722
						 * to unset the option when an API request instructs the site to remove the site icon.
723
						 *
724
						 * @module json-api
725
						 *
726
						 * @since 4.10
727
						 */
728
						if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false )  ) {
729
							$updated[ $key ] = null;
730
						}
731
					} else if ( is_numeric( $value ) ) {
732
						$coerce_value = (int) $value;
733
						if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
734
							$updated[ $key ] = $coerce_value;
735
						}
736
					}
737
					break;
738
739 View Code Duplication
				case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
740
					if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_grandfathered_front_page_meta() ) {
741
						return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
742
					}
743
744
					if ( ! is_string( $value ) ) {
745
						return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
746
					}
747
748
					$new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
749
750
					if ( ! empty( $new_description ) ) {
751
						$updated[ $key ] = $new_description;
752
					}
753
					break;
754
755 View Code Duplication
				case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
756
					if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
757
						return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
758
					}
759
760
					if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
761
						return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
762
					}
763
764
					$new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
765
766
					if ( ! empty( $new_title_formats ) ) {
767
						$updated[ $key ] = $new_title_formats;
768
					}
769
					break;
770
771
				case 'verification_services_codes':
772
					$verification_codes = jetpack_verification_validate( $value );
773
774
					if ( update_option( 'verification_services_codes', $verification_codes ) ) {
775
						$updated[ $key ] = $verification_codes;
776
					}
777
					break;
778
779
				case 'wpcom_publish_posts_with_markdown':
780
				case 'wpcom_publish_comments_with_markdown':
781
					$coerce_value = (bool) $value;
782
					if ( update_option( $key, $coerce_value ) ) {
783
						$updated[ $key ] = $coerce_value;
784
					}
785
					break;
786
787
				case 'amp_is_enabled':
788
					if ( function_exists( 'wpcom_update_amp_enabled' ) ) {
789
						$saved = wpcom_update_amp_enabled( $blog_id, $value );
790
						if ( $saved ) {
791
							$updated[ $key ] = (bool) $value;
792
						}
793
					}
794
					break;
795
796
				case 'net_neutrality':
797
					$original_value = $value;
798
					$value = array( 'enabled' => (bool) $value );
799
					if ( update_option( 'net_neutrality_options_2017', $value ) ) {
800
						$updated[ $key ] = $original_value;
801
					}
802
803
					break;
804
805
				case 'rss_use_excerpt':
806
					update_option( 'rss_use_excerpt', (int)(bool) $value );
807
					break;
808
809
				default:
810
					//allow future versions of this endpoint to support additional settings keys
811
					if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
812
						/**
813
						 * Filter current site setting value to be updated.
814
						 *
815
						 * @module json-api
816
						 *
817
						 * @since 3.9.3
818
						 *
819
						 * @param mixed $response_item A single site setting value.
820
						 */
821
						$value = apply_filters( 'site_settings_endpoint_update_' . $key, $value );
822
						$updated[ $key ] = $value;
823
						continue;
824
					}
825
826
					// no worries, we've already whitelisted and casted arguments above
827
					if ( update_option( $key, $value ) ) {
828
						$updated[ $key ] = $value;
829
					}
830
			}
831
		}
832
833
		if ( count( $jetpack_relatedposts_options ) ) {
834
			// track new jetpack_relatedposts options against old
835
			$old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
836
			if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options ) ) {
837
				foreach ( $jetpack_relatedposts_options as $key => $value ) {
838
					if ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
839
						$updated[ 'jetpack_relatedposts_' . $key ] = $value;
840
					}
841
				}
842
			}
843
		}
844
845
		if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
846
			$ss = new Sharing_Service();
847
848
			// Merge current values with updated, since Sharing_Service expects
849
			// all values to be included when updating
850
			$current_sharing_options = $ss->get_global_options();
851
			foreach ( $current_sharing_options as $key => $val ) {
852
				if ( ! isset( $sharing_options[ $key ] ) ) {
853
					$sharing_options[ $key ] = $val;
854
				}
855
			}
856
857
			$updated_social_options = $ss->set_global_options( $sharing_options );
858
859
			if ( isset( $input['sharing_button_style'] ) ) {
860
				$updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
861
			}
862
			if ( isset( $input['sharing_label'] ) ) {
863
				// Sharing_Service won't report label as updated if set to default
864
				$updated['sharing_label'] = (string) $sharing_options['sharing_label'];
865
			}
866
			if ( isset( $input['sharing_show'] ) ) {
867
				$updated['sharing_show'] = (array) $updated_social_options['show'];
868
			}
869
			if ( isset( $input['sharing_open_links'] ) ) {
870
				$updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
871
			}
872
		}
873
874
		return array(
875
			'updated' => $updated
876
		);
877
878
	}
879
}
880