Completed
Push — fix/twitter-cards-3778 ( 2ceea6...e05f35 )
by Jeremy
09:08
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
		'settings'          => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
12
	);
13
14
	// GET /sites/%s/settings
15
	// POST /sites/%s/settings
16
	function callback( $path = '', $blog_id = 0 ) {
17
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
18
		if ( is_wp_error( $blog_id ) ) {
19
			return $blog_id;
20
		}
21
22
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
23
			$this->load_theme_functions();
24
		}
25
26
		if ( ! is_user_logged_in() ) {
27
			return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
28
		} else if ( ! current_user_can( 'manage_options' ) ) {
29
			return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
30
		}
31
32
		if ( 'GET' === $this->api->method ) {
33
			/**
34
			 * Fires on each GET request to a specific endpoint.
35
			 *
36
			 * @module json-api
37
			 *
38
			 * @since 3.2.0
39
			 *
40
			 * @param string sites.
41
			 */
42
			do_action( 'wpcom_json_api_objects', 'sites' );
43
			return $this->get_settings_response();
44
		} else if ( 'POST' === $this->api->method ) {
45
			return $this->update_settings();
46
		} else {
47
			return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
48
		}
49
50
	}
51
52
	/**
53
	 * Determines whether jetpack_relatedposts is supported
54
	 *
55
	 * @return (bool)
56
	 */
57
	public function jetpack_relatedposts_supported() {
58
		$wpcom_related_posts_theme_blacklist = array(
59
			'Expound',
60
			'Traveler',
61
			'Opti',
62
			'Currents',
63
		);
64
		return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist ) );
65
	}
66
67
	/**
68
	 * Returns category details
69
	 *
70
	 * @return (array)
71
	 */
72
	public function get_category_details( $category ) {
73
		return array(
74
			'value' => $category->term_id,
75
			'name' => $category->name
76
		);
77
	}
78
79
	/**
80
	 * Collects the necessary information to return for a get settings response.
81
	 *
82
	 * @return (array)
83
	 */
84
	public function get_settings_response() {
85
86
		// Allow update in later versions
87
		/**
88
		 * Filter the structure of site settings to return.
89
		 *
90
		 * @module json-api
91
		 *
92
		 * @since 3.9.3
93
		 *
94
		 * @param array $site_format Data structure.
95
		 */
96
		$response_format = apply_filters( 'site_settings_site_format', self::$site_format );
97
98
		$blog_id = (int) $this->api->get_blog_id_for_output();
99
		/** This filter is documented in class.json-api-endpoints.php */
100
		$is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
0 ignored issues
show
Unused Code introduced by
$is_jetpack 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...
101
102
		foreach ( array_keys( $response_format ) as $key ) {
103
104
			// refactoring to change lang parameter to locale in 1.2
105
			if ( $lang_or_locale = $this->get_locale( $key ) ) {
106
				$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...
107
				continue;
108
			}
109
110
			switch ( $key ) {
111
			case 'ID' :
112
				$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...
113
				break;
114
			case 'name' :
115
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
116
				break;
117
			case 'description' :
118
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
119
				break;
120
			case 'URL' :
121
				$response[$key] = (string) home_url();
122
				break;
123
			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...
124
125
				$jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
126
127
				if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
128
					$jetpack_relatedposts_options[ 'enabled' ] = Jetpack::is_module_active( 'related-posts' );
129
				}
130
131
				// array_values() is necessary to ensure the array starts at index 0.
132
				$post_categories = array_values(
133
					array_map(
134
						array( $this, 'get_category_details' ),
135
						get_categories( array( 'hide_empty' => false ) )
136
					)
137
				);
138
139
				$eventbrite_api_token = (int) get_option( 'eventbrite_api_token' );
140
				if ( 0 === $eventbrite_api_token ) {
141
					$eventbrite_api_token = null;
142
				}
143
144
				$holiday_snow = false;
145
				if ( function_exists( 'jetpack_holiday_snow_option_name' ) ) {
146
					$holiday_snow = (bool) get_option( jetpack_holiday_snow_option_name() );
147
				}
148
149
				$response[ $key ] = array(
150
151
					// also exists as "options"
152
					'admin_url'               => get_admin_url(),
153
					'default_ping_status'     => (bool) ( 'closed' != get_option( 'default_ping_status' ) ),
154
					'default_comment_status'  => (bool) ( 'closed' != get_option( 'default_comment_status' ) ),
155
156
					// new stuff starts here
157
					'blog_public'             => (int) get_option( 'blog_public' ),
158
					'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
159
					'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
160
					'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options[ 'enabled' ],
161
					'jetpack_relatedposts_show_headline' => (bool) $jetpack_relatedposts_options[ 'show_headline' ],
162
					'jetpack_relatedposts_show_thumbnails' => (bool) $jetpack_relatedposts_options[ 'show_thumbnails' ],
163
					'default_category'        => (int) get_option('default_category'),
164
					'post_categories'         => (array) $post_categories,
165
					'default_post_format'     => get_option( 'default_post_format' ),
166
					'default_pingback_flag'   => (bool) get_option( 'default_pingback_flag' ),
167
					'require_name_email'      => (bool) get_option( 'require_name_email' ),
168
					'comment_registration'    => (bool) get_option( 'comment_registration' ),
169
					'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
170
					'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
171
					'thread_comments'         => (bool) get_option( 'thread_comments' ),
172
					'thread_comments_depth'   => (int) get_option( 'thread_comments_depth' ),
173
					'page_comments'           => (bool) get_option( 'page_comments' ),
174
					'comments_per_page'       => (int) get_option( 'comments_per_page' ),
175
					'default_comments_page'   => get_option( 'default_comments_page' ),
176
					'comment_order'           => get_option( 'comment_order' ),
177
					'comments_notify'         => (bool) get_option( 'comments_notify' ),
178
					'moderation_notify'       => (bool) get_option( 'moderation_notify' ),
179
					'social_notifications_like' => ( "on" == get_option( 'social_notifications_like' ) ),
180
					'social_notifications_reblog' => ( "on" == get_option( 'social_notifications_reblog' ) ),
181
					'social_notifications_subscribe' => ( "on" == get_option( 'social_notifications_subscribe' ) ),
182
					'comment_moderation'      => (bool) get_option( 'comment_moderation' ),
183
					'comment_whitelist'       => (bool) get_option( 'comment_whitelist' ),
184
					'comment_max_links'       => (int) get_option( 'comment_max_links' ),
185
					'moderation_keys'         => get_option( 'moderation_keys' ),
186
					'blacklist_keys'          => get_option( 'blacklist_keys' ),
187
					'lang_id'                 => get_option( 'lang_id' ),
188
					'wga'                     => get_option( 'wga' ),
189
					'disabled_likes'          => (bool) get_option( 'disabled_likes' ),
190
					'disabled_reblogs'        => (bool) get_option( 'disabled_reblogs' ),
191
					'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
192
					'twitter_via'             => (string) get_option( 'twitter_via' ),
193
					'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
194
					'eventbrite_api_token'    => $eventbrite_api_token,
195
					'holidaysnow'             => $holiday_snow,
196
					'gmt_offset'              => get_option( 'gmt_offset' ),
197
					'timezone_string'         => get_option( 'timezone_string' ),
198
					'jetpack_testimonial'     => (bool) get_option( 'jetpack_testimonial', '0' ),
199
					'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
200
					'jetpack_portfolio'       => (bool) get_option( 'jetpack_portfolio', '0' ),
201
					'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
202
				);
203
204
				//allow future versions of this endpoint to support additional settings keys
205
				/**
206
				 * Filter the current site setting in the returned response.
207
				 *
208
				 * @module json-api
209
				 *
210
				 * @since 3.9.3
211
				 *
212
				 * @param mixed $response_item A single site setting.
213
				 */
214
				$response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ] );
215
216
				if ( class_exists( 'Sharing_Service' ) ) {
217
					$ss = new Sharing_Service();
218
					$sharing = $ss->get_global_options();
219
					$response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
220
					$response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
221
					$response[ $key ]['sharing_show'] = (array) $sharing['show'];
222
					$response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
223
				}
224
225
				if ( function_exists( 'jetpack_protect_format_whitelist' ) ) {
226
					$response[ $key ]['jetpack_protect_whitelist'] = jetpack_protect_format_whitelist();
227
				}
228
229
				if ( ! current_user_can( 'edit_posts' ) )
230
					unset( $response[$key] );
231
				break;
232
			}
233
		}
234
235
		return $response;
236
237
	}
238
239 View Code Duplication
	protected function get_locale( $key ) {
240
		if ( 'lang' == $key ) {
241
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
242
				return (string) get_blog_lang_code();
243
			} else {
244
				return get_locale();
245
			}
246
		}
247
248
		return false;
249
	}
250
251
252
	/**
253
	 * Updates site settings for authorized users
254
	 *
255
	 * @return (array)
256
	 */
257
	public function update_settings() {
258
259
		// $this->input() retrieves posted arguments whitelisted and casted to the $request_format
260
		// specs that get passed in when this class is instantiated
261
		/**
262
		 * Filters the settings to be updated on the site.
263
		 *
264
		 * @module json-api
265
		 *
266
		 * @since 3.6.0
267
		 *
268
		 * @param array $input Associative array of site settings to be updated.
269
		 */
270
		$input = apply_filters( 'rest_api_update_site_settings', $this->input() );
271
272
		$jetpack_relatedposts_options = array();
273
		$sharing_options = array();
274
		$updated = array();
275
276
		foreach ( $input as $key => $value ) {
277
278
			if ( ! is_array( $value ) ) {
279
				$value = trim( $value );
280
			}
281
			$value = wp_unslash( $value );
282
283
			switch ( $key ) {
284
285
				case 'default_ping_status':
286
				case 'default_comment_status':
287
					// settings are stored as closed|open
288
					$coerce_value = ( $value ) ? 'open' : 'closed';
289
					if ( update_option( $key, $coerce_value ) ) {
290
						$updated[ $key ] = $value;
291
					};
292
					break;
293
				case 'jetpack_protect_whitelist':
294
					if ( function_exists( 'jetpack_protect_save_whitelist' ) ) {
295
						$result = jetpack_protect_save_whitelist( $value );
296
						if ( is_wp_error( $result ) ) {
297
							return $result;
298
						}
299
						$updated[ $key ] = jetpack_protect_format_whitelist();
300
					}
301
					break;
302
				case 'jetpack_sync_non_public_post_stati':
303
					Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
304
					break;
305
				case 'jetpack_relatedposts_enabled':
306
				case 'jetpack_relatedposts_show_thumbnails':
307
				case 'jetpack_relatedposts_show_headline':
308
					if ( ! $this->jetpack_relatedposts_supported() ) {
309
						break;
310
					}
311
					if ( 'jetpack_relatedposts_enabled' === $key && method_exists( 'Jetpack', 'is_module_active' ) && $this->jetpack_relatedposts_supported() ) {
312
						$before_action = Jetpack::is_module_active('related-posts');
313
						if ( $value ) {
314
							Jetpack::activate_module( 'related-posts', false, false );
315
						} else {
316
							Jetpack::deactivate_module( 'related-posts' );
317
						}
318
						$after_action = Jetpack::is_module_active('related-posts');
319
						if ( $after_action == $before_action ) {
320
							break;
321
						}
322
					}
323
					$just_the_key = substr( $key, 21 );
324
					$jetpack_relatedposts_options[ $just_the_key ] = $value;
325
				break;
326
327
				case 'social_notifications_like':
328
				case 'social_notifications_reblog':
329
				case 'social_notifications_subscribe':
330
					// settings are stored as on|off
331
					$coerce_value = ( $value ) ? 'on' : 'off';
332
					if ( update_option( $key, $coerce_value ) ) {
333
						$updated[ $key ] = $value;
334
					}
335
					break;
336
				case 'wga':
337
					if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
338
						return new WP_Error( 'invalid_code', 'Invalid UA ID' );
339
					}
340
					$wga = get_option( 'wga', array() );
341
					$wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
342
					if ( update_option( 'wga', $wga ) ) {
343
						$updated[ $key ] = $value;
344
					}
345
346
					$enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
347
348
					/** This action is documented in modules/widgets/social-media-icons.php */
349
					do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
350
351
					$business_plugins = WPCOM_Business_Plugins::instance();
352
					$business_plugins->activate_plugin( 'wp-google-analytics' );
353
					break;
354
355
				case 'jetpack_testimonial':
356
				case 'jetpack_portfolio':
357 View Code Duplication
				case 'jetpack_comment_likes_enabled':
358
					// settings are stored as 1|0
359
					$coerce_value = (int) $value;
360
					if ( update_option( $key, $coerce_value ) ) {
361
						$updated[ $key ] = (bool) $value;
362
					}
363
					break;
364
365
				case 'jetpack_testimonial_posts_per_page':
366 View Code Duplication
				case 'jetpack_portfolio_posts_per_page':
367
					// settings are stored as numeric
368
					$coerce_value = (int) $value;
369
					if ( update_option( $key, $coerce_value ) ) {
370
						$updated[ $key ] = $coerce_value;
371
					}
372
					break;
373
374
				// Sharing options
375
				case 'sharing_button_style':
376
				case 'sharing_show':
377
				case 'sharing_open_links':
378
					$sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
379
					break;
380
				case 'sharing_label':
381
					$sharing_options[ $key ] = $value;
382
					break;
383
384
				// Keyring token option
385
				case 'eventbrite_api_token':
386
					// These options can only be updated for sites hosted on WordPress.com
387
					if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
388
						if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
389
							if ( delete_option( $key ) ) {
390
								$updated[ $key ] = null;
391
							}
392
						} else if ( update_option( $key, $value ) ) {
393
							$updated[ $key ] = (int) $value;
394
						}
395
					}
396
					break;
397
398
				case 'holidaysnow':
399
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
400
						if ( function_exists( 'jetpack_holiday_snow_option_name' ) && delete_option( jetpack_holiday_snow_option_name() ) ) {
401
							$updated[ $key ] = false;
402
						}
403
					} else if ( function_exists( 'jetpack_holiday_snow_option_name' ) && update_option( jetpack_holiday_snow_option_name(), 'letitsnow' ) ) {
404
						$updated[ $key ] = true;
405
					}
406
					break;
407
408
				case 'timezone_string':
409
					// Map UTC+- timezones to gmt_offsets and set timezone_string to empty
410
					// https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
411
					if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
412
						$gmt_offset = preg_replace( '/UTC\+?/', '', $value );
413
						if ( update_option( 'gmt_offset', $gmt_offset ) ) {
414
							$updated[ 'gmt_offset' ] = $gmt_offset;
415
						}
416
417
						$value = '';
418
					}
419
420
					// Always set timezone_string either with the given value or with an 
421
					// empty string
422
					if ( update_option( $key, $value ) ) {
423
						$updated[ $key ] = $value;
424
					}
425
					break;
426
427
				default:
428
					//allow future versions of this endpoint to support additional settings keys
429
					if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
430
						/**
431
						 * Filter current site setting value to be updated.
432
						 *
433
						 * @module json-api
434
						 *
435
						 * @since 3.9.3
436
						 *
437
						 * @param mixed $response_item A single site setting value.
438
						 */
439
						$value = apply_filters( 'site_settings_endpoint_update_' . $key, $value );
440
						$updated[ $key ] = $value;
441
						continue;
442
					}
443
444
					// no worries, we've already whitelisted and casted arguments above
445
					if ( update_option( $key, $value ) ) {
446
						$updated[ $key ] = $value;
447
					}
448
			}
449
		}
450
451
		if ( count( $jetpack_relatedposts_options ) ) {
452
			// track new jetpack_relatedposts options against old
453
			$old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
454
			if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options ) ) {
455
				foreach( $jetpack_relatedposts_options as $key => $value ) {
456
					if ( $value !== $old_relatedposts_options[ $key ] ) {
457
						$updated[ 'jetpack_relatedposts_' . $key ] = $value;
458
					}
459
				}
460
			}
461
		}
462
463
		if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
464
			$ss = new Sharing_Service();
465
466
			// Merge current values with updated, since Sharing_Service expects
467
			// all values to be included when updating
468
			$current_sharing_options = $ss->get_global_options();
469
			foreach ( $current_sharing_options as $key => $val ) {
470
				if ( ! isset( $sharing_options[ $key ] ) ) {
471
					$sharing_options[ $key ] = $val;
472
				}
473
			}
474
475
			$updated_social_options = $ss->set_global_options( $sharing_options );
476
477
			if ( isset( $input['sharing_button_style'] ) ) {
478
				$updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
479
			}
480
			if ( isset( $input['sharing_label'] ) ) {
481
				// Sharing_Service won't report label as updated if set to default
482
				$updated['sharing_label'] = (string) $sharing_options['sharing_label'];
483
			}
484
			if ( isset( $input['sharing_show'] ) ) {
485
				$updated['sharing_show'] = (array) $updated_social_options['show'];
486
			}
487
			if ( isset( $input['sharing_open_links'] ) ) {
488
				$updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
489
			}
490
		}
491
492
		return array(
493
			'updated' => $updated
494
		);
495
496
	}
497
}
498