PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | _inc/lib/core-api/class.jetpack-core-api-module-endpoints.php +137 -37 13.9.216.3-a.1 View file →
@@ -12,8 +12,12 @@
12 12 use Automattic\Jetpack\Status;
13 13 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection;
14 14 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection_Shared_Functions;
15 15
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
16 20 /**
17 21 * This is the base class for every Core API endpoint Jetpack uses.
18 22 */
19 23 class Jetpack_Core_API_Module_Toggle_Endpoint extends Jetpack_Core_API_XMLRPC_Consumer_Endpoint {
@@ -401,15 +405,17 @@
401 405 $module['activated'] = false;
402 406 }
403 407
404 408 $i18n = jetpack_get_module_i18n( $request['slug'] );
405 - if ( isset( $module['name'] ) ) {
406 - $module['name'] = $i18n['name'];
409 + if ( $i18n ) {
410 + if ( isset( $module['name'] ) ) {
411 + $module['name'] = $i18n['name'];
412 + }
413 + if ( isset( $module['description'] ) ) {
414 + $module['description'] = $i18n['description'];
415 + $module['short_description'] = $i18n['description'];
416 + }
407 417 }
408 - if ( isset( $module['description'] ) ) {
409 - $module['description'] = $i18n['description'];
410 - $module['short_description'] = $i18n['description'];
411 - }
412 418 if ( isset( $module['module_tags'] ) ) {
413 419 $module['module_tags'] = array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] );
414 420 }
415 421
@@ -446,9 +452,11 @@
446 452 $response[ $module ] = Jetpack::is_module_active( $module );
447 453 }
448 454 }
449 455
450 - $settings = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'settings' );
456 + $settings = Jetpack_Core_Json_Api_Endpoints::filter_options_for_response(
457 + Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( 'settings' )
458 + );
451 459
452 460 if ( ! function_exists( 'is_plugin_active' ) ) {
453 461 require_once ABSPATH . 'wp-admin/includes/plugin.php';
454 462 }
@@ -490,9 +498,9 @@
490 498 $response[ $setting ] = 1;
491 499 break;
492 500
493 501 default:
494 - $default = isset( $settings[ $setting ]['default'] ) ? $settings[ $setting ]['default'] : false;
502 + $default = $settings[ $setting ]['default'] ?? false;
495 503 $response[ $setting ] = Jetpack_Core_Json_Api_Endpoints::cast_value( get_option( $setting, $default ), $settings[ $setting ] );
496 504 break;
497 505 }
498 506 }
@@ -498,25 +506,23 @@
498 506 }
499 507
500 508 $response['akismet'] = is_plugin_active( 'akismet/akismet.php' );
501 509
510 + require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
511 + if ( class_exists( 'Jetpack_Memberships' ) ) {
512 + $response['newsletter_has_active_plan'] = count( Jetpack_Memberships::get_all_newsletter_plan_ids( false ) ) > 0;
513 + }
514 +
515 + // Make sure we are returning a consistent type
516 + if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) {
517 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php';
518 + }
519 + $response['wpcom_newsletter_categories'] = Jetpack_Newsletter_Category_Helper::get_category_ids();
520 +
502 521 return rest_ensure_response( $response );
503 522 }
504 523
505 524 /**
506 - * Decode the special HTML characters in a certain value.
507 - *
508 - * @since 5.8
509 - *
510 - * @param string $value Value to decode.
511 - *
512 - * @return string Value with decoded HTML characters.
513 - */
514 - private function decode_special_characters( $value ) {
515 - return (string) htmlspecialchars_decode( $value, ENT_QUOTES );
516 - }
517 -
518 - /**
519 525 * If it's a valid Jetpack module and configuration parameters have been sent, update it.
520 526 *
521 527 * @since 4.3.0
522 528 *
@@ -547,9 +553,9 @@
547 553 }
548 554
549 555 /*
550 556 * Get parameters to update the module.
551 - * We can not simply use $request->get_params() because when we registered this route,
557 + * We cannot simply use $request->get_params() because when we registered this route,
552 558 * we are adding the entire output of Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list()
553 559 * to the current request object's params. We are interested in body of the actual request.
554 560 * This may be JSON:
555 561 */
@@ -634,13 +640,22 @@
634 640 if ( ! $updated ) {
635 641 $not_updated[ $option ] = $error;
636 642 }
637 643
644 + if ( $updated ) {
645 + // Return the module state.
646 + $response[ $option ] = $value;
647 + }
648 +
638 649 // Remove module from list so we don't go through it again.
639 650 unset( $params[ $option ] );
640 651 }
641 652 }
642 653
654 + if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) {
655 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php';
656 + }
657 +
643 658 foreach ( $params as $option => $value ) {
644 659
645 660 // Used if there was an error. Can be overwritten with specific error messages.
646 661 $error = '';
@@ -650,8 +665,14 @@
650 665
651 666 // Get option attributes, including the group it belongs to.
652 667 $option_attrs = $options[ $option ];
653 668
669 + // Everything outside the Post by Email group requires the admin capability.
670 + if ( 'post-by-email' !== $option_attrs['jp_group'] && ! current_user_can( 'jetpack_configure_modules' ) ) {
671 + $not_updated[ $option ] = REST_Connector::get_user_permissions_error_msg();
672 + continue;
673 + }
674 +
654 675 // If this is a module option and the related module isn't active for any reason, continue with the next one.
655 676 if ( 'settings' !== $option_attrs['jp_group'] ) {
656 677 if ( ! Jetpack::is_module( $option_attrs['jp_group'] ) ) {
657 678 $not_updated[ $option ] = esc_html__( 'The requested Jetpack module was not found.', 'jetpack' );
@@ -702,8 +723,13 @@
702 723 $updated = get_option( 'WPLANG' ) === $language ? true : update_option( 'WPLANG', $language );
703 724 break;
704 725
705 726 case 'monitor_receive_notifications':
727 + if ( ! class_exists( 'Jetpack_Monitor' ) ) {
728 + $updated = false;
729 + break;
730 + }
731 +
706 732 $monitor = new Jetpack_Monitor();
707 733
708 734 // If we got true as response, consider it done.
709 735 $updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value );
@@ -709,8 +735,13 @@
709 735 $updated = true === $monitor->update_option_receive_jetpack_monitor_notification( $value );
710 736 break;
711 737
712 738 case 'post_by_email_address':
739 + if ( ! class_exists( 'Jetpack_Post_By_Email' ) ) {
740 + $updated = false;
741 + break;
742 + }
743 +
713 744 $result = Jetpack_Post_By_Email::init()->process_api_request( $value );
714 745
715 746 // If we got an email address (create or regenerate) or 1 (delete), consider it done.
716 747 if ( is_string( $result ) && preg_match( '/[a-z0-9]+@post.wordpress.com/', $result ) ) {
@@ -731,9 +762,9 @@
731 762 $result = false;
732 763 }
733 764
734 765 // If we got one of Protect keys, consider it done.
735 - if ( preg_match( '/[a-z0-9]{40,}/i', $result ) ) {
766 + if ( is_string( $result ) && preg_match( '/[a-z0-9]{40,}/i', $result ) ) {
736 767 $response[ $option ] = $result;
737 768 $updated = true;
738 769 }
739 770 break;
@@ -797,8 +828,17 @@
797 828 ? update_option( 'verification_services_codes', $grouped_options )
798 829 : true;
799 830 break;
800 831
832 + case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
833 + Jetpack_SEO_Utils::update_front_page_meta_description( $value );
834 + $response[ $option ] = Jetpack_SEO_Utils::get_front_page_meta_description();
835 + // The helper returns an empty string for a successful clear or
836 + // same-value write, so use its authoritative getter for the response
837 + // and treat every valid request reaching this switch as handled.
838 + $updated = true;
839 + break;
840 +
801 841 case 'sharing_services':
802 842 if ( ! class_exists( 'Sharing_Service' ) && ! include_once JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) {
803 843 break;
804 844 }
@@ -951,9 +991,9 @@
951 991 // If option value was the same, consider it done.
952 992 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
953 993 $sub_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
954 994 ? $value
955 - : Automattic\Jetpack\Modules\Subscriptions\Settings::get_default_reply_to();
995 + : Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
956 996
957 997 $updated = (string) get_option( $option ) !== (string) $sub_value ? update_option( $option, $sub_value ) : true;
958 998 break;
959 999 case 'jetpack_subscriptions_from_name':
@@ -965,8 +1005,9 @@
965 1005 case 'stb_enabled':
966 1006 case 'stc_enabled':
967 1007 case 'sm_enabled':
968 1008 case 'jetpack_subscribe_overlay_enabled':
1009 + case 'jetpack_subscribe_floating_button_enabled':
969 1010 case 'wpcom_newsletter_categories_enabled':
970 1011 case 'wpcom_featured_image_in_email':
971 1012 case 'jetpack_gravatar_in_email':
972 1013 case 'jetpack_author_in_email':
@@ -988,9 +1029,9 @@
988 1029 if ( ! is_array( $value ) ) {
989 1030 break;
990 1031 }
991 1032
992 - $allowed_keys = array( 'invitation', 'comment_follow', 'welcome' );
1033 + $allowed_keys = array( 'invitation', 'comment_follow', 'welcome', 'subscribe_modal_heading', 'free_tier_description', 'hide_free_tier' );
993 1034 $filtered_value = array_filter(
994 1035 $value,
995 1036 function ( $key ) use ( $allowed_keys ) {
996 1037 return in_array( $key, $allowed_keys, true );
@@ -1001,8 +1042,17 @@
1001 1042 if ( empty( $filtered_value ) ) {
1002 1043 break;
1003 1044 }
1004 1045
1046 + // `hide_free_tier` is a boolean flag, so pull it out before the HTML
1047 + // sanitization below (which expects strings). Sanitize it with
1048 + // rest_sanitize_boolean() so stringy booleans (e.g. "false", "0")
1049 + // are interpreted correctly rather than being treated as truthy by a
1050 + // plain `! empty()`.
1051 + $has_hide_free_tier = array_key_exists( 'hide_free_tier', $filtered_value );
1052 + $hide_free_tier = $has_hide_free_tier && rest_sanitize_boolean( $filtered_value['hide_free_tier'] );
1053 + unset( $filtered_value['hide_free_tier'] );
1054 +
1005 1055 array_walk_recursive(
1006 1056 $filtered_value,
1007 1057 function ( &$value ) {
1008 1058 $value = wp_kses(
@@ -1007,9 +1057,15 @@
1007 1057 function ( &$value ) {
1008 1058 $value = wp_kses(
1009 1059 $value,
1010 1060 array(
1011 - 'a' => array(
1061 + 'ul' => array(),
1062 + 'li' => array(),
1063 + 'p' => array(),
1064 + 'strong' => array(),
1065 + 'ol' => array(),
1066 + 'em' => array(),
1067 + 'a' => array(
1012 1068 'href' => array(),
1013 1069 ),
1014 1070 )
1015 1071 );
@@ -1015,8 +1071,33 @@
1015 1071 );
1016 1072 }
1017 1073 );
1018 1074
1075 + // Normalize whitespace-only `subscribe_modal_heading` input to empty so
1076 + // the modal template's `empty()` fallback fires. PHP's `empty()` treats
1077 + // `" "` as non-empty, which would otherwise render a blank heading.
1078 + if ( isset( $filtered_value['subscribe_modal_heading'] ) ) {
1079 + $filtered_value['subscribe_modal_heading'] = trim( $filtered_value['subscribe_modal_heading'] );
1080 + }
1081 +
1082 + // The free tier description is stored as plain markdown source, so strip
1083 + // all HTML and cap its length to match the paid-tier description field.
1084 + // WordPress core guarantees mb_substr() (polyfilled in wp-includes/compat.php
1085 + // when the mbstring extension is unavailable), so it's safe to use directly.
1086 + // A JSON payload could supply a non-scalar (array/object) for this field,
1087 + // which would fatal in wp_kses()/mb_substr() on PHP 8+, so drop invalid values.
1088 + if ( isset( $filtered_value['free_tier_description'] ) ) {
1089 + if ( is_scalar( $filtered_value['free_tier_description'] ) ) {
1090 + $filtered_value['free_tier_description'] = mb_substr( wp_kses( (string) $filtered_value['free_tier_description'], array() ), 0, 500 );
1091 + } else {
1092 + unset( $filtered_value['free_tier_description'] );
1093 + }
1094 + }
1095 +
1096 + if ( $has_hide_free_tier ) {
1097 + $filtered_value['hide_free_tier'] = $hide_free_tier;
1098 + }
1099 +
1019 1100 $old_subscription_options = get_option( 'subscription_options' );
1020 1101 if ( ! is_array( $old_subscription_options ) ) {
1021 1102 $old_subscription_options = array();
1022 1103 }
@@ -1032,8 +1113,30 @@
1032 1113 $error = esc_html__( 'Subscription Options failed to process.', 'jetpack' );
1033 1114 }
1034 1115 break;
1035 1116
1117 + case Jetpack_Newsletter_Category_Helper::NEWSLETTER_CATEGORIES_OPTION:
1118 + if ( ! is_array( $value ) || empty( $value ) ) {
1119 + $updated = true;
1120 + break;
1121 + }
1122 +
1123 + // If we are already current, do nothing
1124 + $current_value = Jetpack_Newsletter_Category_Helper::get_category_ids();
1125 + if ( $value === $current_value ) {
1126 + $updated = true;
1127 + break;
1128 + }
1129 +
1130 + if ( Jetpack_Newsletter_Category_Helper::save_category_ids( $value ) ) {
1131 + $updated = true;
1132 + } else {
1133 + $updated = false;
1134 + $error = esc_html__( 'Newsletter category did not update.', 'jetpack' );
1135 + }
1136 +
1137 + break;
1138 +
1036 1139 default:
1037 1140 // Boolean values are stored as 1 or 0.
1038 1141 if ( isset( $options[ $option ]['type'] ) && 'boolean' === $options[ $option ]['type'] ) {
1039 1142 $value = (int) $value;
@@ -1039,9 +1142,9 @@
1039 1142 $value = (int) $value;
1040 1143 }
1041 1144
1042 1145 // If option value was the same as it's current value, or it's default, consider it done.
1043 - $default = isset( $options[ $option ]['default'] ) ? $options[ $option ]['default'] : false;
1146 + $default = $options[ $option ]['default'] ?? false;
1044 1147 $updated = get_option( $option, $default ) != $value // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual -- ensure we support scalars or strings saved by update_option.
1045 1148 ? update_option( $option, $value )
1046 1149 : true;
1047 1150 break;
@@ -1212,13 +1315,13 @@
1212 1315 if ( ! is_array( $params ) ) {
1213 1316 $params = $request->get_body_params();
1214 1317 }
1215 1318 $options = Jetpack_Core_Json_Api_Endpoints::get_updateable_data_list( $params );
1216 - foreach ( $options as $option => $definition ) {
1217 - if ( in_array( $options[ $option ]['jp_group'], array( 'post-by-email' ), true ) ) {
1218 - $module = $options[ $option ]['jp_group'];
1219 - break;
1220 - }
1319 +
1320 + // The Post by Email gate applies only when the request contains nothing else.
1321 + $groups = array_values( array_unique( array_column( $options, 'jp_group' ) ) );
1322 + if ( array( 'post-by-email' ) === $groups ) {
1323 + $module = 'post-by-email';
1221 1324 }
1222 1325 }
1223 1326 // User is trying to create, regenerate or delete its PbE.
1224 1327 if ( 'post-by-email' === $module ) {
@@ -1234,9 +1337,8 @@
1234 1337 *
1235 1338 * phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
1236 1339 */
1237 1340 class Jetpack_Core_API_Module_Data_Endpoint {
1238 - // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
1239 1341
1240 1342 /**
1241 1343 * Process request and return different data based on the module we are interested in.
1242 1344 *
@@ -1436,11 +1538,9 @@
1436 1538 array(
1437 1539 'general' => $initial_stats,
1438 1540
1439 1541 // Build data for 'day' as if it was $wpcom_stats ->get_visits( array( 'unit' => 'day, 'quantity' => 30).
1440 - 'day' => isset( $initial_stats->visits )
1441 - ? $initial_stats->visits
1442 - : array(),
1542 + 'day' => $initial_stats->visits ?? array(),
1443 1543 )
1444 1544 );
1445 1545 case 'week':
1446 1546 return rest_ensure_response(
@@ -1581,9 +1681,9 @@
1581 1681 $last_service = $copy_services[ $last ];
1582 1682 unset( $copy_services[ $last ] );
1583 1683 $message = esc_html(
1584 1684 sprintf(
1585 - /* translators: %1$s is a comma separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1685 + /* translators: %1$s is a comma-separated list of services, and %2$s is a single service name like Google, Bing, Pinterest, etc. */
1586 1686 __( 'Your site is verified with %1$s and %2$s.', 'jetpack' ),
1587 1687 implode( ', ', $copy_services ),
1588 1688 $last_service
1589 1689 )