Completed
Push — branch-3.8 ( 6dae2a...5ea8e7 )
by
unknown
57:29 queued 49:08
created

WPCOM_JSON_API_Site_Settings_Endpoint   D

Complexity

Total Complexity 87

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5
Metric Value
wmc 87
lcom 1
cbo 5
dl 0
loc 404
rs 4.8718

5 Methods

Rating   Name   Duplication   Size   Complexity  
C callback() 0 35 8
A jetpack_relatedposts_supported() 0 9 1
A get_category_details() 0 6 1
D get_settings_response() 0 121 14
F update_settings() 0 195 63

How to fix   Complexity   

Complex Class

Complex classes like WPCOM_JSON_API_Site_Settings_Endpoint often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WPCOM_JSON_API_Site_Settings_Endpoint, and based on these observations, apply Extract Interface, too.

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
		$response_format = self::$site_format;
87
		$blog_id = (int) $this->api->get_blog_id_for_output();
88
		/** This filter is documented in class.json-api-endpoints.php */
89
		$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...
90
91
		foreach ( array_keys( $response_format ) as $key ) {
92
			switch ( $key ) {
93
			case 'ID' :
94
				$response[$key] = $blog_id;
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...
95
				break;
96
			case 'name' :
97
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
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...
98
				break;
99
			case 'description' :
100
				$response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
101
				break;
102
			case 'URL' :
103
				$response[$key] = (string) home_url();
104
				break;
105
			case 'lang' :
106
				$response[$key] = (string) get_bloginfo( 'language' );
107
				break;
108
			case 'settings':
109
110
				$jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
111
112
				if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
113
					$jetpack_relatedposts_options[ 'enabled' ] = Jetpack::is_module_active( 'related-posts' );
114
				}
115
116
				// array_values() is necessary to ensure the array starts at index 0.
117
				$post_categories = array_values(
118
					array_map(
119
						array( $this, 'get_category_details' ),
120
						get_categories( array( 'hide_empty' => false ) )
121
					)
122
				);
123
124
				$eventbrite_api_token = (int) get_option( 'eventbrite_api_token' );
125
				if ( 0 === $eventbrite_api_token ) {
126
					$eventbrite_api_token = null;
127
				}
128
129
				$holiday_snow = false;
130
				if ( function_exists( 'jetpack_holiday_snow_option_name' ) ) {
131
					$holiday_snow = (bool) get_option( jetpack_holiday_snow_option_name() );
132
				}
133
134
				$response[$key] = array(
135
136
					// also exists as "options"
137
					'admin_url'               => get_admin_url(),
138
					'default_ping_status'     => (bool) ( 'closed' != get_option( 'default_ping_status' ) ),
139
					'default_comment_status'  => (bool) ( 'closed' != get_option( 'default_comment_status' ) ),
140
141
					// new stuff starts here
142
					'blog_public'             => (int) get_option( 'blog_public' ),
143
					'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
144
					'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
145
					'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options[ 'enabled' ],
146
					'jetpack_relatedposts_show_headline' => (bool) $jetpack_relatedposts_options[ 'show_headline' ],
147
					'jetpack_relatedposts_show_thumbnails' => (bool) $jetpack_relatedposts_options[ 'show_thumbnails' ],
148
					'default_category'        => (int) get_option('default_category'),
149
					'post_categories'         => (array) $post_categories,
150
					'default_post_format'     => get_option( 'default_post_format' ),
151
					'default_pingback_flag'   => (bool) get_option( 'default_pingback_flag' ),
152
					'require_name_email'      => (bool) get_option( 'require_name_email' ),
153
					'comment_registration'    => (bool) get_option( 'comment_registration' ),
154
					'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
155
					'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
156
					'thread_comments'         => (bool) get_option( 'thread_comments' ),
157
					'thread_comments_depth'   => (int) get_option( 'thread_comments_depth' ),
158
					'page_comments'           => (bool) get_option( 'page_comments' ),
159
					'comments_per_page'       => (int) get_option( 'comments_per_page' ),
160
					'default_comments_page'   => get_option( 'default_comments_page' ),
161
					'comment_order'           => get_option( 'comment_order' ),
162
					'comments_notify'         => (bool) get_option( 'comments_notify' ),
163
					'moderation_notify'       => (bool) get_option( 'moderation_notify' ),
164
					'social_notifications_like' => ( "on" == get_option( 'social_notifications_like' ) ),
165
					'social_notifications_reblog' => ( "on" == get_option( 'social_notifications_reblog' ) ),
166
					'social_notifications_subscribe' => ( "on" == get_option( 'social_notifications_subscribe' ) ),
167
					'comment_moderation'      => (bool) get_option( 'comment_moderation' ),
168
					'comment_whitelist'       => (bool) get_option( 'comment_whitelist' ),
169
					'comment_max_links'       => (int) get_option( 'comment_max_links' ),
170
					'moderation_keys'         => get_option( 'moderation_keys' ),
171
					'blacklist_keys'          => get_option( 'blacklist_keys' ),
172
					'lang_id'                 => get_option( 'lang_id' ),
173
					'wga'                     => get_option( 'wga' ),
174
					'disabled_likes'          => (bool) get_option( 'disabled_likes' ),
175
					'disabled_reblogs'        => (bool) get_option( 'disabled_reblogs' ),
176
					'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
177
					'twitter_via'             => (string) get_option( 'twitter_via' ),
178
					'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
179
					'eventbrite_api_token'    => $eventbrite_api_token,
180
					'holidaysnow'             => $holiday_snow
181
				);
182
183
				if ( class_exists( 'Sharing_Service' ) ) {
184
					$ss = new Sharing_Service();
185
					$sharing = $ss->get_global_options();
186
					$response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
187
					$response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
188
					$response[ $key ]['sharing_show'] = (array) $sharing['show'];
189
					$response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
190
				}
191
192
				if ( function_exists( 'jetpack_protect_format_whitelist' ) ) {
193
					$response[ $key ]['jetpack_protect_whitelist'] = jetpack_protect_format_whitelist();
194
				}
195
196
				if ( ! current_user_can( 'edit_posts' ) )
197
					unset( $response[$key] );
198
				break;
199
			}
200
		}
201
202
		return $response;
203
204
	}
205
206
	/**
207
	 * Updates site settings for authorized users
208
	 *
209
	 * @return (array)
210
	 */
211
	public function update_settings() {
212
213
		// $this->input() retrieves posted arguments whitelisted and casted to the $request_format
214
		// specs that get passed in when this class is instantiated
215
		/**
216
		 * Filters the settings to be updated on the site.
217
		 *
218
		 * @module json-api
219
		 *
220
		 * @since 3.6.0
221
		 *
222
		 * @param array $input Associative array of site settings to be updated.
223
		 */
224
		$input = apply_filters( 'rest_api_update_site_settings', $this->input() );
225
226
		$jetpack_relatedposts_options = array();
227
		$sharing_options = array();
228
		$updated = array();
229
230
		foreach ( $input as $key => $value ) {
231
232
			if ( ! is_array( $value ) ) {
233
				$value = trim( $value );
234
			}
235
			$value = wp_unslash( $value );
236
237
			switch ( $key ) {
238
239
				case 'default_ping_status':
240
				case 'default_comment_status':
241
					// settings are stored as closed|open
242
					$coerce_value = ( $value ) ? 'open' : 'closed';
243
					if ( update_option( $key, $coerce_value ) ) {
244
						$updated[ $key ] = $value;
245
					};
246
					break;
247
				case 'jetpack_protect_whitelist':
248
					if ( function_exists( 'jetpack_protect_save_whitelist' ) ) {
249
						$result = jetpack_protect_save_whitelist( $value );
250
						if ( is_wp_error( $result ) ) {
251
							return $result;
252
						}
253
						$updated[ $key ] = jetpack_protect_format_whitelist();
254
					}
255
					break;
256
				case 'jetpack_sync_non_public_post_stati':
257
					Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
258
					break;
259
				case 'jetpack_relatedposts_enabled':
260
				case 'jetpack_relatedposts_show_thumbnails':
261
				case 'jetpack_relatedposts_show_headline':
262
					if ( ! $this->jetpack_relatedposts_supported() ) {
263
						break;
264
					}
265
					if ( 'jetpack_relatedposts_enabled' === $key && method_exists( 'Jetpack', 'is_module_active' ) && $this->jetpack_relatedposts_supported() ) {
266
						$before_action = Jetpack::is_module_active('related-posts');
267
						if ( $value ) {
268
							Jetpack::activate_module( 'related-posts', false, false );
269
						} else {
270
							Jetpack::deactivate_module( 'related-posts' );
271
						}
272
						$after_action = Jetpack::is_module_active('related-posts');
273
						if ( $after_action == $before_action ) {
274
							break;
275
						}
276
					}
277
					$just_the_key = substr( $key, 21 );
278
					$jetpack_relatedposts_options[ $just_the_key ] = $value;
279
				break;
280
281
				case 'social_notifications_like':
282
				case 'social_notifications_reblog':
283
				case 'social_notifications_subscribe':
284
					// settings are stored as on|off
285
					$coerce_value = ( $value ) ? 'on' : 'off';
286
					if ( update_option( $key, $coerce_value ) ) {
287
						$updated[ $key ] = $value;
288
					}
289
					break;
290
				case 'wga':
291
					if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
292
						return new WP_Error( 'invalid_code', 'Invalid UA ID' );
293
					}
294
					$wga = get_option( 'wga', array() );
295
					$wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
296
					if ( update_option( 'wga', $wga ) ) {
297
						$updated[ $key ] = $value;
298
					}
299
300
					$enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
301
302
					/** This action is documented in modules/widgets/social-media-icons.php */
303
					do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
304
305
					$business_plugins = WPCOM_Business_Plugins::instance();
306
					$business_plugins->activate_plugin( 'wp-google-analytics' );
307
					break;
308
309
				case 'jetpack_comment_likes_enabled':
310
					// settings are stored as 1|0
311
					$coerce_value = (int) $value;
312
					if ( update_option( $key, $coerce_value ) ) {
313
						$updated[ $key ] = $value;
314
					}
315
					break;
316
317
				// Sharing options
318
				case 'sharing_button_style':
319
				case 'sharing_show':
320
				case 'sharing_open_links':
321
					$sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
322
					break;
323
				case 'sharing_label':
324
					$sharing_options[ $key ] = $value;
325
					break;
326
327
				// Keyring token option
328
				case 'eventbrite_api_token':
329
					// These options can only be updated for sites hosted on WordPress.com
330
					if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
331
						if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
332
							if ( delete_option( $key ) ) {
333
								$updated[ $key ] = null;
334
							}
335
						} else if ( update_option( $key, $value ) ) {
336
							$updated[ $key ] = (int) $value;
337
						}
338
					}
339
					break;
340
341
				case 'holidaysnow':
342
					if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
343
						if ( function_exists( 'jetpack_holiday_snow_option_name' ) && delete_option( jetpack_holiday_snow_option_name() ) ) {
344
							$updated[ $key ] = false;
345
						}
346
 					} else if ( function_exists( 'jetpack_holiday_snow_option_name' ) && update_option( jetpack_holiday_snow_option_name(), 'letitsnow' ) ) {
347
						$updated[ $key ] = true;
348
 					}
349
 					break;
350
351
				// no worries, we've already whitelisted and casted arguments above
352
				default:
353
					if ( update_option( $key, $value ) ) {
354
						$updated[ $key ] = $value;
355
					}
356
357
			}
358
		}
359
360
		if ( count( $jetpack_relatedposts_options ) ) {
361
			// track new jetpack_relatedposts options against old
362
			$old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
363
			if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options ) ) {
364
				foreach( $jetpack_relatedposts_options as $key => $value ) {
365
					if ( $value !== $old_relatedposts_options[ $key ] ) {
366
						$updated[ 'jetpack_relatedposts_' . $key ] = $value;
367
					}
368
				}
369
			}
370
		}
371
372
		if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
373
			$ss = new Sharing_Service();
374
375
			// Merge current values with updated, since Sharing_Service expects
376
			// all values to be included when updating
377
			$current_sharing_options = $ss->get_global_options();
378
			foreach ( $current_sharing_options as $key => $val ) {
379
				if ( ! isset( $sharing_options[ $key ] ) ) {
380
					$sharing_options[ $key ] = $val;
381
				}
382
			}
383
384
			$updated_social_options = $ss->set_global_options( $sharing_options );
385
386
			if ( isset( $input['sharing_button_style'] ) ) {
387
				$updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
388
			}
389
			if ( isset( $input['sharing_label'] ) ) {
390
				// Sharing_Service won't report label as updated if set to default
391
				$updated['sharing_label'] = (string) $sharing_options['sharing_label'];
392
			}
393
			if ( isset( $input['sharing_show'] ) ) {
394
				$updated['sharing_show'] = (array) $updated_social_options['show'];
395
			}
396
			if ( isset( $input['sharing_open_links'] ) ) {
397
				$updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
398
			}
399
		}
400
401
		return array(
402
			'updated' => $updated
403
		);
404
405
	}
406
}
407