Completed
Push — add/jetpack-search-sorting-ui ( 6e6d59...e854e3 )
by
unknown
10:03 queued 59s
created

Jetpack_Core_API_Data   F

Complexity

Total Complexity 228

Size/Duplication

Total Lines 933
Duplicated Lines 6.65 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 228
lcom 1
cbo 11
dl 62
loc 933
rs 3.5555
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 11 3
C get_module() 0 33 7
C get_all_options() 0 66 14
F update_data() 52 476 134
F _process_onboarding() 10 134 40
D handle_business_address() 0 53 11
A has_business_address_widget() 0 12 4
A _process_post_by_email() 0 21 4
C can_request() 0 30 11

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Core_API_Data 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 Jetpack_Core_API_Data, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This is the base class for every Core API endpoint Jetpack uses.
4
 *
5
 */
6
class Jetpack_Core_API_Module_Toggle_Endpoint
7
	extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint {
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
8
9
	/**
10
	 * Check if the module requires the site to be publicly accessible from WPCOM.
11
	 * If the site meets this requirement, the module is activated. Otherwise an error is returned.
12
	 *
13
	 * @since 4.3.0
14
	 *
15
	 * @param WP_REST_Request $request {
16
	 *     Array of parameters received by request.
17
	 *
18
	 *     @type string $slug Module slug.
19
	 *     @type bool   $active should module be activated.
20
	 * }
21
	 *
22
	 * @return WP_REST_Response|WP_Error A REST response if the request was served successfully, otherwise an error.
23
	 */
24
	public function process( $request ) {
25
		if ( $request['active'] ) {
26
			return $this->activate_module( $request );
27
		} else {
28
			return $this->deactivate_module( $request );
29
		}
30
	}
31
32
	/**
33
	 * If it's a valid Jetpack module, activate it.
34
	 *
35
	 * @since 4.3.0
36
	 *
37
	 * @param string|WP_REST_Request $request It's a WP_REST_Request when called from endpoint /module/<slug>/*
38
	 *                                        and a string when called from Jetpack_Core_API_Data->update_data.
39
	 * {
40
	 *     Array of parameters received by request.
41
	 *
42
	 *     @type string $slug Module slug.
43
	 * }
44
	 *
45
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
46
	 */
47
	public function activate_module( $request ) {
48
		$module_slug = '';
0 ignored issues
show
Unused Code introduced by
$module_slug 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...
49
50 View Code Duplication
		if (
51
			(
52
				is_array( $request )
53
				|| is_object( $request )
54
			)
55
			&& isset( $request['slug'] )
56
		) {
57
			$module_slug = $request['slug'];
58
		} else {
59
			$module_slug = $request;
60
		}
61
62 View Code Duplication
		if ( ! Jetpack::is_module( $module_slug ) ) {
63
			return new WP_Error(
64
				'not_found',
65
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
66
				array( 'status' => 404 )
67
			);
68
		}
69
70 View Code Duplication
		if ( Jetpack::activate_module( $module_slug, false, false ) ) {
71
			return rest_ensure_response( array(
72
				'code' 	  => 'success',
73
				'message' => esc_html__( 'The requested Jetpack module was activated.', 'jetpack' ),
74
			) );
75
		}
76
77
		return new WP_Error(
78
			'activation_failed',
79
			esc_html__( 'The requested Jetpack module could not be activated.', 'jetpack' ),
80
			array( 'status' => 424 )
81
		);
82
	}
83
84
	/**
85
	 * If it's a valid Jetpack module, deactivate it.
86
	 *
87
	 * @since 4.3.0
88
	 *
89
	 * @param string|WP_REST_Request $request It's a WP_REST_Request when called from endpoint /module/<slug>/*
90
	 *                                        and a string when called from Jetpack_Core_API_Data->update_data.
91
	 * {
92
	 *     Array of parameters received by request.
93
	 *
94
	 *     @type string $slug Module slug.
95
	 * }
96
	 *
97
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
98
	 */
99
	public function deactivate_module( $request ) {
100
		$module_slug = '';
0 ignored issues
show
Unused Code introduced by
$module_slug 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 View Code Duplication
		if (
103
			(
104
				is_array( $request )
105
				|| is_object( $request )
106
			)
107
			&& isset( $request['slug'] )
108
		) {
109
			$module_slug = $request['slug'];
110
		} else {
111
			$module_slug = $request;
112
		}
113
114 View Code Duplication
		if ( ! Jetpack::is_module( $module_slug ) ) {
115
			return new WP_Error(
116
				'not_found',
117
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
118
				array( 'status' => 404 )
119
			);
120
		}
121
122 View Code Duplication
		if ( ! Jetpack::is_module_active( $module_slug ) ) {
123
			return new WP_Error(
124
				'already_inactive',
125
				esc_html__( 'The requested Jetpack module was already inactive.', 'jetpack' ),
126
				array( 'status' => 409 )
127
			);
128
		}
129
130 View Code Duplication
		if ( Jetpack::deactivate_module( $module_slug ) ) {
131
			return rest_ensure_response( array(
132
				'code' 	  => 'success',
133
				'message' => esc_html__( 'The requested Jetpack module was deactivated.', 'jetpack' ),
134
			) );
135
		}
136
		return new WP_Error(
137
			'deactivation_failed',
138
			esc_html__( 'The requested Jetpack module could not be deactivated.', 'jetpack' ),
139
			array( 'status' => 400 )
140
		);
141
	}
142
143
	/**
144
	 * Check that the current user has permissions to manage Jetpack modules.
145
	 *
146
	 * @since 4.3.0
147
	 *
148
	 * @return bool
149
	 */
150
	public function can_request() {
151
		return current_user_can( 'jetpack_manage_modules' );
152
	}
153
}
154
155
class Jetpack_Core_API_Module_List_Endpoint {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
156
157
	/**
158
	 * A WordPress REST API callback method that accepts a request object and decides what to do with it.
159
	 *
160
	 * @param WP_REST_Request $request The request sent to the WP REST API.
161
	 *
162
	 * @since 4.3.0
163
	 *
164
	 * @return bool|Array|WP_Error a resulting value or object, or an error.
165
	 */
166
	public function process( $request ) {
167
		if ( 'GET' === $request->get_method() ) {
168
			return $this->get_modules( $request );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Core_API_Module_...Endpoint::get_modules() has too many arguments starting with $request.

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...
169
		} else {
170
			return $this->activate_modules( $request );
171
		}
172
	}
173
174
	/**
175
	 * Get a list of all Jetpack modules and their information.
176
	 *
177
	 * @since 4.3.0
178
	 *
179
	 * @return array Array of Jetpack modules.
180
	 */
181
	public function get_modules() {
182
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php' );
183
184
		$modules = Jetpack_Admin::init()->get_modules();
185
		foreach ( $modules as $slug => $properties ) {
186
			$modules[ $slug ]['options'] =
187
				Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $slug );
188
			if (
189
				isset( $modules[ $slug ]['requires_connection'] )
190
				&& $modules[ $slug ]['requires_connection']
191
				&& Jetpack::is_development_mode()
192
			) {
193
				$modules[ $slug ]['activated'] = false;
194
			}
195
		}
196
197
		$modules = Jetpack::get_translated_modules( $modules );
198
199
		return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $modules );
200
	}
201
202
	/**
203
	 * Activate a list of valid Jetpack modules.
204
	 *
205
	 * @since 4.3.0
206
	 *
207
	 * @param WP_REST_Request $request {
208
	 *     Array of parameters received by request.
209
	 *
210
	 *     @type string $slug Module slug.
211
	 * }
212
	 *
213
	 * @return bool|WP_Error True if modules were activated. Otherwise, a WP_Error instance with the corresponding error.
214
	 */
215
	public static function activate_modules( $request ) {
216
217 View Code Duplication
		if (
218
			! isset( $request['modules'] )
219
			|| ! is_array( $request['modules'] )
220
		) {
221
			return new WP_Error(
222
				'not_found',
223
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
224
				array( 'status' => 404 )
225
			);
226
		}
227
228
		$activated = array();
229
		$failed = array();
230
231
		foreach ( $request['modules'] as $module ) {
232
			if ( Jetpack::activate_module( $module, false, false ) ) {
233
				$activated[] = $module;
234
			} else {
235
				$failed[] = $module;
236
			}
237
		}
238
239 View Code Duplication
		if ( empty( $failed ) ) {
240
			return rest_ensure_response( array(
241
				'code' 	  => 'success',
242
				'message' => esc_html__( 'All modules activated.', 'jetpack' ),
243
			) );
244
		}
245
246
		$error = '';
247
248
		$activated_count = count( $activated );
249 View Code Duplication
		if ( $activated_count > 0 ) {
250
			$activated_last = array_pop( $activated );
251
			$activated_text = $activated_count > 1 ? sprintf(
252
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
253
				__( '%1$s and %2$s', 'jetpack' ),
254
				join( ', ', $activated ), $activated_last ) : $activated_last;
255
256
			$error = sprintf(
257
				/* Translators: the variable is a module name. */
258
				_n( 'The module %s was activated.', 'The modules %s were activated.', $activated_count, 'jetpack' ),
259
				$activated_text ) . ' ';
260
		}
261
262
		$failed_count = count( $failed );
263 View Code Duplication
		if ( count( $failed ) > 0 ) {
264
			$failed_last = array_pop( $failed );
265
			$failed_text = $failed_count > 1 ? sprintf(
266
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
267
				__( '%1$s and %2$s', 'jetpack' ),
268
				join( ', ', $failed ), $failed_last ) : $failed_last;
269
270
			$error = sprintf(
271
				/* Translators: the variable is a module name. */
272
				_n( 'The module %s failed to be activated.', 'The modules %s failed to be activated.', $failed_count, 'jetpack' ),
273
				$failed_text ) . ' ';
274
		}
275
276
		return new WP_Error(
277
			'activation_failed',
278
			esc_html( $error ),
279
			array( 'status' => 424 )
280
		);
281
	}
282
283
	/**
284
	 * A WordPress REST API permission callback method that accepts a request object and decides
285
	 * if the current user has enough privileges to act.
286
	 *
287
	 * @since 4.3.0
288
	 *
289
	 * @param WP_REST_Request $request The request sent to the WP REST API.
290
	 *
291
	 * @return bool does the current user have enough privilege.
292
	 */
293
	public function can_request( $request ) {
294
		if ( 'GET' === $request->get_method() ) {
295
			return current_user_can( 'jetpack_admin_page' );
296
		} else {
297
			return current_user_can( 'jetpack_manage_modules' );
298
		}
299
	}
300
}
301
302
/**
303
 * Class that manages updating of Jetpack module options and general Jetpack settings or retrieving module data.
304
 * If no module is specified, all module settings are retrieved/updated.
305
 *
306
 * @since 4.3.0
307
 * @since 4.4.0 Renamed Jetpack_Core_API_Module_Endpoint from to Jetpack_Core_API_Data.
308
 *
309
 * @author Automattic
310
 */
311
class Jetpack_Core_API_Data extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
312
313
	/**
314
	 * Process request by returning the module or updating it.
315
	 * If no module is specified, settings for all modules are assumed.
316
	 *
317
	 * @since 4.3.0
318
	 *
319
	 * @param WP_REST_Request $request
320
	 *
321
	 * @return bool|mixed|void|WP_Error
322
	 */
323
	public function process( $request ) {
324
		if ( 'GET' === $request->get_method() ) {
325
			if ( isset( $request['slug'] ) ) {
326
				return $this->get_module( $request );
327
			}
328
329
			return $this->get_all_options();
330
		} else {
331
			return $this->update_data( $request );
332
		}
333
	}
334
335
	/**
336
	 * Get information about a specific and valid Jetpack module.
337
	 *
338
	 * @since 4.3.0
339
	 *
340
	 * @param WP_REST_Request $request {
341
	 *     Array of parameters received by request.
342
	 *
343
	 *     @type string $slug Module slug.
344
	 * }
345
	 *
346
	 * @return mixed|void|WP_Error
347
	 */
348
	public function get_module( $request ) {
349
		if ( Jetpack::is_module( $request['slug'] ) ) {
350
351
			$module = Jetpack::get_module( $request['slug'] );
352
353
			$module['options'] = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $request['slug'] );
354
355
			if (
356
				isset( $module['requires_connection'] )
357
				&& $module['requires_connection']
358
				&& Jetpack::is_development_mode()
359
			) {
360
				$module['activated'] = false;
361
			}
362
363
			$i18n = jetpack_get_module_i18n( $request['slug'] );
364
			if ( isset( $module['name'] ) ) {
365
				$module['name'] = $i18n['name'];
366
			}
367
			if ( isset( $module['description'] ) ) {
368
				$module['description'] = $i18n['description'];
369
				$module['short_description'] = $i18n['description'];
370
			}
371
372
			return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $module );
373
		}
374
375
		return new WP_Error(
376
			'not_found',
377
			esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
378
			array( 'status' => 404 )
379
		);
380
	}
381
382
	/**
383
	 * Get information about all Jetpack module options and settings.
384
	 *
385
	 * @since 4.6.0
386
	 *
387
	 * @return WP_REST_Response $response
388
	 */
389
	public function get_all_options() {
390
		$response = array();
391
392
		$modules = Jetpack::get_available_modules();
393
		if ( is_array( $modules ) && ! empty( $modules ) ) {
394
			foreach ( $modules as $module ) {
395
				// Add all module options
396
				$options = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $module );
397
				foreach ( $options as $option_name => $option ) {
398
					$response[ $option_name ] = $option['current_value'];
399
				}
400
401
				// Add the module activation state
402
				$response[ $module ] = Jetpack::is_module_active( $module );
403
			}
404
		}
405
406
		$settings = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'settings' );
407
		$holiday_snow_option_name = Jetpack_Core_Json_Api_Endpoints::holiday_snow_option_name();
408
409
		if ( ! function_exists( 'is_plugin_active' ) ) {
410
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
411
		}
412
413
		foreach ( $settings as $setting => $properties ) {
414
			switch ( $setting ) {
415
				case $holiday_snow_option_name:
416
					$response[ $setting ] = get_option( $holiday_snow_option_name ) === 'letitsnow';
417
					break;
418
419
				case 'wordpress_api_key':
420
					// When field is clear, return empty. Otherwise it would return "false".
421
					if ( '' === get_option( 'wordpress_api_key', '' ) ) {
422
						$response[ $setting ] = '';
423
					} else {
424
						if ( ! class_exists( 'Akismet' ) ) {
425
							if ( is_readable( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
426
								require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
427
							}
428
						}
429
						$response[ $setting ] = class_exists( 'Akismet' ) ? Akismet::get_api_key() : '';
430
					}
431
					break;
432
433
				case 'onboarding':
434
					$response[ $setting ] = array(
435
						'siteTitle' => get_option( 'blogname' ),
436
						'siteDescription' => get_option( 'blogdescription' ),
437
						'siteType' => get_option( 'jpo_site_type' ),
438
						'homepageFormat' => get_option( 'jpo_homepage_format' ),
439
						'addContactForm' => intval( get_option( 'jpo_contact_page' ) ),
440
						'businessAddress' => get_option( 'jpo_business_address' ),
441
						'installWooCommerce' => is_plugin_active( 'woocommerce/woocommerce.php' ),
442
					);
443
					break;
444
445
				default:
446
					$response[ $setting ] = Jetpack_Core_Json_Api_Endpoints::cast_value( get_option( $setting ), $settings[ $setting ] );
447
					break;
448
			}
449
		}
450
451
		$response['akismet'] = is_plugin_active( 'akismet/akismet.php' );
452
453
		return rest_ensure_response( $response );
454
	}
455
456
	/**
457
	 * If it's a valid Jetpack module and configuration parameters have been sent, update it.
458
	 *
459
	 * @since 4.3.0
460
	 *
461
	 * @param WP_REST_Request $request {
462
	 *     Array of parameters received by request.
463
	 *
464
	 *     @type string $slug Module slug.
465
	 * }
466
	 *
467
	 * @return bool|WP_Error True if module was updated. Otherwise, a WP_Error instance with the corresponding error.
468
	 */
469
	public function update_data( $request ) {
470
471
		// If it's null, we're trying to update many module options from different modules.
472
		if ( is_null( $request['slug'] ) ) {
473
474
			// Value admitted by Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list that will make it return all module options.
475
			// It will not be passed. It's just checked in this method to pass that method a string or array.
476
			$request['slug'] = 'any';
477
		} else {
478 View Code Duplication
			if ( ! Jetpack::is_module( $request['slug'] ) ) {
479
				return new WP_Error( 'not_found', esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), array( 'status' => 404 ) );
480
			}
481
482 View Code Duplication
			if ( ! Jetpack::is_module_active( $request['slug'] ) ) {
483
				return new WP_Error( 'inactive', esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ), array( 'status' => 409 ) );
484
			}
485
		}
486
487
		// Get parameters to update the module. We can not simply use $request->get_params() because when we registered
488
		// this route, we are adding the entire output of Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list() to
489
		// the current request object's params. We are interested in body of the actual request.
490
		// This may be JSON:
491
		$params = $request->get_json_params();
492
		if ( ! is_array( $params ) ) {
493
			// Or it may be standard POST key-value pairs:
494
			$params = $request->get_body_params();
495
		}
496
497
		// Exit if no parameters were passed.
498
		if ( ! is_array( $params ) ) {
499
			return new WP_Error( 'missing_options', esc_html__( 'Missing options.', 'jetpack' ), array( 'status' => 404 ) );
500
		}
501
502
		// If $params was set via `get_body_params()` there may be some additional variables in the request that can
503
		// cause validation to fail. This method verifies that each param was in fact updated and will throw a `some_updated`
504
		// error if unused variables are included in the request.
505
		foreach ( array_keys( $params ) as $key ) {
506
			if ( is_int( $key ) || 'slug' === $key || 'context' === $key ) {
507
				unset( $params[ $key ] );
508
			}
509
		}
510
511
		// Get available module options.
512
		$options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'any' === $request['slug']
513
			? $params
514
			: $request['slug']
515
		);
516
517
		// Prepare to toggle module if needed
518
		$toggle_module = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
519
520
		// Options that are invalid or failed to update.
521
		$invalid = array_keys( array_diff_key( $params, $options ) );
522
		$not_updated = array();
523
524
		// Remove invalid options
525
		$params = array_intersect_key( $params, $options );
526
527
		// Used if response is successful. The message can be overwritten and additional data can be added here.
528
		$response = array(
529
			'code'	  => 'success',
530
			'message' => esc_html__( 'The requested Jetpack data updates were successful.', 'jetpack' ),
531
		);
532
533
		// If there are modules to activate, activate them first so they're ready when their options are set.
534
		foreach ( $params as $option => $value ) {
535
			if ( 'modules' === $options[ $option ]['jp_group'] ) {
536
537
				// Used if there was an error. Can be overwritten with specific error messages.
538
				$error = '';
539
540
				// Set to true if the module toggling was successful.
541
				$updated = false;
542
543
				// Check if user can toggle the module.
544
				if ( $toggle_module->can_request() ) {
545
546
					// Activate or deactivate the module according to the value passed.
547
					$toggle_result = $value
548
						? $toggle_module->activate_module( $option )
549
						: $toggle_module->deactivate_module( $option );
550
551
					if (
552
						is_wp_error( $toggle_result )
553
						&& 'already_inactive' === $toggle_result->get_error_code()
554
					) {
555
556
						// If the module is already inactive, we don't fail
557
						$updated = true;
558
					} elseif ( is_wp_error( $toggle_result ) ) {
559
						$error = $toggle_result->get_error_message();
560
					} else {
561
						$updated = true;
562
					}
563
				} else {
564
					$error = Jetpack_Core_Json_Api_Endpoints::$user_permissions_error_msg;
565
				}
566
567
				// The module was not toggled.
568
				if ( ! $updated ) {
569
					$not_updated[ $option ] = $error;
570
				}
571
572
				// Remove module from list so we don't go through it again.
573
				unset( $params[ $option ] );
574
			}
575
		}
576
577
		foreach ( $params as $option => $value ) {
578
579
			// Used if there was an error. Can be overwritten with specific error messages.
580
			$error = '';
581
582
			// Set to true if the option update was successful.
583
			$updated = false;
584
585
			// Get option attributes, including the group it belongs to.
586
			$option_attrs = $options[ $option ];
587
588
			// If this is a module option and the related module isn't active for any reason, continue with the next one.
589
			if ( 'settings' !== $option_attrs['jp_group'] ) {
590 View Code Duplication
				if ( ! Jetpack::is_module( $option_attrs['jp_group'] ) ) {
591
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module was not found.', 'jetpack' );
592
					continue;
593
				}
594
595 View Code Duplication
				if (
596
					'any' !== $request['slug']
597
					&& ! Jetpack::is_module_active( $option_attrs['jp_group'] )
598
				) {
599
600
					// We only take note of skipped options when updating one module
601
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' );
602
					continue;
603
				}
604
			}
605
606
			// Properly cast value based on its type defined in endpoint accepted args.
607
			$value = Jetpack_Core_Json_Api_Endpoints::cast_value( $value, $option_attrs );
608
609
			switch ( $option ) {
610
				case 'monitor_receive_notifications':
611
					$monitor = new Jetpack_Monitor();
612
613
					// If we got true as response, consider it done.
614
					$updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value );
615
					break;
616
617
				case 'post_by_email_address':
618
					if ( 'create' == $value ) {
619
						$result = $this->_process_post_by_email(
620
							'jetpack.createPostByEmailAddress',
621
							esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' )
622
						);
623
					} elseif ( 'regenerate' == $value ) {
624
						$result = $this->_process_post_by_email(
625
							'jetpack.regeneratePostByEmailAddress',
626
							esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' )
627
						);
628
					} elseif ( 'delete' == $value ) {
629
						$result = $this->_process_post_by_email(
630
							'jetpack.deletePostByEmailAddress',
631
							esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' )
632
						);
633
					} else {
634
						$result = false;
635
					}
636
637
					// If we got an email address (create or regenerate) or 1 (delete), consider it done.
638
					if ( is_string( $result ) && preg_match( '/[a-z0-9][email protected]/', $result ) ) {
639
						$response[$option] = $result;
640
						$updated           = true;
641
					} elseif ( 1 == $result ) {
642
						$updated = true;
643
					} elseif ( is_array( $result ) && isset( $result['message'] ) ) {
644
						$error = $result['message'];
645
					}
646
					break;
647
648
				case 'jetpack_protect_key':
649
					$protect = Jetpack_Protect_Module::instance();
650
					if ( 'create' == $value ) {
651
						$result = $protect->get_protect_key();
652
					} else {
653
						$result = false;
654
					}
655
656
					// If we got one of Protect keys, consider it done.
657
					if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) {
658
						$response[$option] = $result;
659
						$updated           = true;
660
					}
661
					break;
662
663
				case 'jetpack_protect_global_whitelist':
664
					$updated = jetpack_protect_save_whitelist( explode( PHP_EOL, str_replace( array( ' ', ',' ), array( '', "\n" ), $value ) ) );
665
					if ( is_wp_error( $updated ) ) {
666
						$error = $updated->get_error_message();
667
					}
668
					break;
669
670
				case 'show_headline':
671
				case 'show_thumbnails':
672
					$grouped_options          = $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' );
673
					$grouped_options[$option] = $value;
674
675
					// If option value was the same, consider it done.
676
					$updated = $grouped_options_current != $grouped_options ? Jetpack_Options::update_option( 'relatedposts', $grouped_options ) : true;
677
					break;
678
679
				case 'google':
680
				case 'bing':
681
				case 'pinterest':
682 View Code Duplication
				case 'yandex':
683
					$grouped_options          = $grouped_options_current = (array) get_option( 'verification_services_codes' );
684
					$grouped_options[$option] = $value;
685
686
					// If option value was the same, consider it done.
687
					$updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true;
688
					break;
689
690
				case 'sharing_services':
691
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
692
						break;
693
					}
694
695
					$sharer = new Sharing_Service();
696
697
					// If option value was the same, consider it done.
698
					$updated = $value != $sharer->get_blog_services() ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) : true;
699
					break;
700
701
				case 'button_style':
702
				case 'sharing_label':
703
				case 'show':
704
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
705
						break;
706
					}
707
708
					$sharer = new Sharing_Service();
709
					$grouped_options = $sharer->get_global_options();
710
					$grouped_options[ $option ] = $value;
711
					$updated = $sharer->set_global_options( $grouped_options );
712
					break;
713
714
				case 'custom':
715
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
716
						break;
717
					}
718
719
					$sharer = new Sharing_Service();
720
					$updated = $sharer->new_service( stripslashes( $value['sharing_name'] ), stripslashes( $value['sharing_url'] ), stripslashes( $value['sharing_icon'] ) );
721
722
					// Return new custom service
723
					$response[$option] = $updated;
724
					break;
725
726
				case 'sharing_delete_service':
727
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
728
						break;
729
					}
730
731
					$sharer = new Sharing_Service();
732
					$updated = $sharer->delete_service( $value );
733
					break;
734
735
				case 'jetpack-twitter-cards-site-tag':
736
					$value   = trim( ltrim( strip_tags( $value ), '@' ) );
737
					$updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true;
738
					break;
739
740
				case 'onpublish':
741
				case 'onupdate':
742
				case 'Bias Language':
743
				case 'Cliches':
744
				case 'Complex Expression':
745
				case 'Diacritical Marks':
746
				case 'Double Negative':
747
				case 'Hidden Verbs':
748
				case 'Jargon Language':
749
				case 'Passive voice':
750
				case 'Phrases to Avoid':
751
				case 'Redundant Expression':
752
				case 'guess_lang':
753
					if ( in_array( $option, array( 'onpublish', 'onupdate' ) ) ) {
754
						$atd_option = 'AtD_check_when';
755
					} elseif ( 'guess_lang' == $option ) {
756
						$atd_option = 'AtD_guess_lang';
757
						$option     = 'true';
758
					} else {
759
						$atd_option = 'AtD_options';
760
					}
761
					$user_id                 = get_current_user_id();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 17 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
762
					if ( ! function_exists( 'AtD_get_options' ) ) {
763
						include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' );
764
					}
765
					$grouped_options_current = AtD_get_options( $user_id, $atd_option );
766
					unset( $grouped_options_current['name'] );
767
					$grouped_options = $grouped_options_current;
768
					if ( $value && ! isset( $grouped_options [$option] ) ) {
769
						$grouped_options [$option] = $value;
770
					} elseif ( ! $value && isset( $grouped_options [$option] ) ) {
771
						unset( $grouped_options [$option] );
772
					}
773
					// If option value was the same, consider it done, otherwise try to update it.
774
					$options_to_save = implode( ',', array_keys( $grouped_options ) );
775
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $options_to_save ) : true;
776
					break;
777
778
				case 'ignored_phrases':
779
				case 'unignore_phrase':
780
					$user_id         = get_current_user_id();
781
					$atd_option      = 'AtD_ignored_phrases';
782
					$grouped_options = $grouped_options_current = explode( ',', AtD_get_setting( $user_id, $atd_option ) );
783
					if ( 'ignored_phrases' == $option ) {
784
						$grouped_options = explode( ',', $value );
785
					} else {
786
						$index = array_search( $value, $grouped_options );
787
						if ( false !== $index ) {
788
							unset( $grouped_options[$index] );
789
							$grouped_options = array_values( $grouped_options );
790
						}
791
					}
792
					$ignored_phrases = implode( ',', array_filter( array_map( 'strip_tags', $grouped_options ) ) );
793
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $ignored_phrases ) : true;
794
					break;
795
796
				case 'admin_bar':
797
				case 'roles':
798
				case 'count_roles':
799
				case 'blog_id':
800
				case 'do_not_track':
801
				case 'hide_smile':
802 View Code Duplication
				case 'version':
803
					$grouped_options          = $grouped_options_current = (array) get_option( 'stats_options' );
804
					$grouped_options[$option] = $value;
805
806
					// If option value was the same, consider it done.
807
					$updated = $grouped_options_current != $grouped_options ? update_option( 'stats_options', $grouped_options ) : true;
808
					break;
809
810
				case Jetpack_Core_Json_Api_Endpoints::holiday_snow_option_name():
811
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? 'letitsnow' : '' ) : true;
812
					break;
813
814
				case 'akismet_show_user_comments_approved':
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...
815
816
					// Save Akismet option '1' or '0' like it's done in akismet/class.akismet-admin.php
817
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? '1' : '0' ) : true;
818
					break;
819
820
				case 'wordpress_api_key':
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...
821
822
					if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
823
						$error = esc_html__( 'Please install Akismet.', 'jetpack' );
824
						$updated = false;
825
						break;
826
					}
827
828
					if ( ! defined( 'AKISMET_VERSION' ) ) {
829
						$error = esc_html__( 'Please activate Akismet.', 'jetpack' );
830
						$updated = false;
831
						break;
832
					}
833
834
					// Allow to clear the API key field
835
					if ( '' === $value ) {
836
						$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
837
						break;
838
					}
839
840
					require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
841
					require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
842
843
					if ( class_exists( 'Akismet_Admin' ) && method_exists( 'Akismet_Admin', 'save_key' ) ) {
844
						if ( Akismet::verify_key( $value ) === 'valid' ) {
845
							$akismet_user = Akismet_Admin::get_akismet_user( $value );
846
							if ( $akismet_user ) {
847
								if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) ) ) {
848
									$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
849
									break;
850
								} else {
851
									$error = esc_html__( "Akismet user status doesn't allow to update the key", 'jetpack' );
852
								}
853
							} else {
854
								$error = esc_html__( 'Invalid Akismet user', 'jetpack' );
855
							}
856
						} else {
857
							$error = esc_html__( 'Invalid Akismet key', 'jetpack' );
858
						}
859
					} else {
860
						$error = esc_html__( 'Akismet is not installed or active', 'jetpack' );
861
					}
862
					$updated = false;
863
					break;
864
865 View Code Duplication
				case 'google_analytics_tracking_id':
866
					$grouped_options = $grouped_options_current = (array) get_option( 'jetpack_wga' );
867
					$grouped_options[ 'code' ] = $value;
868
869
					// If option value was the same, consider it done.
870
					$updated = $grouped_options_current != $grouped_options ? update_option( 'jetpack_wga', $grouped_options ) : true;
871
					break;
872
873
				case 'dismiss_dash_app_card':
874 View Code Duplication
				case 'dismiss_empty_stats_card':
875
					// If option value was the same, consider it done.
876
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ) : true;
877
					break;
878
879
				case 'onboarding':
880
					jetpack_require_lib( 'widgets' );
881
					// Break apart and set Jetpack onboarding options.
882
					$result = $this->_process_onboarding( (array) $value );
883
					if ( empty( $result ) ) {
884
						$updated = true;
885
					} else {
886
						$error = sprintf( esc_html__( 'Onboarding failed to process: %s', 'jetpack' ), $result );
887
						$updated = false;
888
					}
889
					break;
890
891 View Code Duplication
				case 'show_welcome_for_new_plan':
892
					// If option value was the same, consider it done.
893
					$updated = get_option( $option ) !== $value ? update_option( $option, (bool) $value ) : true;
894
					break;
895
896 View Code Duplication
				default:
897
					// If option value was the same, consider it done.
898
					$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
899
					break;
900
			}
901
902
			// The option was not updated.
903
			if ( ! $updated ) {
904
				$not_updated[ $option ] = $error;
905
			}
906
		}
907
908
		if ( empty( $invalid ) && empty( $not_updated ) ) {
909
			// The option was updated.
910
			return rest_ensure_response( $response );
911
		} else {
912
			$invalid_count = count( $invalid );
913
			$not_updated_count = count( $not_updated );
914
			$error = '';
915
			if ( $invalid_count > 0 ) {
916
				$error = sprintf(
917
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
918
					_n( 'Invalid option: %s.', 'Invalid options: %s.', $invalid_count, 'jetpack' ),
919
					join( ', ', $invalid )
920
				);
921
			}
922
			if ( $not_updated_count > 0 ) {
923
				$not_updated_messages = array();
924
				foreach ( $not_updated as $not_updated_option => $not_updated_message ) {
925
					if ( ! empty( $not_updated_message ) ) {
926
						$not_updated_messages[] = sprintf(
927
							/* Translators: the first variable is a module option or slug, or setting. The second is the error message . */
928
							__( '%1$s: %2$s', 'jetpack' ),
929
							$not_updated_option, $not_updated_message );
930
					}
931
				}
932
				if ( ! empty( $error ) ) {
933
					$error .= ' ';
934
				}
935
				if ( ! empty( $not_updated_messages ) ) {
936
					$error .= ' ' . join( '. ', $not_updated_messages );
937
				}
938
939
			}
940
			// There was an error because some options were updated but others were invalid or failed to update.
941
			return new WP_Error( 'some_updated', esc_html( $error ), array( 'status' => 400 ) );
942
		}
943
944
	}
945
946
	/**
947
	 * Perform tasks in the site based on onboarding choices.
948
	 *
949
	 * @since 5.4.0
950
	 *
951
	 * @param array $data Onboarding choices made by user.
952
	 *
953
	 * @return string Result of onboarding processing and, if there is one, an error message.
954
	 */
955
	private function _process_onboarding( $data ) {
956
		if ( isset( $data['end'] ) && $data['end'] ) {
957
			return Jetpack::invalidate_onboarding_token()
958
				? ''
959
				: esc_html__( "The onboarding token couldn't be deleted.", 'jetpack' );
960
		}
961
962
		$error = array();
963
964
		if ( ! empty( $data['siteTitle'] ) ) {
965
			// If option value was the same, consider it done.
966
			if ( ! ( update_option( 'blogname', $data['siteTitle'] ) || get_option( 'blogname' ) == $data['siteTitle'] ) ) {
967
				$error[] = 'siteTitle';
968
			}
969
		}
970
971
		if ( ! empty( $data['siteDescription'] ) ) {
972
			// If option value was the same, consider it done.
973
			if ( ! ( update_option( 'blogdescription', $data['siteDescription'] ) || get_option( 'blogdescription' ) == $data['siteDescription'] ) ) {
974
				$error[] = 'siteDescription';
975
			}
976
		}
977
978
		$site_title = get_option( 'blogname' );
979
		$author = get_current_user_id() || 1;
980
981
		if ( ! empty( $data['siteType'] ) ) {
982
			if ( ! ( update_option( 'jpo_site_type', $data['siteType'] ) || get_option( 'jpo_site_type' ) == $data['siteType'] ) ) {
983
				$error[] = 'siteType';
984
			}
985
		}
986
987
		if ( isset( $data['homepageFormat'] ) ) {
988
			// If $data['homepageFormat'] is 'posts', we have nothing to do since it's WordPress' default
989
			if ( 'page' === $data['homepageFormat'] ) {
990
				if ( ! ( update_option( 'show_on_front', 'page' ) || get_option( 'show_on_front' ) == 'page' ) ) {
991
					$error[] = 'homepageFormat';
992
				}
993
994
				$home = wp_insert_post( array(
995
					'post_type'     => 'page',
996
					/* translators: this references the home page of a site, also called front page. */
997
					'post_title'    => esc_html_x( 'Home Page', 'The home page of a website.', 'jetpack' ),
998
					'post_content'  => sprintf( esc_html__( 'Welcome to %s.', 'jetpack' ), $site_title ),
999
					'post_status'   => 'publish',
1000
					'post_author'   => $author,
1001
				) );
1002 View Code Duplication
				if ( 0 == $home ) {
1003
					$error[] = 'home insert: 0';
1004
				} elseif ( is_wp_error( $home ) ) {
1005
					$error[] = 'home creation: '. $home->get_error_message();
1006
				}
1007
				if ( ! ( update_option( 'page_on_front', $home ) || get_option( 'page_on_front' ) == $home ) ) {
1008
					$error[] = 'home set';
1009
				}
1010
1011
				$blog = wp_insert_post( array(
1012
					'post_type'     => 'page',
1013
					/* translators: this references the page where blog posts are listed. */
1014
					'post_title'    => esc_html_x( 'Blog', 'The blog of a website.', 'jetpack' ),
1015
					'post_content'  => sprintf( esc_html__( 'These are the latest posts in %s.', 'jetpack' ), $site_title ),
1016
					'post_status'   => 'publish',
1017
					'post_author'   => $author,
1018
				) );
1019 View Code Duplication
				if ( 0 == $blog ) {
1020
					$error[] = 'blog insert: 0';
1021
				} elseif ( is_wp_error( $blog ) ) {
1022
					$error[] = 'blog creation: '. $blog->get_error_message();
1023
				}
1024
				if ( ! ( update_option( 'page_for_posts', $blog ) || get_option( 'page_for_posts' ) == $blog ) ) {
1025
					$error[] = 'blog set';
1026
				}
1027
			}
1028
1029
			update_option( 'jpo_homepage_format', $data['homepageFormat'] );
1030
		}
1031
1032
		// Setup contact page and add a form and/or business info
1033
		$contact_page = '';
1034
1035
		if ( isset( $data['addContactForm'] ) && $data['addContactForm'] ) {
1036
			$contact_form_module_active = Jetpack::is_module_active( 'contact-form' );
1037
			if ( ! $contact_form_module_active ) {
1038
				$contact_form_module_active = Jetpack::activate_module( 'contact-form', false, false );
1039
			}
1040
1041
			if ( $contact_form_module_active ) {
1042
				$contact_page = '[contact-form][contact-field label="' . esc_html__( 'Name', 'jetpack' ) . '" type="name" required="true" /][contact-field label="' . esc_html__( 'Email', 'jetpack' ) . '" type="email" required="true" /][contact-field label="' . esc_html__( 'Website', 'jetpack' ) . '" type="url" /][contact-field label="' . esc_html__( 'Message', 'jetpack' ) . '" type="textarea" /][/contact-form]';
1043
			} else {
1044
				$error[] = 'contact-form activate';
1045
			}
1046
		}
1047
1048
		if ( isset( $data['businessPersonal'] ) && 'business' === $data['businessPersonal'] ) {
1049
			$contact_page .= "\n" . join( "\n", $data['businessInfo'] );
1050
		}
1051
1052
		if ( ! empty( $contact_page ) ) {
1053
			$form = wp_insert_post( array(
1054
				'post_type'     => 'page',
1055
				/* translators: this references a page with contact details and possibly a form. */
1056
				'post_title'    => esc_html_x( 'Contact us', 'Contact page for your website.', 'jetpack' ),
1057
				'post_content'  => esc_html__( 'Send us a message!', 'jetpack' ) . "\n" . $contact_page,
1058
				'post_status'   => 'publish',
1059
				'post_author'   => $author,
1060
			) );
1061
			if ( 0 == $form ) {
1062
				$error[] = 'form insert: 0';
1063
			} elseif ( is_wp_error( $form ) ) {
1064
				$error[] = 'form creation: '. $form->get_error_message();
1065
			} else {
1066
				update_option( 'jpo_contact_page', $form );
1067
			}
1068
		}
1069
1070
		if ( isset( $data['businessAddress'] ) ) {
1071
			$handled_business_address = self::handle_business_address( $data['businessAddress'] );
1072
			if ( is_wp_error( $handled_business_address ) ) {
1073
				$error[] = 'BusinessAddress';
1074
			}
1075
		}
1076
1077
		if ( ! empty( $data['installWooCommerce'] ) ) {
1078
			jetpack_require_lib( 'plugins' );
1079
			$wc_install_result = Jetpack_Plugins::install_and_activate_plugin( 'woocommerce' );
1080
			if ( is_wp_error( $wc_install_result ) ) {
1081
				$error[] = 'woocommerce installation';
1082
			}
1083
		}
1084
1085
		return empty( $error )
1086
			? ''
1087
			: join( ', ', $error );
1088
	}
1089
1090
	/**
1091
	 * Add or update Business Address widget.
1092
	 *
1093
	 * @param array $address Array of business address fields.
1094
	 *
1095
	 * @return WP_Error|true True if the data was saved correctly.
1096
	*/
1097
	static function handle_business_address( $address ) {
1098
		$first_sidebar = Jetpack_Widgets::get_first_sidebar();
1099
1100
		$widgets_module_active = Jetpack::is_module_active( 'widgets' );
1101
		if ( ! $widgets_module_active ) {
1102
			$widgets_module_active = Jetpack::activate_module( 'widgets', false, false );
1103
		}
1104
		if ( ! $widgets_module_active ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $widgets_module_active of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1105
			return new WP_Error( 'module_activation_failed', 'Failed to activate the widgets module.', 400 );
1106
		}
1107
1108
		if ( $first_sidebar ) {
1109
			$title = isset( $address['name'] ) ? sanitize_text_field( $address['name'] ) : '';
1110
			$street = isset( $address['street'] ) ? sanitize_text_field( $address['street'] ) : '';
1111
			$city = isset( $address['city'] ) ? sanitize_text_field( $address['city'] ) : '';
1112
			$state = isset( $address['state'] ) ? sanitize_text_field( $address['state'] ) : '';
1113
			$zip = isset( $address['zip'] ) ? sanitize_text_field( $address['zip'] ) : '';
1114
1115
			$full_address = implode( ' ', array_filter( array( $street, $city, $state, $zip ) ) );
1116
1117
			$widget_options = array(
1118
				'title'   => $title,
1119
				'address' => $full_address,
1120
				'phone'   => '',
1121
				'hours'   => '',
1122
				'showmap' => false,
1123
				'email' => ''
1124
			);
1125
1126
			$widget_updated = '';
0 ignored issues
show
Unused Code introduced by
$widget_updated 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...
1127
			if ( ! self::has_business_address_widget( $first_sidebar ) ) {
1128
				$widget_updated  = Jetpack_Widgets::insert_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
1129
			} else {
1130
				$widget_updated = Jetpack_Widgets::update_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar );
1131
			}
1132
			if ( is_wp_error( $widget_updated ) ) {
1133
				return new WP_Error( 'widget_update_failed', 'Widget could not be updated.', 400 );
1134
			}
1135
1136
			$address_save = array(
1137
				'name' => $title,
1138
				'street' => $street,
1139
				'city' => $city,
1140
				'state' => $state,
1141
				'zip' => $zip
1142
			);
1143
			update_option( 'jpo_business_address', $address_save );
1144
			return true;
1145
		}
1146
1147
		// No sidebar to place the widget
1148
		return new WP_Error( 'sidebar_not_found', 'No sidebar.', 400 );
1149
	}
1150
1151
	/**
1152
	 * Check whether "Contact Info & Map" widget is present in a given sidebar.
1153
	 *
1154
	 * @param string  $sidebar ID of the sidebar to which the widget will be added.
1155
	 *
1156
	 * @return bool Whether the widget is present in a given sidebar.
1157
	*/
1158
	static function has_business_address_widget( $sidebar ) {
1159
		$sidebars_widgets = get_option( 'sidebars_widgets', array() );
1160
		if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) {
1161
			return false;
1162
		}
1163
		foreach ( $sidebars_widgets[ $sidebar ] as $widget ) {
1164
			if ( strpos( $widget, 'widget_contact_info' ) !== false ) {
1165
				return true;
1166
			}
1167
		}
1168
		return false;
1169
	}
1170
1171
	/**
1172
	 * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
1173
	 * @todo: When all settings are updated to use endpoints, move this to the Post by Email module and replace __process_ajax_proxy_request.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
1174
	 *
1175
	 * @since 4.3.0
1176
	 *
1177
	 * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
1178
	 * @param string $error	   Error message to return.
1179
	 *
1180
	 * @return array
1181
	 */
1182
	private function _process_post_by_email( $endpoint, $error ) {
1183
		if ( ! current_user_can( 'edit_posts' ) ) {
1184
			return array( 'message' => $error );
1185
		}
1186
1187
		$this->xmlrpc->query( $endpoint );
1188
1189
		if ( $this->xmlrpc->isError() ) {
1190
			return array( 'message' => $error );
1191
		}
1192
1193
		$response = $this->xmlrpc->getResponse();
1194
		if ( empty( $response ) ) {
1195
			return array( 'message' => $error );
1196
		}
1197
1198
		// Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
1199
		update_option( 'post_by_email_address' . get_current_user_id(), $response );
1200
1201
		return $response;
1202
	}
1203
1204
	/**
1205
	 * Check if user is allowed to perform the update.
1206
	 *
1207
	 * @since 4.3.0
1208
	 *
1209
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1210
	 *
1211
	 * @return bool
1212
	 */
1213
	public function can_request( $request ) {
1214
		$req_params = $request->get_params();
1215
		if ( ! empty( $req_params['onboarding']['token'] ) && isset( $req_params['rest_route'] ) ) {
1216
			return Jetpack::validate_onboarding_token_action( $req_params['onboarding']['token'], $req_params['rest_route'] );
1217
		}
1218
1219
		if ( 'GET' === $request->get_method() ) {
1220
			return current_user_can( 'jetpack_admin_page' );
1221
		} else {
1222
			$module = Jetpack_Core_Json_Api_Endpoints::get_module_requested();
1223
			if ( empty( $module ) ) {
1224
				$params = $request->get_json_params();
1225
				if ( ! is_array( $params ) ) {
1226
					$params = $request->get_body_params();
1227
				}
1228
				$options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( $params );
1229
				foreach ( $options as $option => $definition ) {
1230
					if ( in_array( $options[ $option ]['jp_group'], array( 'after-the-deadline', 'post-by-email' ) ) ) {
1231
						$module = $options[ $option ]['jp_group'];
1232
						break;
1233
					}
1234
				}
1235
			}
1236
			// User is trying to create, regenerate or delete its PbE || ATD settings.
1237
			if ( 'post-by-email' === $module || 'after-the-deadline' === $module ) {
1238
				return current_user_can( 'edit_posts' ) && current_user_can( 'jetpack_admin_page' );
1239
			}
1240
			return current_user_can( 'jetpack_configure_modules' );
1241
		}
1242
	}
1243
}
1244
1245
class Jetpack_Core_API_Module_Data_Endpoint {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1246
1247
	public function process( $request ) {
1248
		switch( $request['slug'] ) {
1249
			case 'protect':
1250
				return $this->get_protect_data();
1251
			case 'stats':
1252
				return $this->get_stats_data( $request );
1253
			case 'akismet':
1254
				return $this->get_akismet_data();
1255
			case 'monitor':
1256
				return $this->get_monitor_data();
1257
			case 'verification-tools':
1258
				return $this->get_verification_tools_data();
1259
			case 'vaultpress':
1260
				return $this->get_vaultpress_data();
1261
		}
1262
	}
1263
1264
	/**
1265
	 * Decide against which service to check the key.
1266
	 *
1267
	 * @since 4.8.0
1268
	 *
1269
	 * @param WP_REST_Request $request
1270
	 *
1271
	 * @return bool
1272
	 */
1273
	public function key_check( $request ) {
1274
		switch( $request['service'] ) {
1275
			case 'akismet':
1276
				$params = $request->get_json_params();
1277
				if ( isset( $params['api_key'] ) && ! empty( $params['api_key'] ) ) {
1278
					return $this->check_akismet_key( $params['api_key'] );
1279
				}
1280
				return $this->check_akismet_key();
1281
		}
1282
		return false;
1283
	}
1284
1285
	/**
1286
	 * Get number of blocked intrusion attempts.
1287
	 *
1288
	 * @since 4.3.0
1289
	 *
1290
	 * @return mixed|WP_Error Number of blocked attempts if protection is enabled. Otherwise, a WP_Error instance with the corresponding error.
1291
	 */
1292
	public function get_protect_data() {
1293
		if ( Jetpack::is_module_active( 'protect' ) ) {
1294
			return get_site_option( 'jetpack_protect_blocked_attempts' );
1295
		}
1296
1297
		return new WP_Error(
1298
			'not_active',
1299
			esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1300
			array( 'status' => 404 )
1301
		);
1302
	}
1303
1304
	/**
1305
	 * Get number of spam messages blocked by Akismet.
1306
	 *
1307
	 * @since 4.3.0
1308
	 *
1309
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
1310
	 */
1311
	public function get_akismet_data() {
1312
		if ( ! is_wp_error( $status = $this->akismet_is_active_and_registered() ) ) {
1313
			return rest_ensure_response( Akismet_Admin::get_stats( Akismet::get_api_key() ) );
1314
		} else {
1315
			return $status->get_error_code();
1316
		}
1317
	}
1318
1319
	/**
1320
	 * Verify the Akismet API key.
1321
	 *
1322
	 * @since 4.8.0
1323
	 *
1324
	 * @param string $api_key Optional API key to check.
1325
	 *
1326
	 * @return array Information about the key. 'validKey' is true if key is valid, false otherwise.
1327
	 */
1328
	public function check_akismet_key( $api_key = '' ) {
1329
		$akismet_status = $this->akismet_class_exists();
1330
		if ( is_wp_error( $akismet_status ) ) {
1331
			return rest_ensure_response( array(
1332
				'validKey'          => false,
1333
				'invalidKeyCode'    => $akismet_status->get_error_code(),
1334
				'invalidKeyMessage' => $akismet_status->get_error_message(),
1335
			) );
1336
		}
1337
1338
		$key_status = Akismet::check_key_status( empty( $api_key ) ? Akismet::get_api_key() : $api_key );
1339
1340 View Code Duplication
		if ( ! $key_status || 'invalid' === $key_status || 'failed' === $key_status ) {
1341
			return rest_ensure_response( array(
1342
				'validKey'          => false,
1343
				'invalidKeyCode'    => 'invalid_key',
1344
				'invalidKeyMessage' => esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ),
1345
			) );
1346
		}
1347
1348
		return rest_ensure_response( array(
1349
			'validKey' => isset( $key_status[1] ) && 'valid' === $key_status[1]
1350
		) );
1351
	}
1352
1353
	/**
1354
	 * Check if Akismet class file exists and if class is loaded.
1355
	 *
1356
	 * @since 4.8.0
1357
	 *
1358
	 * @return bool|WP_Error Returns true if class file exists and class is loaded, WP_Error otherwise.
1359
	 */
1360
	private function akismet_class_exists() {
1361
		if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
1362
			return new WP_Error( 'not_installed', esc_html__( 'Please install Akismet.', 'jetpack' ), array( 'status' => 400 ) );
1363
		}
1364
1365 View Code Duplication
		if ( ! class_exists( 'Akismet' ) ) {
1366
			return new WP_Error( 'not_active', esc_html__( 'Please activate Akismet.', 'jetpack' ), array( 'status' => 400 ) );
1367
		}
1368
1369
		return true;
1370
	}
1371
1372
	/**
1373
	 * Is Akismet registered and active?
1374
	 *
1375
	 * @since 4.3.0
1376
	 *
1377
	 * @return bool|WP_Error True if Akismet is active and registered. Otherwise, a WP_Error instance with the corresponding error.
1378
	 */
1379
	private function akismet_is_active_and_registered() {
1380
		if ( is_wp_error( $akismet_exists = $this->akismet_class_exists() ) ) {
1381
			return $akismet_exists;
1382
		}
1383
1384
		// What about if Akismet is put in a sub-directory or maybe in mu-plugins?
1385
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
1386
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
1387
		$akismet_key = Akismet::verify_key( Akismet::get_api_key() );
1388
1389 View Code Duplication
		if ( ! $akismet_key || 'invalid' === $akismet_key || 'failed' === $akismet_key ) {
1390
			return new WP_Error( 'invalid_key', esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), array( 'status' => 400 ) );
1391
		}
1392
1393
		return true;
1394
	}
1395
1396
	/**
1397
	 * Get stats data for this site
1398
	 *
1399
	 * @since 4.1.0
1400
	 *
1401
	 * @param WP_REST_Request $request {
1402
	 *     Array of parameters received by request.
1403
	 *
1404
	 *     @type string $date Date range to restrict results to.
1405
	 * }
1406
	 *
1407
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
1408
	 */
1409
	public function get_stats_data( WP_REST_Request $request ) {
1410
		// Get parameters to fetch Stats data.
1411
		$range = $request->get_param( 'range' );
1412
1413
		// If no parameters were passed.
1414
		if (
1415
			empty ( $range )
1416
			|| ! in_array( $range, array( 'day', 'week', 'month' ), true )
1417
		) {
1418
			$range = 'day';
1419
		}
1420
1421
		if ( ! function_exists( 'stats_get_from_restapi' ) ) {
1422
			require_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
1423
		}
1424
1425
		switch ( $range ) {
1426
1427
			// This is always called first on page load
1428
			case 'day':
1429
				$initial_stats = stats_get_from_restapi();
1430
				return rest_ensure_response( array(
1431
					'general' => $initial_stats,
1432
1433
					// Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' );
1434
					'day' => isset( $initial_stats->visits )
1435
						? $initial_stats->visits
1436
						: array(),
1437
				) );
1438
			case 'week':
1439
				return rest_ensure_response( array(
1440
					'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ),
1441
				) );
1442
			case 'month':
1443
				return rest_ensure_response( array(
1444
					'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ),
1445
				) );
1446
		}
1447
	}
1448
1449
	/**
1450
	 * Get date of last downtime.
1451
	 *
1452
	 * @since 4.3.0
1453
	 *
1454
	 * @return mixed|WP_Error Number of days since last downtime. Otherwise, a WP_Error instance with the corresponding error.
1455
	 */
1456
	public function get_monitor_data() {
1457 View Code Duplication
		if ( ! Jetpack::is_module_active( 'monitor' ) ) {
1458
			return new WP_Error(
1459
				'not_active',
1460
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1461
				array( 'status' => 404 )
1462
			);
1463
		}
1464
1465
		$monitor       = new Jetpack_Monitor();
1466
		$last_downtime = $monitor->monitor_get_last_downtime();
1467
		if ( is_wp_error( $last_downtime ) ) {
1468
			return $last_downtime;
1469
		} else if ( false === strtotime( $last_downtime ) ) {
1470
			return rest_ensure_response( array(
1471
				'code' => 'success',
1472
				'date' => null,
1473
			) );
1474
		} else {
1475
			return rest_ensure_response( array(
1476
				'code' => 'success',
1477
				'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ),
1478
			) );
1479
		}
1480
	}
1481
1482
	/**
1483
	 * Get services that this site is verified with.
1484
	 *
1485
	 * @since 4.3.0
1486
	 *
1487
	 * @return mixed|WP_Error List of services that verified this site. Otherwise, a WP_Error instance with the corresponding error.
1488
	 */
1489
	public function get_verification_tools_data() {
1490 View Code Duplication
		if ( ! Jetpack::is_module_active( 'verification-tools' ) ) {
1491
			return new WP_Error(
1492
				'not_active',
1493
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1494
				array( 'status' => 404 )
1495
			);
1496
		}
1497
1498
		$verification_services_codes = get_option( 'verification_services_codes' );
1499 View Code Duplication
		if (
1500
			! is_array( $verification_services_codes )
1501
			|| empty( $verification_services_codes )
1502
		) {
1503
			return new WP_Error(
1504
				'empty',
1505
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1506
				array( 'status' => 404 )
1507
			);
1508
		}
1509
1510
		$services = array();
1511
		foreach ( jetpack_verification_services() as $name => $service ) {
1512
			if ( is_array( $service ) && ! empty( $verification_services_codes[ $name ] ) ) {
1513
				switch ( $name ) {
1514
					case 'google':
1515
						$services[] = 'Google';
1516
						break;
1517
					case 'bing':
1518
						$services[] = 'Bing';
1519
						break;
1520
					case 'pinterest':
1521
						$services[] = 'Pinterest';
1522
						break;
1523
					case 'yandex':
1524
						$services[] = 'Yandex';
1525
						break;
1526
				}
1527
			}
1528
		}
1529
1530
		if ( empty( $services ) ) {
1531
			return new WP_Error(
1532
				'empty',
1533
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1534
				array( 'status' => 404 )
1535
			);
1536
		}
1537
1538
		if ( 2 > count( $services ) ) {
1539
			$message = esc_html(
1540
				sprintf(
1541
					/* translators: %s is a service name like Google, Bing, Pinterest, etc. */
1542
					__( 'Your site is verified with %s.', 'jetpack' ),
1543
					$services[0]
1544
				)
1545
			);
1546
		} else {
1547
			$copy_services = $services;
1548
			$last = count( $copy_services ) - 1;
1549
			$last_service = $copy_services[ $last ];
1550
			unset( $copy_services[ $last ] );
1551
			$message = esc_html(
1552
				sprintf(
1553
					/* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1554
					__( 'Your site is verified with %1$s and %2$s.', 'jetpack' ),
1555
					join( ', ', $copy_services ),
1556
					$last_service
1557
				)
1558
			);
1559
		}
1560
1561
		return rest_ensure_response( array(
1562
			'code'     => 'success',
1563
			'message'  => $message,
1564
			'services' => $services,
1565
		) );
1566
	}
1567
1568
	/**
1569
	 * Get VaultPress site data including, among other things, the date of the last backup if it was completed.
1570
	 *
1571
	 * @since 4.3.0
1572
	 *
1573
	 * @return mixed|WP_Error VaultPress site data. Otherwise, a WP_Error instance with the corresponding error.
1574
	 */
1575
	public function get_vaultpress_data() {
1576 View Code Duplication
		if ( ! class_exists( 'VaultPress' ) ) {
1577
			return new WP_Error(
1578
				'not_active',
1579
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1580
				array( 'status' => 404 )
1581
			);
1582
		}
1583
1584
		$vaultpress = new VaultPress();
1585
		if ( ! $vaultpress->is_registered() ) {
1586
			return rest_ensure_response( array(
1587
				'code'    => 'not_registered',
1588
				'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' )
1589
			) );
1590
		}
1591
1592
		$data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) );
1593
		if ( false == $data ) {
1594
			return rest_ensure_response( array(
1595
				'code'    => 'not_registered',
1596
				'message' => esc_html__( 'Could not connect to VaultPress.', 'jetpack' )
1597
			) );
1598
		} else if ( is_wp_error( $data ) || ! isset( $data->backups->last_backup ) ) {
1599
			return $data;
1600
		} else if ( empty( $data->backups->last_backup ) ) {
1601
			return rest_ensure_response( array(
1602
				'code'    => 'success',
1603
				'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ),
1604
				'data'    => $data,
1605
			) );
1606
		} else {
1607
			return rest_ensure_response( array(
1608
				'code'    => 'success',
1609
				'message' => esc_html(
1610
					sprintf(
1611
						__( 'Your site was successfully backed-up %s ago.', 'jetpack' ),
1612
						human_time_diff(
1613
							$data->backups->last_backup,
1614
							current_time( 'timestamp' )
1615
						)
1616
					)
1617
				),
1618
				'data'    => $data,
1619
			) );
1620
		}
1621
	}
1622
1623
	/**
1624
	 * A WordPress REST API permission callback method that accepts a request object and
1625
	 * decides if the current user has enough privileges to act.
1626
	 *
1627
	 * @since 4.3.0
1628
	 *
1629
	 * @return bool does a current user have enough privileges.
1630
	 */
1631
	public function can_request() {
1632
		return current_user_can( 'jetpack_admin_page' );
1633
	}
1634
}
1635
1636
/**
1637
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1638
 *
1639
 * @since 4.3.1
1640
 */
1641
function jetpack_do_after_gravatar_hovercards_activation() {
1642
1643
	// When Gravatar Hovercards is activated, enable them automatically.
1644
	update_option( 'gravatar_disable_hovercards', 'enabled' );
1645
}
1646
add_action( 'jetpack_activate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_activation' );
1647
1648
/**
1649
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1650
 *
1651
 * @since 4.3.1
1652
 */
1653
function jetpack_do_after_gravatar_hovercards_deactivation() {
1654
1655
	// When Gravatar Hovercards is deactivated, disable them automatically.
1656
	update_option( 'gravatar_disable_hovercards', 'disabled' );
1657
}
1658
add_action( 'jetpack_deactivate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_deactivation' );
1659
1660
/**
1661
 * Actions performed only when Markdown is activated through the endpoint call.
1662
 *
1663
 * @since 4.7.0
1664
 */
1665
function jetpack_do_after_markdown_activation() {
1666
1667
	// When Markdown is activated, enable support for post editing automatically.
1668
	update_option( 'wpcom_publish_posts_with_markdown', true );
1669
}
1670
add_action( 'jetpack_activate_module_markdown', 'jetpack_do_after_markdown_activation' );
1671