Completed
Push — add/jpo-rest-api-stats-module ( 85be45 )
by Marin
11:03
created

Jetpack_Core_API_Data   D

Complexity

Total Complexity 254

Size/Duplication

Total Lines 1025
Duplicated Lines 6.05 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 62
loc 1025
rs 4
c 0
b 0
f 0
wmc 254
lcom 1
cbo 11

10 Methods

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