Completed
Push — kraftbj-patch-5 ( c43ab3 )
by
unknown
08:45
created

Jetpack_Core_API_Data   D

Complexity

Total Complexity 251

Size/Duplication

Total Lines 1018
Duplicated Lines 6.58 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 67
loc 1018
rs 4.3898
c 0
b 0
f 0
wmc 251
lcom 1
cbo 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 11 3
C get_module() 0 33 7
D get_all_options() 0 82 19
A decode_special_characters() 0 3 1
F update_data() 46 501 140
F _process_onboarding() 21 163 50
D handle_business_address() 0 55 12
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 View Code Duplication
	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
		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
		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
		if ( ! Jetpack::active_plan_supports( $module_slug ) ) {
71
			return new WP_Error(
72
				'not_supported',
73
				esc_html__( 'The requested Jetpack module is not supported by your plan.', 'jetpack' ),
74
				array( 'status' => 424 )
75
			);
76
		}
77
78
		if ( Jetpack::activate_module( $module_slug, false, false ) ) {
79
			return rest_ensure_response( array(
80
				'code' 	  => 'success',
81
				'message' => esc_html__( 'The requested Jetpack module was activated.', 'jetpack' ),
82
			) );
83
		}
84
85
		return new WP_Error(
86
			'activation_failed',
87
			esc_html__( 'The requested Jetpack module could not be activated.', 'jetpack' ),
88
			array( 'status' => 424 )
89
		);
90
	}
91
92
	/**
93
	 * If it's a valid Jetpack module, deactivate it.
94
	 *
95
	 * @since 4.3.0
96
	 *
97
	 * @param string|WP_REST_Request $request It's a WP_REST_Request when called from endpoint /module/<slug>/*
98
	 *                                        and a string when called from Jetpack_Core_API_Data->update_data.
99
	 * {
100
	 *     Array of parameters received by request.
101
	 *
102
	 *     @type string $slug Module slug.
103
	 * }
104
	 *
105
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
106
	 */
107 View Code Duplication
	public function deactivate_module( $request ) {
108
		$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...
109
110
		if (
111
			(
112
				is_array( $request )
113
				|| is_object( $request )
114
			)
115
			&& isset( $request['slug'] )
116
		) {
117
			$module_slug = $request['slug'];
118
		} else {
119
			$module_slug = $request;
120
		}
121
122
		if ( ! Jetpack::is_module( $module_slug ) ) {
123
			return new WP_Error(
124
				'not_found',
125
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
126
				array( 'status' => 404 )
127
			);
128
		}
129
130
		if ( ! Jetpack::is_module_active( $module_slug ) ) {
131
			return new WP_Error(
132
				'already_inactive',
133
				esc_html__( 'The requested Jetpack module was already inactive.', 'jetpack' ),
134
				array( 'status' => 409 )
135
			);
136
		}
137
138
		if ( Jetpack::deactivate_module( $module_slug ) ) {
139
			return rest_ensure_response( array(
140
				'code' 	  => 'success',
141
				'message' => esc_html__( 'The requested Jetpack module was deactivated.', 'jetpack' ),
142
			) );
143
		}
144
		return new WP_Error(
145
			'deactivation_failed',
146
			esc_html__( 'The requested Jetpack module could not be deactivated.', 'jetpack' ),
147
			array( 'status' => 400 )
148
		);
149
	}
150
151
	/**
152
	 * Check that the current user has permissions to manage Jetpack modules.
153
	 *
154
	 * @since 4.3.0
155
	 *
156
	 * @return bool
157
	 */
158
	public function can_request() {
159
		return current_user_can( 'jetpack_manage_modules' );
160
	}
161
}
162
163
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...
164
165
	/**
166
	 * A WordPress REST API callback method that accepts a request object and decides what to do with it.
167
	 *
168
	 * @param WP_REST_Request $request The request sent to the WP REST API.
169
	 *
170
	 * @since 4.3.0
171
	 *
172
	 * @return bool|Array|WP_Error a resulting value or object, or an error.
173
	 */
174
	public function process( $request ) {
175
		if ( 'GET' === $request->get_method() ) {
176
			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...
177
		} else {
178
			return $this->activate_modules( $request );
179
		}
180
	}
181
182
	/**
183
	 * Get a list of all Jetpack modules and their information.
184
	 *
185
	 * @since 4.3.0
186
	 *
187
	 * @return array Array of Jetpack modules.
188
	 */
189
	public function get_modules() {
190
		require_once( JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php' );
191
192
		$modules = Jetpack_Admin::init()->get_modules();
193
		foreach ( $modules as $slug => $properties ) {
194
			$modules[ $slug ]['options'] =
195
				Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $slug );
196
			if (
197
				isset( $modules[ $slug ]['requires_connection'] )
198
				&& $modules[ $slug ]['requires_connection']
199
				&& Jetpack::is_development_mode()
200
			) {
201
				$modules[ $slug ]['activated'] = false;
202
			}
203
		}
204
205
		$modules = Jetpack::get_translated_modules( $modules );
206
207
		return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $modules );
208
	}
209
210
	/**
211
	 * Activate a list of valid Jetpack modules.
212
	 *
213
	 * @since 4.3.0
214
	 *
215
	 * @param WP_REST_Request $request {
216
	 *     Array of parameters received by request.
217
	 *
218
	 *     @type string $slug Module slug.
219
	 * }
220
	 *
221
	 * @return bool|WP_Error True if modules were activated. Otherwise, a WP_Error instance with the corresponding error.
222
	 */
223
	public static function activate_modules( $request ) {
224
225 View Code Duplication
		if (
226
			! isset( $request['modules'] )
227
			|| ! is_array( $request['modules'] )
228
		) {
229
			return new WP_Error(
230
				'not_found',
231
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
232
				array( 'status' => 404 )
233
			);
234
		}
235
236
		$activated = array();
237
		$failed = array();
238
239
		foreach ( $request['modules'] as $module ) {
240
			if ( Jetpack::activate_module( $module, false, false ) ) {
241
				$activated[] = $module;
242
			} else {
243
				$failed[] = $module;
244
			}
245
		}
246
247 View Code Duplication
		if ( empty( $failed ) ) {
248
			return rest_ensure_response( array(
249
				'code' 	  => 'success',
250
				'message' => esc_html__( 'All modules activated.', 'jetpack' ),
251
			) );
252
		}
253
254
		$error = '';
255
256
		$activated_count = count( $activated );
257 View Code Duplication
		if ( $activated_count > 0 ) {
258
			$activated_last = array_pop( $activated );
259
			$activated_text = $activated_count > 1 ? sprintf(
260
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
261
				__( '%1$s and %2$s', 'jetpack' ),
262
				join( ', ', $activated ), $activated_last ) : $activated_last;
263
264
			$error = sprintf(
265
				/* Translators: the variable is a module name. */
266
				_n( 'The module %s was activated.', 'The modules %s were activated.', $activated_count, 'jetpack' ),
267
				$activated_text ) . ' ';
268
		}
269
270
		$failed_count = count( $failed );
271 View Code Duplication
		if ( count( $failed ) > 0 ) {
272
			$failed_last = array_pop( $failed );
273
			$failed_text = $failed_count > 1 ? sprintf(
274
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
275
				__( '%1$s and %2$s', 'jetpack' ),
276
				join( ', ', $failed ), $failed_last ) : $failed_last;
277
278
			$error = sprintf(
279
				/* Translators: the variable is a module name. */
280
				_n( 'The module %s failed to be activated.', 'The modules %s failed to be activated.', $failed_count, 'jetpack' ),
281
				$failed_text ) . ' ';
282
		}
283
284
		return new WP_Error(
285
			'activation_failed',
286
			esc_html( $error ),
287
			array( 'status' => 424 )
288
		);
289
	}
290
291
	/**
292
	 * A WordPress REST API permission callback method that accepts a request object and decides
293
	 * if the current user has enough privileges to act.
294
	 *
295
	 * @since 4.3.0
296
	 *
297
	 * @param WP_REST_Request $request The request sent to the WP REST API.
298
	 *
299
	 * @return bool does the current user have enough privilege.
300
	 */
301
	public function can_request( $request ) {
302
		if ( 'GET' === $request->get_method() ) {
303
			return current_user_can( 'jetpack_admin_page' );
304
		} else {
305
			return current_user_can( 'jetpack_manage_modules' );
306
		}
307
	}
308
}
309
310
/**
311
 * Class that manages updating of Jetpack module options and general Jetpack settings or retrieving module data.
312
 * If no module is specified, all module settings are retrieved/updated.
313
 *
314
 * @since 4.3.0
315
 * @since 4.4.0 Renamed Jetpack_Core_API_Module_Endpoint from to Jetpack_Core_API_Data.
316
 *
317
 * @author Automattic
318
 */
319
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...
320
321
	/**
322
	 * Process request by returning the module or updating it.
323
	 * If no module is specified, settings for all modules are assumed.
324
	 *
325
	 * @since 4.3.0
326
	 *
327
	 * @param WP_REST_Request $request
328
	 *
329
	 * @return bool|mixed|void|WP_Error
330
	 */
331
	public function process( $request ) {
332
		if ( 'GET' === $request->get_method() ) {
333
			if ( isset( $request['slug'] ) ) {
334
				return $this->get_module( $request );
335
			}
336
337
			return $this->get_all_options();
338
		} else {
339
			return $this->update_data( $request );
340
		}
341
	}
342
343
	/**
344
	 * Get information about a specific and valid Jetpack module.
345
	 *
346
	 * @since 4.3.0
347
	 *
348
	 * @param WP_REST_Request $request {
349
	 *     Array of parameters received by request.
350
	 *
351
	 *     @type string $slug Module slug.
352
	 * }
353
	 *
354
	 * @return mixed|void|WP_Error
355
	 */
356
	public function get_module( $request ) {
357
		if ( Jetpack::is_module( $request['slug'] ) ) {
358
359
			$module = Jetpack::get_module( $request['slug'] );
360
361
			$module['options'] = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $request['slug'] );
362
363
			if (
364
				isset( $module['requires_connection'] )
365
				&& $module['requires_connection']
366
				&& Jetpack::is_development_mode()
367
			) {
368
				$module['activated'] = false;
369
			}
370
371
			$i18n = jetpack_get_module_i18n( $request['slug'] );
372
			if ( isset( $module['name'] ) ) {
373
				$module['name'] = $i18n['name'];
374
			}
375
			if ( isset( $module['description'] ) ) {
376
				$module['description'] = $i18n['description'];
377
				$module['short_description'] = $i18n['description'];
378
			}
379
380
			return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $module );
381
		}
382
383
		return new WP_Error(
384
			'not_found',
385
			esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
386
			array( 'status' => 404 )
387
		);
388
	}
389
390
	/**
391
	 * Get information about all Jetpack module options and settings.
392
	 *
393
	 * @since 4.6.0
394
	 *
395
	 * @return WP_REST_Response $response
396
	 */
397
	public function get_all_options() {
398
		$response = array();
399
400
		$modules = Jetpack::get_available_modules();
401
		if ( is_array( $modules ) && ! empty( $modules ) ) {
402
			foreach ( $modules as $module ) {
403
				// Add all module options
404
				$options = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $module );
405
				foreach ( $options as $option_name => $option ) {
406
					$response[ $option_name ] = $option['current_value'];
407
				}
408
409
				// Add the module activation state
410
				$response[ $module ] = Jetpack::is_module_active( $module );
411
			}
412
		}
413
414
		$settings = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'settings' );
415
416
		if ( ! function_exists( 'is_plugin_active' ) ) {
417
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
418
		}
419
420
		foreach ( $settings as $setting => $properties ) {
421
			switch ( $setting ) {
422
				case 'lang_id':
423
					if ( defined( 'WPLANG' ) ) {
424
						// We can't affect this setting, so warn the client
425
						$response[ $setting ] = 'error_const';
426
						break;
427
					}
428
429
					if ( ! current_user_can( 'install_languages' ) ) {
430
						// The user doesn't have caps to install language packs, so warn the client
431
						$response[ $setting ] = 'error_cap';
432
						break;
433
					}
434
435
					$value = get_option( 'WPLANG' );
436
					$response[ $setting ] = empty( $value ) ? 'en_US' : $value;
437
					break;
438
439
				case 'wordpress_api_key':
440
					// When field is clear, return empty. Otherwise it would return "false".
441
					if ( '' === get_option( 'wordpress_api_key', '' ) ) {
442
						$response[ $setting ] = '';
443
					} else {
444
						if ( ! class_exists( 'Akismet' ) ) {
445
							if ( is_readable( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
446
								require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
447
							}
448
						}
449
						$response[ $setting ] = class_exists( 'Akismet' ) ? Akismet::get_api_key() : '';
450
					}
451
					break;
452
453
				case 'onboarding':
454
					$business_address = get_option( 'jpo_business_address' );
455
					$business_address = is_array( $business_address ) ? array_map( array( $this, 'decode_special_characters' ), $business_address ) : $business_address;
456
457
					$response[ $setting ] = array(
458
						'siteTitle' => $this->decode_special_characters( get_option( 'blogname' ) ),
459
						'siteDescription' => $this->decode_special_characters( get_option( 'blogdescription' ) ),
460
						'siteType' => get_option( 'jpo_site_type' ),
461
						'homepageFormat' => get_option( 'jpo_homepage_format' ),
462
						'addContactForm' => intval( get_option( 'jpo_contact_page' ) ),
463
						'businessAddress' => $business_address,
464
						'installWooCommerce' => is_plugin_active( 'woocommerce/woocommerce.php' ),
465
						'stats' => Jetpack::is_active() && Jetpack::is_module_active( 'stats' ),
466
					);
467
					break;
468
469
				default:
470
					$response[ $setting ] = Jetpack_Core_Json_Api_Endpoints::cast_value( get_option( $setting ), $settings[ $setting ] );
471
					break;
472
			}
473
		}
474
475
		$response['akismet'] = is_plugin_active( 'akismet/akismet.php' );
476
477
		return rest_ensure_response( $response );
478
	}
479
480
	/**
481
	 * Decode the special HTML characters in a certain value.
482
	 *
483
	 * @since 5.8
484
	 *
485
	 * @param string $value Value to decode.
486
	 *
487
	 * @return string Value with decoded HTML characters.
488
	 */
489
	private function decode_special_characters( $value ) {
490
		return (string) htmlspecialchars_decode( $value, ENT_QUOTES );
491
	}
492
493
	/**
494
	 * If it's a valid Jetpack module and configuration parameters have been sent, update it.
495
	 *
496
	 * @since 4.3.0
497
	 *
498
	 * @param WP_REST_Request $request {
499
	 *     Array of parameters received by request.
500
	 *
501
	 *     @type string $slug Module slug.
502
	 * }
503
	 *
504
	 * @return bool|WP_Error True if module was updated. Otherwise, a WP_Error instance with the corresponding error.
505
	 */
506
	public function update_data( $request ) {
507
508
		// If it's null, we're trying to update many module options from different modules.
509
		if ( is_null( $request['slug'] ) ) {
510
511
			// Value admitted by Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list that will make it return all module options.
512
			// It will not be passed. It's just checked in this method to pass that method a string or array.
513
			$request['slug'] = 'any';
514
		} else {
515
			if ( ! Jetpack::is_module( $request['slug'] ) ) {
516
				return new WP_Error( 'not_found', esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), array( 'status' => 404 ) );
517
			}
518
519
			if ( ! Jetpack::is_module_active( $request['slug'] ) ) {
520
				return new WP_Error( 'inactive', esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ), array( 'status' => 409 ) );
521
			}
522
		}
523
524
		// Get parameters to update the module. We can not simply use $request->get_params() because when we registered
525
		// this route, we are adding the entire output of Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list() to
526
		// the current request object's params. We are interested in body of the actual request.
527
		// This may be JSON:
528
		$params = $request->get_json_params();
529
		if ( ! is_array( $params ) ) {
530
			// Or it may be standard POST key-value pairs:
531
			$params = $request->get_body_params();
532
		}
533
534
		// Exit if no parameters were passed.
535
		if ( ! is_array( $params ) ) {
536
			return new WP_Error( 'missing_options', esc_html__( 'Missing options.', 'jetpack' ), array( 'status' => 404 ) );
537
		}
538
539
		// If $params was set via `get_body_params()` there may be some additional variables in the request that can
540
		// cause validation to fail. This method verifies that each param was in fact updated and will throw a `some_updated`
541
		// error if unused variables are included in the request.
542
		foreach ( array_keys( $params ) as $key ) {
543
			if ( is_int( $key ) || 'slug' === $key || 'context' === $key ) {
544
				unset( $params[ $key ] );
545
			}
546
		}
547
548
		// Get available module options.
549
		$options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'any' === $request['slug']
550
			? $params
551
			: $request['slug']
552
		);
553
554
		// Prepare to toggle module if needed
555
		$toggle_module = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
556
557
		// Options that are invalid or failed to update.
558
		$invalid = array_keys( array_diff_key( $params, $options ) );
559
		$not_updated = array();
560
561
		// Remove invalid options
562
		$params = array_intersect_key( $params, $options );
563
564
		// Used if response is successful. The message can be overwritten and additional data can be added here.
565
		$response = array(
566
			'code'	  => 'success',
567
			'message' => esc_html__( 'The requested Jetpack data updates were successful.', 'jetpack' ),
568
		);
569
570
		// If there are modules to activate, activate them first so they're ready when their options are set.
571
		foreach ( $params as $option => $value ) {
572
			if ( 'modules' === $options[ $option ]['jp_group'] ) {
573
574
				// Used if there was an error. Can be overwritten with specific error messages.
575
				$error = '';
576
577
				// Set to true if the module toggling was successful.
578
				$updated = false;
579
580
				// Check if user can toggle the module.
581
				if ( $toggle_module->can_request() ) {
582
583
					// Activate or deactivate the module according to the value passed.
584
					$toggle_result = $value
585
						? $toggle_module->activate_module( $option )
586
						: $toggle_module->deactivate_module( $option );
587
588
					if (
589
						is_wp_error( $toggle_result )
590
						&& 'already_inactive' === $toggle_result->get_error_code()
591
					) {
592
593
						// If the module is already inactive, we don't fail
594
						$updated = true;
595
					} elseif ( is_wp_error( $toggle_result ) ) {
596
						$error = $toggle_result->get_error_message();
597
					} else {
598
						$updated = true;
599
					}
600
				} else {
601
					$error = Jetpack_Core_Json_Api_Endpoints::$user_permissions_error_msg;
602
				}
603
604
				// The module was not toggled.
605
				if ( ! $updated ) {
606
					$not_updated[ $option ] = $error;
607
				}
608
609
				// Remove module from list so we don't go through it again.
610
				unset( $params[ $option ] );
611
			}
612
		}
613
614
		foreach ( $params as $option => $value ) {
615
616
			// Used if there was an error. Can be overwritten with specific error messages.
617
			$error = '';
618
619
			// Set to true if the option update was successful.
620
			$updated = false;
621
622
			// Get option attributes, including the group it belongs to.
623
			$option_attrs = $options[ $option ];
624
625
			// If this is a module option and the related module isn't active for any reason, continue with the next one.
626
			if ( 'settings' !== $option_attrs['jp_group'] ) {
627 View Code Duplication
				if ( ! Jetpack::is_module( $option_attrs['jp_group'] ) ) {
628
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module was not found.', 'jetpack' );
629
					continue;
630
				}
631
632 View Code Duplication
				if (
633
					'any' !== $request['slug']
634
					&& ! Jetpack::is_module_active( $option_attrs['jp_group'] )
635
				) {
636
637
					// We only take note of skipped options when updating one module
638
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' );
639
					continue;
640
				}
641
			}
642
643
			// Properly cast value based on its type defined in endpoint accepted args.
644
			$value = Jetpack_Core_Json_Api_Endpoints::cast_value( $value, $option_attrs );
645
646
			switch ( $option ) {
647
				case 'lang_id':
648
					if ( defined( 'WPLANG' ) || ! current_user_can( 'install_languages' ) ) {
649
						// We can't affect this setting
650
						$updated = false;
651
						break;
652
					}
653
654
					if ( $value === 'en_US' || empty( $value ) ) {
655
						return delete_option( 'WPLANG' );
656
					}
657
658
					if ( ! function_exists( 'request_filesystem_credentials' ) ) {
659
						require_once( ABSPATH . 'wp-admin/includes/file.php' );
660
					}
661
662
					if ( ! function_exists( 'wp_download_language_pack' ) ) {
663
						require_once ABSPATH . 'wp-admin/includes/translation-install.php';
664
					}
665
666
					// `wp_download_language_pack` only tries to download packs if they're not already available
667
					$language = wp_download_language_pack( $value );
668
					if ( $language === false ) {
669
						// The language pack download failed.
670
						$updated = false;
671
						break;
672
					}
673
					$updated = get_option( 'WPLANG' ) === $language ? true : update_option( 'WPLANG', $language );
674
					break;
675
676
				case 'monitor_receive_notifications':
677
					$monitor = new Jetpack_Monitor();
678
679
					// If we got true as response, consider it done.
680
					$updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value );
681
					break;
682
683
				case 'post_by_email_address':
684
					if ( 'create' == $value ) {
685
						$result = $this->_process_post_by_email(
686
							'jetpack.createPostByEmailAddress',
687
							esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' )
688
						);
689
					} elseif ( 'regenerate' == $value ) {
690
						$result = $this->_process_post_by_email(
691
							'jetpack.regeneratePostByEmailAddress',
692
							esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' )
693
						);
694
					} elseif ( 'delete' == $value ) {
695
						$result = $this->_process_post_by_email(
696
							'jetpack.deletePostByEmailAddress',
697
							esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' )
698
						);
699
					} else {
700
						$result = false;
701
					}
702
703
					// If we got an email address (create or regenerate) or 1 (delete), consider it done.
704
					if ( is_string( $result ) && preg_match( '/[a-z0-9][email protected]/', $result ) ) {
705
						$response[$option] = $result;
706
						$updated           = true;
707
					} elseif ( 1 == $result ) {
708
						$updated = true;
709
					} elseif ( is_array( $result ) && isset( $result['message'] ) ) {
710
						$error = $result['message'];
711
					}
712
					break;
713
714
				case 'jetpack_protect_key':
715
					$protect = Jetpack_Protect_Module::instance();
716
					if ( 'create' == $value ) {
717
						$result = $protect->get_protect_key();
718
					} else {
719
						$result = false;
720
					}
721
722
					// If we got one of Protect keys, consider it done.
723
					if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) {
724
						$response[$option] = $result;
725
						$updated           = true;
726
					}
727
					break;
728
729
				case 'jetpack_protect_global_whitelist':
730
					$updated = jetpack_protect_save_whitelist( explode( PHP_EOL, str_replace( array( ' ', ',' ), array( '', "\n" ), $value ) ) );
731
					if ( is_wp_error( $updated ) ) {
732
						$error = $updated->get_error_message();
733
					}
734
					break;
735
736
				case 'show_headline':
737
				case 'show_thumbnails':
738
					$grouped_options          = $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' );
739
					$grouped_options[$option] = $value;
740
741
					// If option value was the same, consider it done.
742
					$updated = $grouped_options_current != $grouped_options ? Jetpack_Options::update_option( 'relatedposts', $grouped_options ) : true;
743
					break;
744
745
				case 'google':
746
				case 'bing':
747
				case 'pinterest':
748 View Code Duplication
				case 'yandex':
749
					$grouped_options          = $grouped_options_current = (array) get_option( 'verification_services_codes' );
750
					$grouped_options[$option] = $value;
751
752
					// If option value was the same, consider it done.
753
					$updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true;
754
					break;
755
756
				case 'sharing_services':
757
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
758
						break;
759
					}
760
761
					$sharer = new Sharing_Service();
762
763
					// If option value was the same, consider it done.
764
					$updated = $value != $sharer->get_blog_services() ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) : true;
765
					break;
766
767
				case 'button_style':
768
				case 'sharing_label':
769
				case 'show':
770
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
771
						break;
772
					}
773
774
					$sharer = new Sharing_Service();
775
					$grouped_options = $sharer->get_global_options();
776
					$grouped_options[ $option ] = $value;
777
					$updated = $sharer->set_global_options( $grouped_options );
778
					break;
779
780
				case 'custom':
781
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
782
						break;
783
					}
784
785
					$sharer = new Sharing_Service();
786
					$updated = $sharer->new_service( stripslashes( $value['sharing_name'] ), stripslashes( $value['sharing_url'] ), stripslashes( $value['sharing_icon'] ) );
787
788
					// Return new custom service
789
					$response[$option] = $updated;
790
					break;
791
792
				case 'sharing_delete_service':
793
					if ( ! class_exists( 'Sharing_Service' ) && ! include_once( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
794
						break;
795
					}
796
797
					$sharer = new Sharing_Service();
798
					$updated = $sharer->delete_service( $value );
799
					break;
800
801
				case 'jetpack-twitter-cards-site-tag':
802
					$value   = trim( ltrim( strip_tags( $value ), '@' ) );
803
					$updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true;
804
					break;
805
806
				case 'onpublish':
807
				case 'onupdate':
808
				case 'Bias Language':
809
				case 'Cliches':
810
				case 'Complex Expression':
811
				case 'Diacritical Marks':
812
				case 'Double Negative':
813
				case 'Hidden Verbs':
814
				case 'Jargon Language':
815
				case 'Passive voice':
816
				case 'Phrases to Avoid':
817
				case 'Redundant Expression':
818
				case 'guess_lang':
819
					if ( in_array( $option, array( 'onpublish', 'onupdate' ) ) ) {
820
						$atd_option = 'AtD_check_when';
821
					} elseif ( 'guess_lang' == $option ) {
822
						$atd_option = 'AtD_guess_lang';
823
						$option     = 'true';
824
					} else {
825
						$atd_option = 'AtD_options';
826
					}
827
					$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...
828
					if ( ! function_exists( 'AtD_get_options' ) ) {
829
						include_once( JETPACK__PLUGIN_DIR . 'modules/after-the-deadline.php' );
830
					}
831
					$grouped_options_current = AtD_get_options( $user_id, $atd_option );
832
					unset( $grouped_options_current['name'] );
833
					$grouped_options = $grouped_options_current;
834
					if ( $value && ! isset( $grouped_options [$option] ) ) {
835
						$grouped_options [$option] = $value;
836
					} elseif ( ! $value && isset( $grouped_options [$option] ) ) {
837
						unset( $grouped_options [$option] );
838
					}
839
					// If option value was the same, consider it done, otherwise try to update it.
840
					$options_to_save = implode( ',', array_keys( $grouped_options ) );
841
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $options_to_save ) : true;
842
					break;
843
844
				case 'ignored_phrases':
845
				case 'unignore_phrase':
846
					$user_id         = get_current_user_id();
847
					$atd_option      = 'AtD_ignored_phrases';
848
					$grouped_options = $grouped_options_current = explode( ',', AtD_get_setting( $user_id, $atd_option ) );
849
					if ( 'ignored_phrases' == $option ) {
850
						$grouped_options = explode( ',', $value );
851
					} else {
852
						$index = array_search( $value, $grouped_options );
853
						if ( false !== $index ) {
854
							unset( $grouped_options[$index] );
855
							$grouped_options = array_values( $grouped_options );
856
						}
857
					}
858
					$ignored_phrases = implode( ',', array_filter( array_map( 'strip_tags', $grouped_options ) ) );
859
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $ignored_phrases ) : true;
860
					break;
861
862
				case 'admin_bar':
863
				case 'roles':
864
				case 'count_roles':
865
				case 'blog_id':
866
				case 'do_not_track':
867
				case 'hide_smile':
868 View Code Duplication
				case 'version':
869
					$grouped_options          = $grouped_options_current = (array) get_option( 'stats_options' );
870
					$grouped_options[$option] = $value;
871
872
					// If option value was the same, consider it done.
873
					$updated = $grouped_options_current != $grouped_options ? update_option( 'stats_options', $grouped_options ) : true;
874
					break;
875
876
				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...
877
878
					// Save Akismet option '1' or '0' like it's done in akismet/class.akismet-admin.php
879
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? '1' : '0' ) : true;
880
					break;
881
882
				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...
883
884
					if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
885
						$error = esc_html__( 'Please install Akismet.', 'jetpack' );
886
						$updated = false;
887
						break;
888
					}
889
890
					if ( ! defined( 'AKISMET_VERSION' ) ) {
891
						$error = esc_html__( 'Please activate Akismet.', 'jetpack' );
892
						$updated = false;
893
						break;
894
					}
895
896
					// Allow to clear the API key field
897
					if ( '' === $value ) {
898
						$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
899
						break;
900
					}
901
902
					require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
903
					require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
904
905
					if ( class_exists( 'Akismet_Admin' ) && method_exists( 'Akismet_Admin', 'save_key' ) ) {
906
						if ( Akismet::verify_key( $value ) === 'valid' ) {
907
							$akismet_user = Akismet_Admin::get_akismet_user( $value );
908
							if ( $akismet_user ) {
909
								if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) ) ) {
910
									$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
911
									break;
912
								} else {
913
									$error = esc_html__( "Akismet user status doesn't allow to update the key", 'jetpack' );
914
								}
915
							} else {
916
								$error = esc_html__( 'Invalid Akismet user', 'jetpack' );
917
							}
918
						} else {
919
							$error = esc_html__( 'Invalid Akismet key', 'jetpack' );
920
						}
921
					} else {
922
						$error = esc_html__( 'Akismet is not installed or active', 'jetpack' );
923
					}
924
					$updated = false;
925
					break;
926
927 View Code Duplication
				case 'google_analytics_tracking_id':
928
					$grouped_options = $grouped_options_current = (array) get_option( 'jetpack_wga' );
929
					$grouped_options[ 'code' ] = $value;
930
931
					// If option value was the same, consider it done.
932
					$updated = $grouped_options_current != $grouped_options ? update_option( 'jetpack_wga', $grouped_options ) : true;
933
					break;
934
935
				case 'dismiss_dash_app_card':
936 View Code Duplication
				case 'dismiss_empty_stats_card':
937
					// If option value was the same, consider it done.
938
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ) : true;
939
					break;
940
941
				case 'onboarding':
942
					jetpack_require_lib( 'widgets' );
943
					// Break apart and set Jetpack onboarding options.
944
					$result = $this->_process_onboarding( (array) $value );
945
					if ( empty( $result ) ) {
946
						$updated = true;
947
					} else {
948
						$error = sprintf( esc_html__( 'Onboarding failed to process: %s', 'jetpack' ), $result );
949
						$updated = false;
950
					}
951
					break;
952
953 View Code Duplication
				case 'show_welcome_for_new_plan':
954
					// If option value was the same, consider it done.
955
					$updated = get_option( $option ) !== $value ? update_option( $option, (bool) $value ) : true;
956
					break;
957
958 View Code Duplication
				default:
959
					// If option value was the same, consider it done.
960
					$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
961
					break;
962
			}
963
964
			// The option was not updated.
965
			if ( ! $updated ) {
966
				$not_updated[ $option ] = $error;
967
			}
968
		}
969
970
		if ( empty( $invalid ) && empty( $not_updated ) ) {
971
			// The option was updated.
972
			return rest_ensure_response( $response );
973
		} else {
974
			$invalid_count = count( $invalid );
975
			$not_updated_count = count( $not_updated );
976
			$error = '';
977
			if ( $invalid_count > 0 ) {
978
				$error = sprintf(
979
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
980
					_n( 'Invalid option: %s.', 'Invalid options: %s.', $invalid_count, 'jetpack' ),
981
					join( ', ', $invalid )
982
				);
983
			}
984
			if ( $not_updated_count > 0 ) {
985
				$not_updated_messages = array();
986
				foreach ( $not_updated as $not_updated_option => $not_updated_message ) {
987
					if ( ! empty( $not_updated_message ) ) {
988
						$not_updated_messages[] = sprintf(
989
							/* Translators: the first variable is a module option or slug, or setting. The second is the error message . */
990
							__( '%1$s: %2$s', 'jetpack' ),
991
							$not_updated_option, $not_updated_message );
992
					}
993
				}
994
				if ( ! empty( $error ) ) {
995
					$error .= ' ';
996
				}
997
				if ( ! empty( $not_updated_messages ) ) {
998
					$error .= ' ' . join( '. ', $not_updated_messages );
999
				}
1000
1001
			}
1002
			// There was an error because some options were updated but others were invalid or failed to update.
1003
			return new WP_Error( 'some_updated', esc_html( $error ), array( 'status' => 400 ) );
1004
		}
1005
1006
	}
1007
1008
	/**
1009
	 * Perform tasks in the site based on onboarding choices.
1010
	 *
1011
	 * @since 5.4.0
1012
	 *
1013
	 * @param array $data Onboarding choices made by user.
1014
	 *
1015
	 * @return string Result of onboarding processing and, if there is one, an error message.
1016
	 */
1017
	private function _process_onboarding( $data ) {
1018
		if ( isset( $data['end'] ) && $data['end'] ) {
1019
			return Jetpack::invalidate_onboarding_token()
1020
				? ''
1021
				: esc_html__( "The onboarding token couldn't be deleted.", 'jetpack' );
1022
		}
1023
1024
		$error = array();
1025
1026 View Code Duplication
		if ( ! empty( $data['siteTitle'] ) ) {
1027
			// If option value was the same, consider it done.
1028
			if ( ! ( update_option( 'blogname', $data['siteTitle'] ) || get_option( 'blogname' ) == $data['siteTitle'] ) ) {
1029
				$error[] = 'siteTitle';
1030
			}
1031
		}
1032
1033
		if ( isset( $data['siteDescription'] ) ) {
1034
			// If option value was the same, consider it done.
1035
			if ( ! ( update_option( 'blogdescription', $data['siteDescription'] ) || get_option( 'blogdescription' ) == $data['siteDescription'] ) ) {
1036
				$error[] = 'siteDescription';
1037
			}
1038
		}
1039
1040
		$site_title = get_option( 'blogname' );
1041
		$author = get_current_user_id() || 1;
1042
1043 View Code Duplication
		if ( ! empty( $data['siteType'] ) ) {
1044
			if ( ! ( update_option( 'jpo_site_type', $data['siteType'] ) || get_option( 'jpo_site_type' ) == $data['siteType'] ) ) {
1045
				$error[] = 'siteType';
1046
			}
1047
		}
1048
1049
		if ( isset( $data['homepageFormat'] ) ) {
1050
			// If $data['homepageFormat'] is 'posts', we have nothing to do since it's WordPress' default
1051
			// if it exists, just update
1052
			$homepage_format = get_option( 'jpo_homepage_format' );
1053
			if ( ! $homepage_format || $homepage_format !== $data['homepageFormat'] ) {
1054
				if ( 'page' === $data['homepageFormat'] ) {
1055
					if ( ! ( update_option( 'show_on_front', 'page' ) || get_option( 'show_on_front' ) == 'page' ) ) {
1056
						$error[] = 'homepageFormat';
1057
					}
1058
1059
					$home = wp_insert_post( array(
1060
						'post_type'     => 'page',
1061
						/* translators: this references the home page of a site, also called front page. */
1062
						'post_title'    => esc_html_x( 'Home Page', 'The home page of a website.', 'jetpack' ),
1063
						'post_content'  => sprintf( esc_html__( 'Welcome to %s.', 'jetpack' ), $site_title ),
1064
						'post_status'   => 'publish',
1065
						'post_author'   => $author,
1066
					) );
1067 View Code Duplication
					if ( 0 == $home ) {
1068
						$error[] = 'home insert: 0';
1069
					} elseif ( is_wp_error( $home ) ) {
1070
						$error[] = 'home creation: '. $home->get_error_message();
1071
					}
1072
					if ( ! ( update_option( 'page_on_front', $home ) || get_option( 'page_on_front' ) == $home ) ) {
1073
1074
						$error[] = 'home set';
1075
					}
1076
1077
					$blog = wp_insert_post( array(
1078
						'post_type'     => 'page',
1079
						/* translators: this references the page where blog posts are listed. */
1080
						'post_title'    => esc_html_x( 'Blog', 'The blog of a website.', 'jetpack' ),
1081
						'post_content'  => sprintf( esc_html__( 'These are the latest posts in %s.', 'jetpack' ), $site_title ),
1082
						'post_status'   => 'publish',
1083
						'post_author'   => $author,
1084
					) );
1085 View Code Duplication
					if ( 0 == $blog ) {
1086
						$error[] = 'blog insert: 0';
1087
					} elseif ( is_wp_error( $blog ) ) {
1088
						$error[] = 'blog creation: '. $blog->get_error_message();
1089
					}
1090
					if ( ! ( update_option( 'page_for_posts', $blog ) || get_option( 'page_for_posts' ) == $blog ) ) {
1091
						$error[] = 'blog set';
1092
					}
1093
				} else {
1094
					$front_page = get_option( 'page_on_front' );
1095
					$posts_page = get_option( 'page_for_posts' );
1096
					if ( $posts_page && get_post( $posts_page ) ) {
1097
						wp_delete_post( $posts_page );
1098
					}
1099
					if ( $front_page && get_post( $front_page ) ) {
1100
						wp_delete_post( $front_page );
1101
					}
1102
					update_option( 'show_on_front', 'posts' );
1103
				}
1104
			}
1105
			update_option( 'jpo_homepage_format', $data['homepageFormat'] );
1106
		}
1107
1108
		// Setup contact page and add a form and/or business info
1109
		$contact_page = '';
1110
		if ( ! empty( $data['addContactForm'] ) && ! get_option( 'jpo_contact_page' ) ) {
1111
			$contact_form_module_active = Jetpack::is_module_active( 'contact-form' );
1112
			if ( ! $contact_form_module_active ) {
1113
				$contact_form_module_active = Jetpack::activate_module( 'contact-form', false, false );
1114
			}
1115
1116
			if ( $contact_form_module_active ) {
1117
				$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]';
1118
			} else {
1119
				$error[] = 'contact-form activate';
1120
			}
1121
		}
1122
1123
		if ( isset( $data['businessPersonal'] ) && 'business' === $data['businessPersonal'] ) {
1124
			$contact_page .= "\n" . join( "\n", $data['businessInfo'] );
1125
		}
1126
1127
		if ( ! empty( $contact_page ) ) {
1128
			$form = wp_insert_post( array(
1129
				'post_type'     => 'page',
1130
				/* translators: this references a page with contact details and possibly a form. */
1131
				'post_title'    => esc_html_x( 'Contact us', 'Contact page for your website.', 'jetpack' ),
1132
				'post_content'  => esc_html__( 'Send us a message!', 'jetpack' ) . "\n" . $contact_page,
1133
				'post_status'   => 'publish',
1134
				'post_author'   => $author,
1135
			) );
1136
			if ( 0 == $form ) {
1137
				$error[] = 'form insert: 0';
1138
			} elseif ( is_wp_error( $form ) ) {
1139
				$error[] = 'form creation: '. $form->get_error_message();
1140
			} else {
1141
				update_option( 'jpo_contact_page', $form );
1142
			}
1143
		}
1144
1145
		if ( isset( $data['businessAddress'] ) ) {
1146
			$handled_business_address = self::handle_business_address( $data['businessAddress'] );
1147
			if ( is_wp_error( $handled_business_address ) ) {
1148
				$error[] = 'BusinessAddress';
1149
			}
1150
		}
1151
1152
		if ( ! empty( $data['installWooCommerce'] ) ) {
1153
			jetpack_require_lib( 'plugins' );
1154
			$wc_install_result = Jetpack_Plugins::install_and_activate_plugin( 'woocommerce' );
1155
			delete_transient( '_wc_activation_redirect' ); // Redirecting to WC setup would kill our users' flow
1156
			if ( is_wp_error( $wc_install_result ) ) {
1157
				$error[] = 'woocommerce installation';
1158
			}
1159
		}
1160
1161
		if ( ! empty( $data['stats'] ) ) {
1162
			if ( Jetpack::is_active() ) {
1163
				$stats_module_active = Jetpack::is_module_active( 'stats' );
1164
				if ( ! $stats_module_active ) {
1165
					$stats_module_active = Jetpack::activate_module( 'stats', false, false );
1166
				}
1167
1168
				if ( ! $stats_module_active ) {
1169
					$error[] = 'stats activate';
1170
				}
1171
			} else {
1172
				$error[] = 'stats not connected';
1173
			}
1174
		}
1175
1176
		return empty( $error )
1177
			? ''
1178
			: join( ', ', $error );
1179
	}
1180
1181
	/**
1182
	 * Add or update Business Address widget.
1183
	 *
1184
	 * @param array $address Array of business address fields.
1185
	 *
1186
	 * @return WP_Error|true True if the data was saved correctly.
1187
	*/
1188
	static function handle_business_address( $address ) {
1189
		$first_sidebar = Jetpack_Widgets::get_first_sidebar();
1190
1191
		$widgets_module_active = Jetpack::is_module_active( 'widgets' );
1192
		if ( ! $widgets_module_active ) {
1193
			$widgets_module_active = Jetpack::activate_module( 'widgets', false, false );
1194
		}
1195
		if ( ! $widgets_module_active ) {
1196
			return new WP_Error( 'module_activation_failed', 'Failed to activate the widgets module.', 400 );
1197
		}
1198
1199
		if ( $first_sidebar ) {
1200
			$title = isset( $address['name'] ) ? sanitize_text_field( $address['name'] ) : '';
1201
			$street = isset( $address['street'] ) ? sanitize_text_field( $address['street'] ) : '';
1202
			$city = isset( $address['city'] ) ? sanitize_text_field( $address['city'] ) : '';
1203
			$state = isset( $address['state'] ) ? sanitize_text_field( $address['state'] ) : '';
1204
			$zip = isset( $address['zip'] ) ? sanitize_text_field( $address['zip'] ) : '';
1205
			$country = isset( $address['country'] ) ? sanitize_text_field( $address['country'] ) : '';
1206
1207
			$full_address = implode( ' ', array_filter( array( $street, $city, $state, $zip, $country ) ) );
1208
1209
			$widget_options = array(
1210
				'title'   => $title,
1211
				'address' => $full_address,
1212
				'phone'   => '',
1213
				'hours'   => '',
1214
				'showmap' => false,
1215
				'email' => ''
1216
			);
1217
1218
			$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...
1219
			if ( ! self::has_business_address_widget( $first_sidebar ) ) {
1220
				$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...
1221
			} else {
1222
				$widget_updated = Jetpack_Widgets::update_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar );
1223
			}
1224
			if ( is_wp_error( $widget_updated ) ) {
1225
				return new WP_Error( 'widget_update_failed', 'Widget could not be updated.', 400 );
1226
			}
1227
1228
			$address_save = array(
1229
				'name' => $title,
1230
				'street' => $street,
1231
				'city' => $city,
1232
				'state' => $state,
1233
				'zip' => $zip,
1234
				'country' => $country
1235
			);
1236
			update_option( 'jpo_business_address', $address_save );
1237
			return true;
1238
		}
1239
1240
		// No sidebar to place the widget
1241
		return new WP_Error( 'sidebar_not_found', 'No sidebar.', 400 );
1242
	}
1243
1244
	/**
1245
	 * Check whether "Contact Info & Map" widget is present in a given sidebar.
1246
	 *
1247
	 * @param string  $sidebar ID of the sidebar to which the widget will be added.
1248
	 *
1249
	 * @return bool Whether the widget is present in a given sidebar.
1250
	*/
1251
	static function has_business_address_widget( $sidebar ) {
1252
		$sidebars_widgets = get_option( 'sidebars_widgets', array() );
1253
		if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) {
1254
			return false;
1255
		}
1256
		foreach ( $sidebars_widgets[ $sidebar ] as $widget ) {
1257
			if ( strpos( $widget, 'widget_contact_info' ) !== false ) {
1258
				return true;
1259
			}
1260
		}
1261
		return false;
1262
	}
1263
1264
	/**
1265
	 * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
1266
	 * @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...
1267
	 *
1268
	 * @since 4.3.0
1269
	 *
1270
	 * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
1271
	 * @param string $error	   Error message to return.
1272
	 *
1273
	 * @return array
1274
	 */
1275
	private function _process_post_by_email( $endpoint, $error ) {
1276
		if ( ! current_user_can( 'edit_posts' ) ) {
1277
			return array( 'message' => $error );
1278
		}
1279
1280
		$this->xmlrpc->query( $endpoint );
1281
1282
		if ( $this->xmlrpc->isError() ) {
1283
			return array( 'message' => $error );
1284
		}
1285
1286
		$response = $this->xmlrpc->getResponse();
1287
		if ( empty( $response ) ) {
1288
			return array( 'message' => $error );
1289
		}
1290
1291
		// Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
1292
		update_option( 'post_by_email_address' . get_current_user_id(), $response );
1293
1294
		return $response;
1295
	}
1296
1297
	/**
1298
	 * Check if user is allowed to perform the update.
1299
	 *
1300
	 * @since 4.3.0
1301
	 *
1302
	 * @param WP_REST_Request $request The request sent to the WP REST API.
1303
	 *
1304
	 * @return bool
1305
	 */
1306
	public function can_request( $request ) {
1307
		$req_params = $request->get_params();
1308
		if ( ! empty( $req_params['onboarding']['token'] ) && isset( $req_params['rest_route'] ) ) {
1309
			return Jetpack::validate_onboarding_token_action( $req_params['onboarding']['token'], $req_params['rest_route'] );
1310
		}
1311
1312
		if ( 'GET' === $request->get_method() ) {
1313
			return current_user_can( 'jetpack_admin_page' );
1314
		} else {
1315
			$module = Jetpack_Core_Json_Api_Endpoints::get_module_requested();
1316
			if ( empty( $module ) ) {
1317
				$params = $request->get_json_params();
1318
				if ( ! is_array( $params ) ) {
1319
					$params = $request->get_body_params();
1320
				}
1321
				$options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( $params );
1322
				foreach ( $options as $option => $definition ) {
1323
					if ( in_array( $options[ $option ]['jp_group'], array( 'after-the-deadline', 'post-by-email' ) ) ) {
1324
						$module = $options[ $option ]['jp_group'];
1325
						break;
1326
					}
1327
				}
1328
			}
1329
			// User is trying to create, regenerate or delete its PbE || ATD settings.
1330
			if ( 'post-by-email' === $module || 'after-the-deadline' === $module ) {
1331
				return current_user_can( 'edit_posts' ) && current_user_can( 'jetpack_admin_page' );
1332
			}
1333
			return current_user_can( 'jetpack_configure_modules' );
1334
		}
1335
	}
1336
}
1337
1338
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...
1339
1340
	public function process( $request ) {
1341
		switch( $request['slug'] ) {
1342
			case 'protect':
1343
				return $this->get_protect_data();
1344
			case 'stats':
1345
				return $this->get_stats_data( $request );
1346
			case 'akismet':
1347
				return $this->get_akismet_data();
1348
			case 'monitor':
1349
				return $this->get_monitor_data();
1350
			case 'verification-tools':
1351
				return $this->get_verification_tools_data();
1352
			case 'vaultpress':
1353
				return $this->get_vaultpress_data();
1354
		}
1355
	}
1356
1357
	/**
1358
	 * Decide against which service to check the key.
1359
	 *
1360
	 * @since 4.8.0
1361
	 *
1362
	 * @param WP_REST_Request $request
1363
	 *
1364
	 * @return bool
1365
	 */
1366
	public function key_check( $request ) {
1367
		switch( $request['service'] ) {
1368
			case 'akismet':
1369
				$params = $request->get_json_params();
1370
				if ( isset( $params['api_key'] ) && ! empty( $params['api_key'] ) ) {
1371
					return $this->check_akismet_key( $params['api_key'] );
1372
				}
1373
				return $this->check_akismet_key();
1374
		}
1375
		return false;
1376
	}
1377
1378
	/**
1379
	 * Get number of blocked intrusion attempts.
1380
	 *
1381
	 * @since 4.3.0
1382
	 *
1383
	 * @return mixed|WP_Error Number of blocked attempts if protection is enabled. Otherwise, a WP_Error instance with the corresponding error.
1384
	 */
1385
	public function get_protect_data() {
1386
		if ( Jetpack::is_module_active( 'protect' ) ) {
1387
			return get_site_option( 'jetpack_protect_blocked_attempts' );
1388
		}
1389
1390
		return new WP_Error(
1391
			'not_active',
1392
			esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1393
			array( 'status' => 404 )
1394
		);
1395
	}
1396
1397
	/**
1398
	 * Get number of spam messages blocked by Akismet.
1399
	 *
1400
	 * @since 4.3.0
1401
	 *
1402
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
1403
	 */
1404
	public function get_akismet_data() {
1405
		if ( ! is_wp_error( $status = $this->akismet_is_active_and_registered() ) ) {
1406
			return rest_ensure_response( Akismet_Admin::get_stats( Akismet::get_api_key() ) );
1407
		} else {
1408
			return $status->get_error_code();
1409
		}
1410
	}
1411
1412
	/**
1413
	 * Verify the Akismet API key.
1414
	 *
1415
	 * @since 4.8.0
1416
	 *
1417
	 * @param string $api_key Optional API key to check.
1418
	 *
1419
	 * @return array Information about the key. 'validKey' is true if key is valid, false otherwise.
1420
	 */
1421
	public function check_akismet_key( $api_key = '' ) {
1422
		$akismet_status = $this->akismet_class_exists();
1423
		if ( is_wp_error( $akismet_status ) ) {
1424
			return rest_ensure_response( array(
1425
				'validKey'          => false,
1426
				'invalidKeyCode'    => $akismet_status->get_error_code(),
1427
				'invalidKeyMessage' => $akismet_status->get_error_message(),
1428
			) );
1429
		}
1430
1431
		$key_status = Akismet::check_key_status( empty( $api_key ) ? Akismet::get_api_key() : $api_key );
1432
1433 View Code Duplication
		if ( ! $key_status || 'invalid' === $key_status || 'failed' === $key_status ) {
1434
			return rest_ensure_response( array(
1435
				'validKey'          => false,
1436
				'invalidKeyCode'    => 'invalid_key',
1437
				'invalidKeyMessage' => esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ),
1438
			) );
1439
		}
1440
1441
		return rest_ensure_response( array(
1442
			'validKey' => isset( $key_status[1] ) && 'valid' === $key_status[1]
1443
		) );
1444
	}
1445
1446
	/**
1447
	 * Check if Akismet class file exists and if class is loaded.
1448
	 *
1449
	 * @since 4.8.0
1450
	 *
1451
	 * @return bool|WP_Error Returns true if class file exists and class is loaded, WP_Error otherwise.
1452
	 */
1453
	private function akismet_class_exists() {
1454
		if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
1455
			return new WP_Error( 'not_installed', esc_html__( 'Please install Akismet.', 'jetpack' ), array( 'status' => 400 ) );
1456
		}
1457
1458 View Code Duplication
		if ( ! class_exists( 'Akismet' ) ) {
1459
			return new WP_Error( 'not_active', esc_html__( 'Please activate Akismet.', 'jetpack' ), array( 'status' => 400 ) );
1460
		}
1461
1462
		return true;
1463
	}
1464
1465
	/**
1466
	 * Is Akismet registered and active?
1467
	 *
1468
	 * @since 4.3.0
1469
	 *
1470
	 * @return bool|WP_Error True if Akismet is active and registered. Otherwise, a WP_Error instance with the corresponding error.
1471
	 */
1472
	private function akismet_is_active_and_registered() {
1473
		if ( is_wp_error( $akismet_exists = $this->akismet_class_exists() ) ) {
1474
			return $akismet_exists;
1475
		}
1476
1477
		// What about if Akismet is put in a sub-directory or maybe in mu-plugins?
1478
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
1479
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
1480
		$akismet_key = Akismet::verify_key( Akismet::get_api_key() );
1481
1482 View Code Duplication
		if ( ! $akismet_key || 'invalid' === $akismet_key || 'failed' === $akismet_key ) {
1483
			return new WP_Error( 'invalid_key', esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), array( 'status' => 400 ) );
1484
		}
1485
1486
		return true;
1487
	}
1488
1489
	/**
1490
	 * Get stats data for this site
1491
	 *
1492
	 * @since 4.1.0
1493
	 *
1494
	 * @param WP_REST_Request $request {
1495
	 *     Array of parameters received by request.
1496
	 *
1497
	 *     @type string $date Date range to restrict results to.
1498
	 * }
1499
	 *
1500
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
1501
	 */
1502
	public function get_stats_data( WP_REST_Request $request ) {
1503
		// Get parameters to fetch Stats data.
1504
		$range = $request->get_param( 'range' );
1505
1506
		// If no parameters were passed.
1507
		if (
1508
			empty ( $range )
1509
			|| ! in_array( $range, array( 'day', 'week', 'month' ), true )
1510
		) {
1511
			$range = 'day';
1512
		}
1513
1514
		if ( ! function_exists( 'stats_get_from_restapi' ) ) {
1515
			require_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
1516
		}
1517
1518
		switch ( $range ) {
1519
1520
			// This is always called first on page load
1521
			case 'day':
1522
				$initial_stats = stats_get_from_restapi();
1523
				return rest_ensure_response( array(
1524
					'general' => $initial_stats,
1525
1526
					// Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' );
1527
					'day' => isset( $initial_stats->visits )
1528
						? $initial_stats->visits
1529
						: array(),
1530
				) );
1531
			case 'week':
1532
				return rest_ensure_response( array(
1533
					'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ),
1534
				) );
1535
			case 'month':
1536
				return rest_ensure_response( array(
1537
					'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ),
1538
				) );
1539
		}
1540
	}
1541
1542
	/**
1543
	 * Get date of last downtime.
1544
	 *
1545
	 * @since 4.3.0
1546
	 *
1547
	 * @return mixed|WP_Error Number of days since last downtime. Otherwise, a WP_Error instance with the corresponding error.
1548
	 */
1549
	public function get_monitor_data() {
1550 View Code Duplication
		if ( ! Jetpack::is_module_active( 'monitor' ) ) {
1551
			return new WP_Error(
1552
				'not_active',
1553
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1554
				array( 'status' => 404 )
1555
			);
1556
		}
1557
1558
		$monitor       = new Jetpack_Monitor();
1559
		$last_downtime = $monitor->monitor_get_last_downtime();
1560
		if ( is_wp_error( $last_downtime ) ) {
1561
			return $last_downtime;
1562
		} else if ( false === strtotime( $last_downtime ) ) {
1563
			return rest_ensure_response( array(
1564
				'code' => 'success',
1565
				'date' => null,
1566
			) );
1567
		} else {
1568
			return rest_ensure_response( array(
1569
				'code' => 'success',
1570
				'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ),
1571
			) );
1572
		}
1573
	}
1574
1575
	/**
1576
	 * Get services that this site is verified with.
1577
	 *
1578
	 * @since 4.3.0
1579
	 *
1580
	 * @return mixed|WP_Error List of services that verified this site. Otherwise, a WP_Error instance with the corresponding error.
1581
	 */
1582
	public function get_verification_tools_data() {
1583 View Code Duplication
		if ( ! Jetpack::is_module_active( 'verification-tools' ) ) {
1584
			return new WP_Error(
1585
				'not_active',
1586
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1587
				array( 'status' => 404 )
1588
			);
1589
		}
1590
1591
		$verification_services_codes = get_option( 'verification_services_codes' );
1592 View Code Duplication
		if (
1593
			! is_array( $verification_services_codes )
1594
			|| empty( $verification_services_codes )
1595
		) {
1596
			return new WP_Error(
1597
				'empty',
1598
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1599
				array( 'status' => 404 )
1600
			);
1601
		}
1602
1603
		$services = array();
1604
		foreach ( jetpack_verification_services() as $name => $service ) {
1605
			if ( is_array( $service ) && ! empty( $verification_services_codes[ $name ] ) ) {
1606
				switch ( $name ) {
1607
					case 'google':
1608
						$services[] = 'Google';
1609
						break;
1610
					case 'bing':
1611
						$services[] = 'Bing';
1612
						break;
1613
					case 'pinterest':
1614
						$services[] = 'Pinterest';
1615
						break;
1616
					case 'yandex':
1617
						$services[] = 'Yandex';
1618
						break;
1619
				}
1620
			}
1621
		}
1622
1623
		if ( empty( $services ) ) {
1624
			return new WP_Error(
1625
				'empty',
1626
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1627
				array( 'status' => 404 )
1628
			);
1629
		}
1630
1631
		if ( 2 > count( $services ) ) {
1632
			$message = esc_html(
1633
				sprintf(
1634
					/* translators: %s is a service name like Google, Bing, Pinterest, etc. */
1635
					__( 'Your site is verified with %s.', 'jetpack' ),
1636
					$services[0]
1637
				)
1638
			);
1639
		} else {
1640
			$copy_services = $services;
1641
			$last = count( $copy_services ) - 1;
1642
			$last_service = $copy_services[ $last ];
1643
			unset( $copy_services[ $last ] );
1644
			$message = esc_html(
1645
				sprintf(
1646
					/* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1647
					__( 'Your site is verified with %1$s and %2$s.', 'jetpack' ),
1648
					join( ', ', $copy_services ),
1649
					$last_service
1650
				)
1651
			);
1652
		}
1653
1654
		return rest_ensure_response( array(
1655
			'code'     => 'success',
1656
			'message'  => $message,
1657
			'services' => $services,
1658
		) );
1659
	}
1660
1661
	/**
1662
	 * Get VaultPress site data including, among other things, the date of the last backup if it was completed.
1663
	 *
1664
	 * @since 4.3.0
1665
	 *
1666
	 * @return mixed|WP_Error VaultPress site data. Otherwise, a WP_Error instance with the corresponding error.
1667
	 */
1668
	public function get_vaultpress_data() {
1669 View Code Duplication
		if ( ! class_exists( 'VaultPress' ) ) {
1670
			return new WP_Error(
1671
				'not_active',
1672
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1673
				array( 'status' => 404 )
1674
			);
1675
		}
1676
1677
		$vaultpress = new VaultPress();
1678
		if ( ! $vaultpress->is_registered() ) {
1679
			return rest_ensure_response( array(
1680
				'code'    => 'not_registered',
1681
				'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' )
1682
			) );
1683
		}
1684
1685
		$data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) );
1686
		if ( false == $data ) {
1687
			return rest_ensure_response( array(
1688
				'code'    => 'not_registered',
1689
				'message' => esc_html__( 'Could not connect to VaultPress.', 'jetpack' )
1690
			) );
1691
		} else if ( is_wp_error( $data ) || ! isset( $data->backups->last_backup ) ) {
1692
			return $data;
1693
		} else if ( empty( $data->backups->last_backup ) ) {
1694
			return rest_ensure_response( array(
1695
				'code'    => 'success',
1696
				'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ),
1697
				'data'    => $data,
1698
			) );
1699
		} else {
1700
			return rest_ensure_response( array(
1701
				'code'    => 'success',
1702
				'message' => esc_html(
1703
					sprintf(
1704
						__( 'Your site was successfully backed-up %s ago.', 'jetpack' ),
1705
						human_time_diff(
1706
							$data->backups->last_backup,
1707
							current_time( 'timestamp' )
1708
						)
1709
					)
1710
				),
1711
				'data'    => $data,
1712
			) );
1713
		}
1714
	}
1715
1716
	/**
1717
	 * A WordPress REST API permission callback method that accepts a request object and
1718
	 * decides if the current user has enough privileges to act.
1719
	 *
1720
	 * @since 4.3.0
1721
	 *
1722
	 * @return bool does a current user have enough privileges.
1723
	 */
1724
	public function can_request() {
1725
		return current_user_can( 'jetpack_admin_page' );
1726
	}
1727
}
1728
1729
/**
1730
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1731
 *
1732
 * @since 4.3.1
1733
 */
1734
function jetpack_do_after_gravatar_hovercards_activation() {
1735
1736
	// When Gravatar Hovercards is activated, enable them automatically.
1737
	update_option( 'gravatar_disable_hovercards', 'enabled' );
1738
}
1739
add_action( 'jetpack_activate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_activation' );
1740
1741
/**
1742
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1743
 *
1744
 * @since 4.3.1
1745
 */
1746
function jetpack_do_after_gravatar_hovercards_deactivation() {
1747
1748
	// When Gravatar Hovercards is deactivated, disable them automatically.
1749
	update_option( 'gravatar_disable_hovercards', 'disabled' );
1750
}
1751
add_action( 'jetpack_deactivate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_deactivation' );
1752
1753
/**
1754
 * Actions performed only when Markdown is activated through the endpoint call.
1755
 *
1756
 * @since 4.7.0
1757
 */
1758
function jetpack_do_after_markdown_activation() {
1759
1760
	// When Markdown is activated, enable support for post editing automatically.
1761
	update_option( 'wpcom_publish_posts_with_markdown', true );
1762
}
1763
add_action( 'jetpack_activate_module_markdown', 'jetpack_do_after_markdown_activation' );
1764