Completed
Push — fix/issue-5330 ( 1d6fc4...24b227 )
by
unknown
120:58 queued 110:16
created

Jetpack_Core_API_Data   D

Complexity

Total Complexity 114

Size/Duplication

Total Lines 490
Duplicated Lines 7.14 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 35
loc 490
rs 4.8717
c 0
b 0
f 0
wmc 114
lcom 1
cbo 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 7 2
C get_module() 0 33 7
F update_data() 35 355 96
A _process_post_by_email() 0 21 4
B can_request() 0 12 5

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
	 * List of modules that require WPCOM public access.
11
	 *
12
	 * @since 4.3.0
13
	 *
14
	 * @var array
15
	 */
16
	private $modules_requiring_public = array(
17
		'photon',
18
		'enhanced-distribution',
19
		'json-api',
20
	);
21
22
	/**
23
	 * Check if the module requires the site to be publicly accessible from WPCOM.
24
	 * If the site meets this requirement, the module is activated. Otherwise an error is returned.
25
	 *
26
	 * @since 4.3.0
27
	 *
28
	 * @param WP_REST_Request $data {
29
	 *     Array of parameters received by request.
30
	 *
31
	 *     @type string $slug Module slug.
32
	 *     @type bool   $active should module be activated.
33
	 * }
34
	 *
35
	 * @return WP_REST_Response|WP_Error A REST response if the request was served successfully, otherwise an error.
36
	 */
37
	public function process( $data ) {
38
		if ( $data['active'] ) {
39
			return $this->activate_module( $data );
40
		} else {
41
			return $this->deactivate_module( $data );
42
		}
43
	}
44
45
	/**
46
	 * If it's a valid Jetpack module, activate it.
47
	 *
48
	 * @since 4.3.0
49
	 *
50
	 * @param string|WP_REST_Request $data {
51
	 *     Array of parameters received by request.
52
	 *
53
	 *     @type string $slug Module slug.
54
	 * }
55
	 *
56
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
57
	 */
58
	public function activate_module( $data ) {
59
		$module_slug = isset( $data['slug'] )
60
			? $data['slug']
61
			: $data;
62
63 View Code Duplication
		if ( ! Jetpack::is_module( $module_slug ) ) {
64
			return new WP_Error(
65
				'not_found',
66
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
67
				array( 'status' => 404 )
68
			);
69
		}
70
71
		if (
72
			in_array( $module_slug, $this->modules_requiring_public )
73
			&& ! $this->is_site_public()
74
		) {
75
			return new WP_Error(
76
				'rest_cannot_publish',
77
				esc_html__( 'This module requires your site to be set to publicly accessible.', 'jetpack' ),
78
				array( 'status' => 424 )
79
			);
80
		}
81
82
		if ( Jetpack::activate_module( $module_slug, false, false ) ) {
83
			return rest_ensure_response( array(
84
				'code' 	  => 'success',
85
				'message' => esc_html__( 'The requested Jetpack module was activated.', 'jetpack' ),
86
			) );
87
		}
88
89
		return new WP_Error(
90
			'activation_failed',
91
			esc_html__( 'The requested Jetpack module could not be activated.', 'jetpack' ),
92
			array( 'status' => 424 )
93
		);
94
	}
95
96
	/**
97
	 * If it's a valid Jetpack module, deactivate it.
98
	 *
99
	 * @since 4.3.0
100
	 *
101
	 * @param string|WP_REST_Request $data {
102
	 *     Array of parameters received by request.
103
	 *
104
	 *     @type string $slug Module slug.
105
	 * }
106
	 *
107
	 * @return bool|WP_Error True if module was activated. Otherwise, a WP_Error instance with the corresponding error.
108
	 */
109
	public function deactivate_module( $data ) {
110
		$module_slug = isset( $data['slug'] )
111
			? $data['slug']
112
			: $data;
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
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 );
0 ignored issues
show
Documentation introduced by
$modules is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200
	}
201
202
	/**
203
	 * Activate a list of valid Jetpack modules.
204
	 *
205
	 * @since 4.3.0
206
	 *
207
	 * @param WP_REST_Request $data {
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( $data ) {
216
		$params = $data->get_json_params();
217
218
		if (
219
			! isset( $params['modules'] )
220
			|| is_array( $params['modules'] )
221
		) {
222
			return new WP_Error(
223
				'not_found',
224
				esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
225
				array( 'status' => 404 )
226
			);
227
		}
228
229
		$activated = array();
230
		$failed = array();
231
232
		foreach ( $params['modules'] as $module ) {
233
			if ( Jetpack::activate_module( $module, false, false ) ) {
234
				$activated[] = $module;
235
			} else {
236
				$failed[] = $module;
237
			}
238
		}
239
240 View Code Duplication
		if ( empty( $failed ) ) {
241
			return rest_ensure_response( array(
242
				'code' 	  => 'success',
243
				'message' => esc_html__( 'All modules activated.', 'jetpack' ),
244
			) );
245
		}
246
247
		$error = '';
248
249
		$activated_count = count( $activated );
250 View Code Duplication
		if ( $activated_count > 0 ) {
251
			$activated_last = array_pop( $activated );
252
			$activated_text = $activated_count > 1 ? sprintf(
253
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
254
				__( '%1$s and %2$s', 'jetpack' ),
255
				join( ', ', $activated ), $activated_last ) : $activated_last;
256
257
			$error = sprintf(
258
				/* Translators: the variable is a module name. */
259
				_n( 'The module %s was activated.', 'The modules %s were activated.', $activated_count, 'jetpack' ),
260
				$activated_text ) . ' ';
261
		}
262
263
		$failed_count = count( $failed );
264 View Code Duplication
		if ( count( $failed ) > 0 ) {
265
			$failed_last = array_pop( $failed );
266
			$failed_text = $failed_count > 1 ? sprintf(
267
				/* Translators: first variable is a list followed by the last item, which is the second variable. Example: dog, cat and bird. */
268
				__( '%1$s and %2$s', 'jetpack' ),
269
				join( ', ', $failed ), $failed_last ) : $failed_last;
270
271
			$error = sprintf(
272
				/* Translators: the variable is a module name. */
273
				_n( 'The module %s failed to be activated.', 'The modules %s failed to be activated.', $failed_count, 'jetpack' ),
274
				$failed_text ) . ' ';
275
		}
276
277
		return new WP_Error(
278
			'activation_failed',
279
			esc_html( $error ),
280
			array( 'status' => 424 )
281
		);
282
	}
283
284
	/**
285
	 * A WordPress REST API permission callback method that accepts a request object and decides
286
	 * if the current user has enough privileges to act.
287
	 *
288
	 * @since 4.3.0
289
	 *
290
	 * @param WP_REST_Request $request
291
	 *
292
	 * @return bool does the current user have enough privilege.
293
	 */
294
	public function can_request( $request ) {
295
		if ( 'GET' === $request->get_method() ) {
296
			return current_user_can( 'jetpack_admin_page' );
297
		} else {
298
			return current_user_can( 'jetpack_manage_modules' );
299
		}
300
	}
301
}
302
303
/**
304
 * Class that manages updating of Jetpack module options and general Jetpack settings or retrieving module data.
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
	 *
316
	 * @since 4.3.0
317
	 *
318
	 * @param WP_REST_Request $data
319
	 *
320
	 * @return bool|mixed|void|WP_Error
321
	 */
322
	public function process( $data ) {
323
		if ( 'GET' === $data->get_method() ) {
324
			return $this->get_module( $data );
325
		} else {
326
			return $this->update_data( $data );
327
		}
328
	}
329
330
	/**
331
	 * Get information about a specific and valid Jetpack module.
332
	 *
333
	 * @since 4.3.0
334
	 *
335
	 * @param WP_REST_Request $data {
336
	 *     Array of parameters received by request.
337
	 *
338
	 *     @type string $slug Module slug.
339
	 * }
340
	 *
341
	 * @return mixed|void|WP_Error
342
	 */
343
	public function get_module( $data ) {
344
		if ( Jetpack::is_module( $data['slug'] ) ) {
345
346
			$module = Jetpack::get_module( $data['slug'] );
347
348
			$module['options'] = Jetpack_Core_Json_Api_Endpoints::prepare_options_for_response( $data['slug'] );
349
350
			if (
351
				isset( $module['requires_connection'] )
352
				&& $module['requires_connection']
353
				&& Jetpack::is_development_mode()
354
			) {
355
				$module['activated'] = false;
356
			}
357
358
			$i18n = jetpack_get_module_i18n( $data['slug'] );
359
			if ( isset( $module['name'] ) ) {
360
				$module['name'] = $i18n['name'];
361
			}
362
			if ( isset( $module['description'] ) ) {
363
				$module['description'] = $i18n['description'];
364
				$module['short_description'] = $i18n['description'];
365
			}
366
367
			return Jetpack_Core_Json_Api_Endpoints::prepare_modules_for_response( $module );
0 ignored issues
show
Documentation introduced by
$module is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
368
		}
369
370
		return new WP_Error(
371
			'not_found',
372
			esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ),
373
			array( 'status' => 404 )
374
		);
375
	}
376
377
	/**
378
	 * If it's a valid Jetpack module and configuration parameters have been sent, update it.
379
	 *
380
	 * @since 4.3.0
381
	 *
382
	 * @param WP_REST_Request $data {
383
	 *     Array of parameters received by request.
384
	 *
385
	 *     @type string $slug Module slug.
386
	 * }
387
	 *
388
	 * @return bool|WP_Error True if module was updated. Otherwise, a WP_Error instance with the corresponding error.
389
	 */
390
	public function update_data( $data ) {
391
392
		// If it's null, we're trying to update many module options from different modules.
393
		if ( is_null( $data['slug'] ) ) {
394
395
			// Value admitted by Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list that will make it return all module options.
396
			// It will not be passed. It's just checked in this method to pass that method a string or array.
397
			$data['slug'] = 'any';
398
		} else {
399 View Code Duplication
			if ( ! Jetpack::is_module( $data['slug'] ) ) {
400
				return new WP_Error( 'not_found', esc_html__( 'The requested Jetpack module was not found.', 'jetpack' ), array( 'status' => 404 ) );
401
			}
402
403 View Code Duplication
			if ( ! Jetpack::is_module_active( $data['slug'] ) ) {
404
				return new WP_Error( 'inactive', esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' ), array( 'status' => 409 ) );
405
			}
406
		}
407
408
		// Get parameters to update the module.
409
		$params = $data->get_json_params();
410
411
		// Exit if no parameters were passed.
412 View Code Duplication
		if ( ! is_array( $params ) ) {
413
			return new WP_Error( 'missing_options', esc_html__( 'Missing options.', 'jetpack' ), array( 'status' => 404 ) );
414
		}
415
416
		// Get available module options.
417
		$options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'any' === $data['slug']
418
			? $params
419
			: $data['slug']
420
		);
421
422
		// Prepare to toggle module if needed
423
		$toggle_module = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() );
424
425
		// Options that are invalid or failed to update.
426
		$invalid = array_keys( array_diff_key( $params, $options ) );
427
		$not_updated = array();
428
429
		// Remove invalid options
430
		$params = array_intersect_key( $params, $options );
431
432
		// Used if response is successful. The message can be overwritten and additional data can be added here.
433
		$response = array(
434
			'code'	  => 'success',
435
			'message' => esc_html__( 'The requested Jetpack data updates were successful.', 'jetpack' ),
436
		);
437
438
		// If there are modules to activate, activate them first so they're ready when their options are set.
439
		foreach ( $params as $option => $value ) {
440
			if ( 'modules' === $options[ $option ]['jp_group'] ) {
441
442
				// Used if there was an error. Can be overwritten with specific error messages.
443
				$error = '';
444
445
				// Set to true if the module toggling was successful.
446
				$updated = false;
447
448
				// Check if user can toggle the module.
449
				if ( $toggle_module->can_request() ) {
450
451
					// Activate or deactivate the module according to the value passed.
452
					$toggle_result = $value
453
						? $toggle_module->activate_module( $option )
454
						: $toggle_module->deactivate_module( $option );
455
456
					if ( is_wp_error( $toggle_result ) ) {
457
						$error = $toggle_result->get_error_message();
458
					} else {
459
						$updated = true;
460
					}
461
				} else {
462
					$error = Jetpack_Core_Json_Api_Endpoints::$user_permissions_error_msg;
463
				}
464
465
				// The module was not toggled.
466
				if ( ! $updated ) {
467
					$not_updated[ $option ] = $error;
468
				}
469
470
				// Remove module from list so we don't go through it again.
471
				unset( $params[ $option ] );
472
			}
473
		}
474
475
		foreach ( $params as $option => $value ) {
476
477
			// Used if there was an error. Can be overwritten with specific error messages.
478
			$error = '';
479
480
			// Set to true if the option update was successful.
481
			$updated = false;
482
483
			// Get option attributes, including the group it belongs to.
484
			$option_attrs = $options[ $option ];
485
486
			// If this is a module option and the related module isn't active for any reason, continue with the next one.
487
			if ( 'settings' !== $option_attrs['jp_group'] ) {
488 View Code Duplication
				if ( ! Jetpack::is_module( $option_attrs['jp_group'] ) ) {
489
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module was not found.', 'jetpack' );
490
					continue;
491
				}
492
493 View Code Duplication
				if ( ! Jetpack::is_module_active( $option_attrs['jp_group'] ) ) {
494
					$not_updated[ $option ] = esc_html__( 'The requested Jetpack module is inactive.', 'jetpack' );
495
					continue;
496
				}
497
			}
498
499
			// Properly cast value based on its type defined in endpoint accepted args.
500
			$value = Jetpack_Core_Json_Api_Endpoints::cast_value( $value, $option_attrs );
501
502
			switch ( $option ) {
503
				case 'monitor_receive_notifications':
504
					$monitor = new Jetpack_Monitor();
505
506
					// If we got true as response, consider it done.
507
					$updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value );
508
					break;
509
510
				case 'post_by_email_address':
511
					if ( 'create' == $value ) {
512
						$result = $this->_process_post_by_email(
513
							'jetpack.createPostByEmailAddress',
514
							esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' )
515
						);
516
					} elseif ( 'regenerate' == $value ) {
517
						$result = $this->_process_post_by_email(
518
							'jetpack.regeneratePostByEmailAddress',
519
							esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' )
520
						);
521
					} elseif ( 'delete' == $value ) {
522
						$result = $this->_process_post_by_email(
523
							'jetpack.deletePostByEmailAddress',
524
							esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' )
525
						);
526
					} else {
527
						$result = false;
528
					}
529
530
					// If we got an email address (create or regenerate) or 1 (delete), consider it done.
531
					if ( preg_match( '/[a-z0-9][email protected]/', $result ) ) {
532
						$response[$option] = $result;
533
						$updated           = true;
534
					} elseif ( 1 == $result ) {
535
						$updated = true;
536
					} elseif ( is_array( $result ) && isset( $result['message'] ) ) {
537
						$error = $result['message'];
538
					}
539
					break;
540
541
				case 'jetpack_protect_key':
542
					$protect = Jetpack_Protect_Module::instance();
543
					if ( 'create' == $value ) {
544
						$result = $protect->get_protect_key();
545
					} else {
546
						$result = false;
547
					}
548
549
					// If we got one of Protect keys, consider it done.
550
					if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) {
551
						$response[$option] = $result;
552
						$updated           = true;
553
					}
554
					break;
555
556
				case 'jetpack_protect_global_whitelist':
557
					$updated = jetpack_protect_save_whitelist( explode( PHP_EOL, str_replace( array( ' ', ',' ), array( '', "\n" ), $value ) ) );
558
					if ( is_wp_error( $updated ) ) {
559
						$error = $updated->get_error_message();
560
					}
561
					break;
562
563
				case 'show_headline':
564
				case 'show_thumbnails':
565
					$grouped_options          = $grouped_options_current = (array) Jetpack_Options::get_option( 'relatedposts' );
566
					$grouped_options[$option] = $value;
567
568
					// If option value was the same, consider it done.
569
					$updated = $grouped_options_current != $grouped_options ? Jetpack_Options::update_option( 'relatedposts', $grouped_options ) : true;
570
					break;
571
572
				case 'google':
573
				case 'bing':
574
				case 'pinterest':
575 View Code Duplication
				case 'yandex':
576
					$grouped_options          = $grouped_options_current = (array) get_option( 'verification_services_codes' );
577
					$grouped_options[$option] = $value;
578
579
					// If option value was the same, consider it done.
580
					$updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true;
581
					break;
582
583
				case 'sharing_services':
584
					$sharer = new Sharing_Service();
585
586
					// If option value was the same, consider it done.
587
					$updated = $value != $sharer->get_blog_services() ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) : true;
588
					break;
589
590
				case 'button_style':
591
				case 'sharing_label':
592
				case 'show':
593
					$sharer                   = new Sharing_Service();
594
					$grouped_options          = $sharer->get_global_options();
595
					$grouped_options[$option] = $value;
596
					$updated                  = $sharer->set_global_options( $grouped_options );
597
					break;
598
599
				case 'custom':
600
					$sharer  = new Sharing_Service();
601
					$updated = $sharer->new_service( stripslashes( $value['sharing_name'] ), stripslashes( $value['sharing_url'] ), stripslashes( $value['sharing_icon'] ) );
602
603
					// Return new custom service
604
					$response[$option] = $updated;
605
					break;
606
607
				case 'sharing_delete_service':
608
					$sharer  = new Sharing_Service();
609
					$updated = $sharer->delete_service( $value );
610
					break;
611
612
				case 'jetpack-twitter-cards-site-tag':
613
					$value   = trim( ltrim( strip_tags( $value ), '@' ) );
614
					$updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true;
615
					break;
616
617
				case 'onpublish':
618
				case 'onupdate':
619
				case 'Bias Language':
620
				case 'Cliches':
621
				case 'Complex Expression':
622
				case 'Diacritical Marks':
623
				case 'Double Negative':
624
				case 'Hidden Verbs':
625
				case 'Jargon Language':
626
				case 'Passive voice':
627
				case 'Phrases to Avoid':
628
				case 'Redundant Expression':
629
				case 'guess_lang':
630
					if ( in_array( $option, array( 'onpublish', 'onupdate' ) ) ) {
631
						$atd_option = 'AtD_check_when';
632
					} elseif ( 'guess_lang' == $option ) {
633
						$atd_option = 'AtD_guess_lang';
634
						$option     = 'true';
635
					} else {
636
						$atd_option = 'AtD_options';
637
					}
638
					$user_id                 = get_current_user_id();
639
					$grouped_options_current = AtD_get_options( $user_id, $atd_option );
640
					unset( $grouped_options_current['name'] );
641
					$grouped_options = $grouped_options_current;
642
					if ( $value && ! isset( $grouped_options [$option] ) ) {
643
						$grouped_options [$option] = $value;
644
					} elseif ( ! $value && isset( $grouped_options [$option] ) ) {
645
						unset( $grouped_options [$option] );
646
					}
647
					// If option value was the same, consider it done, otherwise try to update it.
648
					$options_to_save = implode( ',', array_keys( $grouped_options ) );
649
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $options_to_save ) : true;
650
					break;
651
652
				case 'ignored_phrases':
653
				case 'unignore_phrase':
654
					$user_id         = get_current_user_id();
655
					$atd_option      = 'AtD_ignored_phrases';
656
					$grouped_options = $grouped_options_current = explode( ',', AtD_get_setting( $user_id, $atd_option ) );
657
					if ( 'ignored_phrases' == $option ) {
658
						$grouped_options = explode( ',', $value );
659
					} else {
660
						$index = array_search( $value, $grouped_options );
661
						if ( false !== $index ) {
662
							unset( $grouped_options[$index] );
663
							$grouped_options = array_values( $grouped_options );
664
						}
665
					}
666
					$ignored_phrases = implode( ',', array_filter( array_map( 'strip_tags', $grouped_options ) ) );
667
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $ignored_phrases ) : true;
668
					break;
669
670
				case 'admin_bar':
671
				case 'roles':
672
				case 'count_roles':
673
				case 'blog_id':
674
				case 'do_not_track':
675
				case 'hide_smile':
676 View Code Duplication
				case 'version':
677
					$grouped_options          = $grouped_options_current = (array) get_option( 'stats_options' );
678
					$grouped_options[$option] = $value;
679
680
					// If option value was the same, consider it done.
681
					$updated = $grouped_options_current != $grouped_options ? update_option( 'stats_options', $grouped_options ) : true;
682
					break;
683
684
				case Jetpack_Core_Json_Api_Endpoints::holiday_snow_option_name():
685
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? 'letitsnow' : '' ) : true;
686
					break;
687
688
				case 'wp_mobile_featured_images':
689
				case 'wp_mobile_excerpt':
690
					$value = ( 'enabled' === $value ) ? '1' : '0';
691
				// break intentionally omitted
692
				default:
693
					// If option value was the same, consider it done.
694
					$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
695
					break;
696
			}
697
698
			// The option was not updated.
699
			if ( ! $updated ) {
700
				$not_updated[ $option ] = $error;
701
			}
702
		}
703
704
		if ( empty( $invalid ) && empty( $not_updated ) ) {
705
			// The option was updated.
706
			return rest_ensure_response( $response );
707
		} else {
708
			$invalid_count = count( $invalid );
709
			$not_updated_count = count( $not_updated );
710
			$error = '';
711
			if ( $invalid_count > 0 ) {
712
				$error = sprintf(
713
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
714
					_n( 'Invalid option: %s.', 'Invalid options: %s.', $invalid_count, 'jetpack' ),
715
					join( ', ', $invalid )
716
				);
717
			}
718
			if ( $not_updated_count > 0 ) {
719
				$not_updated_messages = array();
720
				foreach ( $not_updated as $not_updated_option => $not_updated_message ) {
721
					if ( ! empty( $not_updated_message ) ) {
722
						$not_updated_messages[] = sprintf(
723
						/* Translators: the first variable is a module option or slug, or setting. The second is the error message . */
724
							__( 'Extra info for %1$s: %2$s', 'jetpack' ),
725
							$not_updated_option, $not_updated_message );
726
					}
727
				}
728
				if ( ! empty( $error ) ) {
729
					$error .= ' ';
730
				}
731
				$error .= sprintf(
732
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
733
					_n( 'Option not updated: %s.', 'Options not updated: %s.', $not_updated_count, 'jetpack' ),
734
					join( ', ', array_keys( $not_updated ) ) );
735
				if ( ! empty( $not_updated_messages ) ) {
736
					$error .= ' ' . join( '. ', $not_updated_messages );
737
				}
738
739
			}
740
			// There was an error because some options were updated but others were invalid or failed to update.
741
			return new WP_Error( 'some_updated', esc_html( $error ), array( 'status' => 400 ) );
742
		}
743
744
	}
745
746
	/**
747
	 * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
748
	 * @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...
749
	 *
750
	 * @since 4.3.0
751
	 *
752
	 * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
753
	 * @param string $error	   Error message to return.
754
	 *
755
	 * @return array
756
	 */
757
	private function _process_post_by_email( $endpoint, $error ) {
758
		if ( ! current_user_can( 'edit_posts' ) ) {
759
			return array( 'message' => $error );
760
		}
761
762
		$this->xmlrpc->query( $endpoint );
763
764
		if ( $this->xmlrpc->isError() ) {
765
			return array( 'message' => $error );
766
		}
767
768
		$response = $this->xmlrpc->getResponse();
769
		if ( empty( $response ) ) {
770
			return array( 'message' => $error );
771
		}
772
773
		// Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
774
		update_option( 'post_by_email_address' . get_current_user_id(), $response );
775
776
		return $response;
777
	}
778
779
	/**
780
	 * Check if user is allowed to perform the update.
781
	 *
782
	 * @since 4.3.0
783
	 *
784
	 * @param WP_REST_Request $request
785
	 *
786
	 * @return bool
787
	 */
788
	public function can_request( $request ) {
789
		if ( 'GET' === $request->get_method() ) {
790
			return current_user_can( 'jetpack_admin_page' );
791
		} else {
792
			$module = Jetpack_Core_Json_Api_Endpoints::get_module_requested();
793
			// User is trying to create, regenerate or delete its PbE || ATD settings.
794
			if ( 'post-by-email' === $module || 'after-the-deadline' === $module ) {
795
				return current_user_can( 'edit_posts' ) && current_user_can( 'jetpack_admin_page' );
796
			}
797
			return current_user_can( 'jetpack_configure_modules' );
798
		}
799
	}
800
}
801
802
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...
803
804
	public function process( $request ) {
805
		switch( $request['slug'] ) {
806
			case 'protect':
807
				return $this->get_protect_data();
808
			case 'stats':
809
				return $this->get_stats_data( $request );
810
			case 'akismet':
811
				return $this->get_akismet_data();
812
			case 'monitor':
813
				return $this->get_monitor_data();
814
			case 'verification-tools':
815
				return $this->get_verification_tools_data();
816
			case 'vaultpress':
817
				return $this->get_vaultpress_data();
818
		}
819
	}
820
821
	/**
822
	 * Get number of blocked intrusion attempts.
823
	 *
824
	 * @since 4.3.0
825
	 *
826
	 * @return mixed|WP_Error Number of blocked attempts if protection is enabled. Otherwise, a WP_Error instance with the corresponding error.
827
	 */
828
	public function get_protect_data() {
829
		if ( Jetpack::is_module_active( 'protect' ) ) {
830
			return get_site_option( 'jetpack_protect_blocked_attempts' );
831
		}
832
833
		return new WP_Error(
834
			'not_active',
835
			esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
836
			array( 'status' => 404 )
837
		);
838
	}
839
840
	/**
841
	 * Get number of spam messages blocked by Akismet.
842
	 *
843
	 * @since 4.3.0
844
	 *
845
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
846
	 */
847
	public function get_akismet_data() {
848
		if ( ! is_wp_error( $status = $this->akismet_is_active_and_registered() ) ) {
849
			return rest_ensure_response( Akismet_Admin::get_stats( Akismet::get_api_key() ) );
850
		} else {
851
			return $status->get_error_code();
852
		}
853
	}
854
855
	/**
856
	 * Is Akismet registered and active?
857
	 *
858
	 * @since 4.3.0
859
	 *
860
	 * @return bool|WP_Error True if Akismet is active and registered. Otherwise, a WP_Error instance with the corresponding error.
861
	 */
862
	private function akismet_is_active_and_registered() {
863
		if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
864
			return new WP_Error( 'not_installed', esc_html__( 'Please install Akismet.', 'jetpack' ), array( 'status' => 400 ) );
865
		}
866
867 View Code Duplication
		if ( ! class_exists( 'Akismet' ) ) {
868
			return new WP_Error( 'not_active', esc_html__( 'Please activate Akismet.', 'jetpack' ), array( 'status' => 400 ) );
869
		}
870
871
		// What about if Akismet is put in a sub-directory or maybe in mu-plugins?
872
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
873
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
874
		$akismet_key = Akismet::verify_key( Akismet::get_api_key() );
875
876
		if ( ! $akismet_key || 'invalid' === $akismet_key || 'failed' === $akismet_key ) {
877
			return new WP_Error( 'invalid_key', esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), array( 'status' => 400 ) );
878
		}
879
880
		return true;
881
	}
882
883
	/**
884
	 * Get stats data for this site
885
	 *
886
	 * @since 4.1.0
887
	 *
888
	 * @param WP_REST_Request $data {
889
	 *     Array of parameters received by request.
890
	 *
891
	 *     @type string $date Date range to restrict results to.
892
	 * }
893
	 *
894
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
895
	 */
896
	public function get_stats_data( WP_REST_Request $data ) {
897
		// Get parameters to fetch Stats data.
898
		$range = $data->get_param( 'range' );
899
900
		// If no parameters were passed.
901
		if (
902
			empty ( $range )
903
			|| ! in_array( $range, array( 'day', 'week', 'month' ), true )
904
		) {
905
			$range = 'day';
906
		}
907
908
		if ( ! function_exists( 'stats_get_from_restapi' ) ) {
909
			require_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
910
		}
911
912
		switch ( $range ) {
913
914
			// This is always called first on page load
915
			case 'day':
916
				$initial_stats = stats_get_from_restapi();
917
				return rest_ensure_response( array(
918
					'general' => $initial_stats,
919
920
					// Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' );
921
					'day' => isset( $initial_stats->visits )
922
						? $initial_stats->visits
923
						: array(),
924
				) );
925
			case 'week':
926
				return rest_ensure_response( array(
927
					'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ),
928
				) );
929
			case 'month':
930
				return rest_ensure_response( array(
931
					'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ),
932
				) );
933
		}
934
	}
935
936
	/**
937
	 * Get date of last downtime.
938
	 *
939
	 * @since 4.3.0
940
	 *
941
	 * @return mixed|WP_Error Number of days since last downtime. Otherwise, a WP_Error instance with the corresponding error.
942
	 */
943
	public function get_monitor_data() {
944 View Code Duplication
		if ( ! Jetpack::is_module_active( 'monitor' ) ) {
945
			return new WP_Error(
946
				'not_active',
947
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
948
				array( 'status' => 404 )
949
			);
950
		}
951
952
		$monitor       = new Jetpack_Monitor();
953
		$last_downtime = $monitor->monitor_get_last_downtime();
954
		if ( is_wp_error( $last_downtime ) ) {
955
			return $last_downtime;
956
		} else if ( false === strtotime( $last_downtime ) ) {
957
			return rest_ensure_response( array(
958
				'code' => 'success',
959
				'date' => null,
960
			) );
961
		} else {
962
			return rest_ensure_response( array(
963
				'code' => 'success',
964
				'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ),
965
			) );
966
		}
967
	}
968
969
	/**
970
	 * Get services that this site is verified with.
971
	 *
972
	 * @since 4.3.0
973
	 *
974
	 * @return mixed|WP_Error List of services that verified this site. Otherwise, a WP_Error instance with the corresponding error.
975
	 */
976
	public function get_verification_tools_data() {
977 View Code Duplication
		if ( ! Jetpack::is_module_active( 'verification-tools' ) ) {
978
			return new WP_Error(
979
				'not_active',
980
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
981
				array( 'status' => 404 )
982
			);
983
		}
984
985
		$verification_services_codes = get_option( 'verification_services_codes' );
986 View Code Duplication
		if (
987
			! is_array( $verification_services_codes )
988
			|| empty( $verification_services_codes )
989
		) {
990
			return new WP_Error(
991
				'empty',
992
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
993
				array( 'status' => 404 )
994
			);
995
		}
996
997
		$services = array();
998
		foreach ( jetpack_verification_services() as $name => $service ) {
999
			if ( is_array( $service ) && ! empty( $verification_services_codes[ $name ] ) ) {
1000
				switch ( $name ) {
1001
					case 'google':
1002
						$services[] = 'Google';
1003
						break;
1004
					case 'bing':
1005
						$services[] = 'Bing';
1006
						break;
1007
					case 'pinterest':
1008
						$services[] = 'Pinterest';
1009
						break;
1010
					case 'yandex':
1011
						$services[] = 'Yandex';
1012
						break;
1013
				}
1014
			}
1015
		}
1016
1017
		if ( empty( $services ) ) {
1018
			return new WP_Error(
1019
				'empty',
1020
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1021
				array( 'status' => 404 )
1022
			);
1023
		}
1024
1025
		if ( 2 > count( $services ) ) {
1026
			$message = esc_html(
1027
				sprintf(
1028
					/* translators: %s is a service name like Google, Bing, Pinterest, etc. */
1029
					__( 'Your site is verified with %s.', 'jetpack' ),
1030
					$services[0]
1031
				)
1032
			);
1033
		} else {
1034
			$copy_services = $services;
1035
			$last = count( $copy_services ) - 1;
1036
			$last_service = $copy_services[ $last ];
1037
			unset( $copy_services[ $last ] );
1038
			$message = esc_html(
1039
				sprintf(
1040
					/* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1041
					__( 'Your site is verified with %1$s and %2$s.', 'jetpack' ),
1042
					join( ', ', $copy_services ),
1043
					$last_service
1044
				)
1045
			);
1046
		}
1047
1048
		return rest_ensure_response( array(
1049
			'code'     => 'success',
1050
			'message'  => $message,
1051
			'services' => $services,
1052
		) );
1053
	}
1054
1055
	/**
1056
	 * Get VaultPress site data including, among other things, the date of the last backup if it was completed.
1057
	 *
1058
	 * @since 4.3.0
1059
	 *
1060
	 * @return mixed|WP_Error VaultPress site data. Otherwise, a WP_Error instance with the corresponding error.
1061
	 */
1062
	public function get_vaultpress_data() {
1063 View Code Duplication
		if ( ! class_exists( 'VaultPress' ) ) {
1064
			return new WP_Error(
1065
				'not_active',
1066
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1067
				array( 'status' => 404 )
1068
			);
1069
		}
1070
1071
		$vaultpress = new VaultPress();
1072
		if ( ! $vaultpress->is_registered() ) {
1073
			return rest_ensure_response( array(
1074
				'code'    => 'not_registered',
1075
				'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' )
1076
			) );
1077
		}
1078
1079
		$data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) );
1080
		if ( is_wp_error( $data ) ) {
1081
			return $data;
1082
		} else if ( ! $data->backups->last_backup ) {
1083
			return rest_ensure_response( array(
1084
				'code'    => 'success',
1085
				'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ),
1086
				'data'    => $data,
1087
			) );
1088
		} else {
1089
			return rest_ensure_response( array(
1090
				'code'    => 'success',
1091
				'message' => esc_html(
1092
					sprintf(
1093
						__( 'Your site was successfully backed-up %s ago.', 'jetpack' ),
1094
						human_time_diff(
1095
							$data->backups->last_backup,
1096
							current_time( 'timestamp' )
1097
						)
1098
					)
1099
				),
1100
				'data'    => $data,
1101
			) );
1102
		}
1103
	}
1104
1105
	/**
1106
	 * A WordPress REST API permission callback method that accepts a request object and
1107
	 * decides if the current user has enough privileges to act.
1108
	 *
1109
	 * @since 4.3.0
1110
	 *
1111
	 * @param WP_REST_Request $request
0 ignored issues
show
Bug introduced by
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1112
	 *
1113
	 * @return bool does a current user have enough privileges.
1114
	 */
1115
	public function can_request() {
1116
		return current_user_can( 'jetpack_admin_page' );
1117
	}
1118
}
1119
1120
/**
1121
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1122
 *
1123
 * @since 4.3.1
1124
 */
1125
function jetpack_do_after_gravatar_hovercards_activation() {
1126
1127
	// When Gravatar Hovercards is activated, enable them automatically.
1128
	update_option( 'gravatar_disable_hovercards', 'enabled' );
1129
}
1130
add_action( 'jetpack_activate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_activation' );
1131
1132
/**
1133
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1134
 *
1135
 * @since 4.3.1
1136
 */
1137
function jetpack_do_after_gravatar_hovercards_deactivation() {
1138
1139
	// When Gravatar Hovercards is deactivated, disable them automatically.
1140
	update_option( 'gravatar_disable_hovercards', 'disabled' );
1141
}
1142
add_action( 'jetpack_deactivate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_deactivation' );