Completed
Push — fix/fatal-sharing ( 07bde2...104337 )
by Jeremy
591:31 queued 581:47
created

Jetpack_Core_API_Data   D

Complexity

Total Complexity 121

Size/Duplication

Total Lines 505
Duplicated Lines 8.91 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 45
loc 505
rs 4.8717
c 0
b 0
f 0
wmc 121
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() 45 370 103
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 View Code Duplication
				case 'pinterest':
575
					$grouped_options          = $grouped_options_current = (array) get_option( 'verification_services_codes' );
576
					$grouped_options[$option] = $value;
577
578
					// If option value was the same, consider it done.
579
					$updated = $grouped_options_current != $grouped_options ? update_option( 'verification_services_codes', $grouped_options ) : true;
580
					break;
581
582 View Code Duplication
				case 'sharing_services':
583
					if ( ! class_exists( 'Sharing_Service' ) && ! @include( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
584
						break;
585
					}
586
587
					$sharer = new Sharing_Service();
588
589
					// If option value was the same, consider it done.
590
					$updated = $value != $sharer->get_blog_services() ? $sharer->set_blog_services( $value['visible'], $value['hidden'] ) : true;
591
					break;
592
593
				case 'button_style':
594
				case 'sharing_label':
595
				case 'show':
596
					if ( ! class_exists( 'Sharing_Service' ) && ! @include( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
597
						break;
598
					}
599
600
					$sharer = new Sharing_Service();
601
					$grouped_options = $sharer->get_global_options();
602
					$grouped_options[ $option ] = $value;
603
					$updated = $sharer->set_global_options( $grouped_options );
604
					break;
605
606
				case 'custom':
607
					if ( ! class_exists( 'Sharing_Service' ) && ! @include( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
608
						break;
609
					}
610
611
					$sharer = new Sharing_Service();
612
					$updated = $sharer->new_service( stripslashes( $value['sharing_name'] ), stripslashes( $value['sharing_url'] ), stripslashes( $value['sharing_icon'] ) );
613
614
					// Return new custom service
615
					$response[$option] = $updated;
616
					break;
617
618
				case 'sharing_delete_service':
619
					if ( ! class_exists( 'Sharing_Service' ) && ! @include( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
620
						break;
621
					}
622
623
					$sharer = new Sharing_Service();
624
					$updated = $sharer->delete_service( $value );
625
					break;
626
627
				case 'jetpack-twitter-cards-site-tag':
628
					$value   = trim( ltrim( strip_tags( $value ), '@' ) );
629
					$updated = get_option( $option ) !== $value ? update_option( $option, $value ) : true;
630
					break;
631
632
				case 'onpublish':
633
				case 'onupdate':
634
				case 'Bias Language':
635
				case 'Cliches':
636
				case 'Complex Expression':
637
				case 'Diacritical Marks':
638
				case 'Double Negative':
639
				case 'Hidden Verbs':
640
				case 'Jargon Language':
641
				case 'Passive voice':
642
				case 'Phrases to Avoid':
643
				case 'Redundant Expression':
644
				case 'guess_lang':
645
					if ( in_array( $option, array( 'onpublish', 'onupdate' ) ) ) {
646
						$atd_option = 'AtD_check_when';
647
					} elseif ( 'guess_lang' == $option ) {
648
						$atd_option = 'AtD_guess_lang';
649
						$option     = 'true';
650
					} else {
651
						$atd_option = 'AtD_options';
652
					}
653
					$user_id                 = get_current_user_id();
654
					$grouped_options_current = AtD_get_options( $user_id, $atd_option );
655
					unset( $grouped_options_current['name'] );
656
					$grouped_options = $grouped_options_current;
657
					if ( $value && ! isset( $grouped_options [$option] ) ) {
658
						$grouped_options [$option] = $value;
659
					} elseif ( ! $value && isset( $grouped_options [$option] ) ) {
660
						unset( $grouped_options [$option] );
661
					}
662
					// If option value was the same, consider it done, otherwise try to update it.
663
					$options_to_save = implode( ',', array_keys( $grouped_options ) );
664
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $options_to_save ) : true;
665
					break;
666
667
				case 'ignored_phrases':
668
				case 'unignore_phrase':
669
					$user_id         = get_current_user_id();
670
					$atd_option      = 'AtD_ignored_phrases';
671
					$grouped_options = $grouped_options_current = explode( ',', AtD_get_setting( $user_id, $atd_option ) );
672
					if ( 'ignored_phrases' == $option ) {
673
						$grouped_options = explode( ',', $value );
674
					} else {
675
						$index = array_search( $value, $grouped_options );
676
						if ( false !== $index ) {
677
							unset( $grouped_options[$index] );
678
							$grouped_options = array_values( $grouped_options );
679
						}
680
					}
681
					$ignored_phrases = implode( ',', array_filter( array_map( 'strip_tags', $grouped_options ) ) );
682
					$updated         = $grouped_options != $grouped_options_current ? AtD_update_setting( $user_id, $atd_option, $ignored_phrases ) : true;
683
					break;
684
685
				case 'admin_bar':
686
				case 'roles':
687
				case 'count_roles':
688
				case 'blog_id':
689
				case 'do_not_track':
690
				case 'hide_smile':
691 View Code Duplication
				case 'version':
692
					$grouped_options          = $grouped_options_current = (array) get_option( 'stats_options' );
693
					$grouped_options[$option] = $value;
694
695
					// If option value was the same, consider it done.
696
					$updated = $grouped_options_current != $grouped_options ? update_option( 'stats_options', $grouped_options ) : true;
697
					break;
698
699
				case Jetpack_Core_Json_Api_Endpoints::holiday_snow_option_name():
700
					$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? 'letitsnow' : '' ) : true;
701
					break;
702
703
				case 'wp_mobile_featured_images':
704
				case 'wp_mobile_excerpt':
705
					$value = ( 'enabled' === $value ) ? '1' : '0';
706
				// break intentionally omitted
707
				default:
708
					// If option value was the same, consider it done.
709
					$updated = get_option( $option ) != $value ? update_option( $option, $value ) : true;
710
					break;
711
			}
712
713
			// The option was not updated.
714
			if ( ! $updated ) {
715
				$not_updated[ $option ] = $error;
716
			}
717
		}
718
719
		if ( empty( $invalid ) && empty( $not_updated ) ) {
720
			// The option was updated.
721
			return rest_ensure_response( $response );
722
		} else {
723
			$invalid_count = count( $invalid );
724
			$not_updated_count = count( $not_updated );
725
			$error = '';
726
			if ( $invalid_count > 0 ) {
727
				$error = sprintf(
728
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
729
					_n( 'Invalid option: %s.', 'Invalid options: %s.', $invalid_count, 'jetpack' ),
730
					join( ', ', $invalid )
731
				);
732
			}
733
			if ( $not_updated_count > 0 ) {
734
				$not_updated_messages = array();
735
				foreach ( $not_updated as $not_updated_option => $not_updated_message ) {
736
					if ( ! empty( $not_updated_message ) ) {
737
						$not_updated_messages[] = sprintf(
738
						/* Translators: the first variable is a module option or slug, or setting. The second is the error message . */
739
							__( 'Extra info for %1$s: %2$s', 'jetpack' ),
740
							$not_updated_option, $not_updated_message );
741
					}
742
				}
743
				if ( ! empty( $error ) ) {
744
					$error .= ' ';
745
				}
746
				$error .= sprintf(
747
				/* Translators: the plural variable is a comma-separated list. Example: dog, cat, bird. */
748
					_n( 'Option not updated: %s.', 'Options not updated: %s.', $not_updated_count, 'jetpack' ),
749
					join( ', ', array_keys( $not_updated ) ) );
750
				if ( ! empty( $not_updated_messages ) ) {
751
					$error .= ' ' . join( '. ', $not_updated_messages );
752
				}
753
754
			}
755
			// There was an error because some options were updated but others were invalid or failed to update.
756
			return new WP_Error( 'some_updated', esc_html( $error ), array( 'status' => 400 ) );
757
		}
758
759
	}
760
761
	/**
762
	 * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
763
	 * @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...
764
	 *
765
	 * @since 4.3.0
766
	 *
767
	 * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
768
	 * @param string $error	   Error message to return.
769
	 *
770
	 * @return array
771
	 */
772
	private function _process_post_by_email( $endpoint, $error ) {
773
		if ( ! current_user_can( 'edit_posts' ) ) {
774
			return array( 'message' => $error );
775
		}
776
777
		$this->xmlrpc->query( $endpoint );
778
779
		if ( $this->xmlrpc->isError() ) {
780
			return array( 'message' => $error );
781
		}
782
783
		$response = $this->xmlrpc->getResponse();
784
		if ( empty( $response ) ) {
785
			return array( 'message' => $error );
786
		}
787
788
		// Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
789
		update_option( 'post_by_email_address' . get_current_user_id(), $response );
790
791
		return $response;
792
	}
793
794
	/**
795
	 * Check if user is allowed to perform the update.
796
	 *
797
	 * @since 4.3.0
798
	 *
799
	 * @param WP_REST_Request $request
800
	 *
801
	 * @return bool
802
	 */
803
	public function can_request( $request ) {
804
		if ( 'GET' === $request->get_method() ) {
805
			return current_user_can( 'jetpack_admin_page' );
806
		} else {
807
			$module = Jetpack_Core_Json_Api_Endpoints::get_module_requested();
808
			// User is trying to create, regenerate or delete its PbE || ATD settings.
809
			if ( 'post-by-email' === $module || 'after-the-deadline' === $module ) {
810
				return current_user_can( 'edit_posts' ) && current_user_can( 'jetpack_admin_page' );
811
			}
812
			return current_user_can( 'jetpack_configure_modules' );
813
		}
814
	}
815
}
816
817
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...
818
819
	public function process( $request ) {
820
		switch( $request['slug'] ) {
821
			case 'protect':
822
				return $this->get_protect_data();
823
			case 'stats':
824
				return $this->get_stats_data( $request );
825
			case 'akismet':
826
				return $this->get_akismet_data();
827
			case 'monitor':
828
				return $this->get_monitor_data();
829
			case 'verification-tools':
830
				return $this->get_verification_tools_data();
831
			case 'vaultpress':
832
				return $this->get_vaultpress_data();
833
		}
834
	}
835
836
	/**
837
	 * Get number of blocked intrusion attempts.
838
	 *
839
	 * @since 4.3.0
840
	 *
841
	 * @return mixed|WP_Error Number of blocked attempts if protection is enabled. Otherwise, a WP_Error instance with the corresponding error.
842
	 */
843
	public function get_protect_data() {
844
		if ( Jetpack::is_module_active( 'protect' ) ) {
845
			return get_site_option( 'jetpack_protect_blocked_attempts' );
846
		}
847
848
		return new WP_Error(
849
			'not_active',
850
			esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
851
			array( 'status' => 404 )
852
		);
853
	}
854
855
	/**
856
	 * Get number of spam messages blocked by Akismet.
857
	 *
858
	 * @since 4.3.0
859
	 *
860
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
861
	 */
862
	public function get_akismet_data() {
863
		if ( ! is_wp_error( $status = $this->akismet_is_active_and_registered() ) ) {
864
			return rest_ensure_response( Akismet_Admin::get_stats( Akismet::get_api_key() ) );
865
		} else {
866
			return $status->get_error_code();
867
		}
868
	}
869
870
	/**
871
	 * Is Akismet registered and active?
872
	 *
873
	 * @since 4.3.0
874
	 *
875
	 * @return bool|WP_Error True if Akismet is active and registered. Otherwise, a WP_Error instance with the corresponding error.
876
	 */
877
	private function akismet_is_active_and_registered() {
878
		if ( ! file_exists( WP_PLUGIN_DIR . '/akismet/class.akismet.php' ) ) {
879
			return new WP_Error( 'not_installed', esc_html__( 'Please install Akismet.', 'jetpack' ), array( 'status' => 400 ) );
880
		}
881
882 View Code Duplication
		if ( ! class_exists( 'Akismet' ) ) {
883
			return new WP_Error( 'not_active', esc_html__( 'Please activate Akismet.', 'jetpack' ), array( 'status' => 400 ) );
884
		}
885
886
		// What about if Akismet is put in a sub-directory or maybe in mu-plugins?
887
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet.php';
888
		require_once WP_PLUGIN_DIR . '/akismet/class.akismet-admin.php';
889
		$akismet_key = Akismet::verify_key( Akismet::get_api_key() );
890
891
		if ( ! $akismet_key || 'invalid' === $akismet_key || 'failed' === $akismet_key ) {
892
			return new WP_Error( 'invalid_key', esc_html__( 'Invalid Akismet key. Please contact support.', 'jetpack' ), array( 'status' => 400 ) );
893
		}
894
895
		return true;
896
	}
897
898
	/**
899
	 * Get stats data for this site
900
	 *
901
	 * @since 4.1.0
902
	 *
903
	 * @param WP_REST_Request $data {
904
	 *     Array of parameters received by request.
905
	 *
906
	 *     @type string $date Date range to restrict results to.
907
	 * }
908
	 *
909
	 * @return int|string Number of spam blocked by Akismet. Otherwise, an error message.
910
	 */
911
	public function get_stats_data( WP_REST_Request $data ) {
912
		// Get parameters to fetch Stats data.
913
		$range = $data->get_param( 'range' );
914
915
		// If no parameters were passed.
916
		if (
917
			empty ( $range )
918
			|| ! in_array( $range, array( 'day', 'week', 'month' ), true )
919
		) {
920
			$range = 'day';
921
		}
922
923
		if ( ! function_exists( 'stats_get_from_restapi' ) ) {
924
			require_once( JETPACK__PLUGIN_DIR . 'modules/stats.php' );
925
		}
926
927
		switch ( $range ) {
928
929
			// This is always called first on page load
930
			case 'day':
931
				$initial_stats = stats_get_from_restapi();
932
				return rest_ensure_response( array(
933
					'general' => $initial_stats,
934
935
					// Build data for 'day' as if it was stats_get_from_restapi( array(), 'visits?unit=day&quantity=30' );
936
					'day' => isset( $initial_stats->visits )
937
						? $initial_stats->visits
938
						: array(),
939
				) );
940
			case 'week':
941
				return rest_ensure_response( array(
942
					'week' => stats_get_from_restapi( array(), 'visits?unit=week&quantity=14' ),
943
				) );
944
			case 'month':
945
				return rest_ensure_response( array(
946
					'month' => stats_get_from_restapi( array(), 'visits?unit=month&quantity=12&' ),
947
				) );
948
		}
949
	}
950
951
	/**
952
	 * Get date of last downtime.
953
	 *
954
	 * @since 4.3.0
955
	 *
956
	 * @return mixed|WP_Error Number of days since last downtime. Otherwise, a WP_Error instance with the corresponding error.
957
	 */
958
	public function get_monitor_data() {
959 View Code Duplication
		if ( ! Jetpack::is_module_active( 'monitor' ) ) {
960
			return new WP_Error(
961
				'not_active',
962
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
963
				array( 'status' => 404 )
964
			);
965
		}
966
967
		$monitor       = new Jetpack_Monitor();
968
		$last_downtime = $monitor->monitor_get_last_downtime();
969
		if ( is_wp_error( $last_downtime ) ) {
970
			return $last_downtime;
971
		} else if ( false === strtotime( $last_downtime ) ) {
972
			return rest_ensure_response( array(
973
				'code' => 'success',
974
				'date' => null,
975
			) );
976
		} else {
977
			return rest_ensure_response( array(
978
				'code' => 'success',
979
				'date' => human_time_diff( strtotime( $last_downtime ), strtotime( 'now' ) ),
980
			) );
981
		}
982
	}
983
984
	/**
985
	 * Get services that this site is verified with.
986
	 *
987
	 * @since 4.3.0
988
	 *
989
	 * @return mixed|WP_Error List of services that verified this site. Otherwise, a WP_Error instance with the corresponding error.
990
	 */
991
	public function get_verification_tools_data() {
992 View Code Duplication
		if ( ! Jetpack::is_module_active( 'verification-tools' ) ) {
993
			return new WP_Error(
994
				'not_active',
995
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
996
				array( 'status' => 404 )
997
			);
998
		}
999
1000
		$verification_services_codes = get_option( 'verification_services_codes' );
1001 View Code Duplication
		if (
1002
			! is_array( $verification_services_codes )
1003
			|| empty( $verification_services_codes )
1004
		) {
1005
			return new WP_Error(
1006
				'empty',
1007
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1008
				array( 'status' => 404 )
1009
			);
1010
		}
1011
1012
		$services = array();
1013
		foreach ( jetpack_verification_services() as $name => $service ) {
1014
			if ( is_array( $service ) && ! empty( $verification_services_codes[ $name ] ) ) {
1015
				switch ( $name ) {
1016
					case 'google':
1017
						$services[] = 'Google';
1018
						break;
1019
					case 'bing':
1020
						$services[] = 'Bing';
1021
						break;
1022
					case 'pinterest':
1023
						$services[] = 'Pinterest';
1024
						break;
1025
				}
1026
			}
1027
		}
1028
1029
		if ( empty( $services ) ) {
1030
			return new WP_Error(
1031
				'empty',
1032
				esc_html__( 'Site not verified with any service.', 'jetpack' ),
1033
				array( 'status' => 404 )
1034
			);
1035
		}
1036
1037
		if ( 2 > count( $services ) ) {
1038
			$message = esc_html(
1039
				sprintf(
1040
					/* translators: %s is a service name like Google, Bing, Pinterest, etc. */
1041
					__( 'Your site is verified with %s.', 'jetpack' ),
1042
					$services[0]
1043
				)
1044
			);
1045
		} else {
1046
			$copy_services = $services;
1047
			$last = count( $copy_services ) - 1;
1048
			$last_service = $copy_services[ $last ];
1049
			unset( $copy_services[ $last ] );
1050
			$message = esc_html(
1051
				sprintf(
1052
					/* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1053
					__( 'Your site is verified with %1$s and %2$s.', 'jetpack' ),
1054
					join( ', ', $copy_services ),
1055
					$last_service
1056
				)
1057
			);
1058
		}
1059
1060
		return rest_ensure_response( array(
1061
			'code'     => 'success',
1062
			'message'  => $message,
1063
			'services' => $services,
1064
		) );
1065
	}
1066
1067
	/**
1068
	 * Get VaultPress site data including, among other things, the date of the last backup if it was completed.
1069
	 *
1070
	 * @since 4.3.0
1071
	 *
1072
	 * @return mixed|WP_Error VaultPress site data. Otherwise, a WP_Error instance with the corresponding error.
1073
	 */
1074
	public function get_vaultpress_data() {
1075 View Code Duplication
		if ( ! class_exists( 'VaultPress' ) ) {
1076
			return new WP_Error(
1077
				'not_active',
1078
				esc_html__( 'The requested Jetpack module is not active.', 'jetpack' ),
1079
				array( 'status' => 404 )
1080
			);
1081
		}
1082
1083
		$vaultpress = new VaultPress();
1084
		if ( ! $vaultpress->is_registered() ) {
1085
			return rest_ensure_response( array(
1086
				'code'    => 'not_registered',
1087
				'message' => esc_html__( 'You need to register for VaultPress.', 'jetpack' )
1088
			) );
1089
		}
1090
1091
		$data = json_decode( base64_decode( $vaultpress->contact_service( 'plugin_data' ) ) );
1092
		if ( is_wp_error( $data ) ) {
1093
			return $data;
1094
		} else if ( ! $data->backups->last_backup ) {
1095
			return rest_ensure_response( array(
1096
				'code'    => 'success',
1097
				'message' => esc_html__( 'VaultPress is active and will back up your site soon.', 'jetpack' ),
1098
				'data'    => $data,
1099
			) );
1100
		} else {
1101
			return rest_ensure_response( array(
1102
				'code'    => 'success',
1103
				'message' => esc_html(
1104
					sprintf(
1105
						__( 'Your site was successfully backed-up %s ago.', 'jetpack' ),
1106
						human_time_diff(
1107
							$data->backups->last_backup,
1108
							current_time( 'timestamp' )
1109
						)
1110
					)
1111
				),
1112
				'data'    => $data,
1113
			) );
1114
		}
1115
	}
1116
1117
	/**
1118
	 * A WordPress REST API permission callback method that accepts a request object and
1119
	 * decides if the current user has enough privileges to act.
1120
	 *
1121
	 * @since 4.3.0
1122
	 *
1123
	 * @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...
1124
	 *
1125
	 * @return bool does a current user have enough privileges.
1126
	 */
1127
	public function can_request() {
1128
		return current_user_can( 'jetpack_admin_page' );
1129
	}
1130
}
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_activation() {
1138
1139
	// When Gravatar Hovercards is activated, enable them automatically.
1140
	update_option( 'gravatar_disable_hovercards', 'enabled' );
1141
}
1142
add_action( 'jetpack_activate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_activation' );
1143
1144
/**
1145
 * Actions performed only when Gravatar Hovercards is activated through the endpoint call.
1146
 *
1147
 * @since 4.3.1
1148
 */
1149
function jetpack_do_after_gravatar_hovercards_deactivation() {
1150
1151
	// When Gravatar Hovercards is deactivated, disable them automatically.
1152
	update_option( 'gravatar_disable_hovercards', 'disabled' );
1153
}
1154
add_action( 'jetpack_deactivate_module_gravatar-hovercards', 'jetpack_do_after_gravatar_hovercards_deactivation' );
1155