Completed
Push — add/plugin-new-api-endpoint ( eab5d8...51811e )
by
unknown
364:06 queued 355:53
created

Jetpack_Core_API_Data   D

Complexity

Total Complexity 113

Size/Duplication

Total Lines 489
Duplicated Lines 7.16 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

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