Completed
Push — add/react-videopress-settings ( 0e3d99...2de6fa )
by
unknown
768:38 queued 751:35
created

Jetpack_Core_API_Data::process()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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