Completed
Push — update/jpo-enable-widgets-no-l... ( 217ee2...7b88f5 )
by
unknown
25:30 queued 16:35
created

Jetpack_Core_API_Data   F

Complexity

Total Complexity 228

Size/Duplication

Total Lines 922
Duplicated Lines 6.72 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

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

9 Methods

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