| @@ -549,8 +549,103 @@ | ||
| 549 | 549 | ), |
| 550 | 550 | ) |
| 551 | 551 | ); |
| 552 | 552 | |
| 553 | + register_rest_route( | |
| 554 | + 'jetpack/v4', | |
| 555 | + '/recommendations/data', | |
| 556 | + array( | |
| 557 | + array( | |
| 558 | + 'methods' => WP_REST_Server::READABLE, | |
| 559 | + 'callback' => __CLASS__ . '::get_recommendations_data', | |
| 560 | + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', | |
| 561 | + ), | |
| 562 | + array( | |
| 563 | + 'methods' => WP_REST_Server::EDITABLE, | |
| 564 | + 'callback' => __CLASS__ . '::update_recommendations_data', | |
| 565 | + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', | |
| 566 | + 'args' => array( | |
| 567 | + 'data' => array( | |
| 568 | + 'required' => true, | |
| 569 | + 'type' => 'object', | |
| 570 | + 'validate_callback' => __CLASS__ . '::validate_recommendations_data', | |
| 571 | + ), | |
| 572 | + ), | |
| 573 | + ), | |
| 574 | + ) | |
| 575 | + ); | |
| 576 | + | |
| 577 | + register_rest_route( | |
| 578 | + 'jetpack/v4', | |
| 579 | + '/recommendations/step', | |
| 580 | + array( | |
| 581 | + array( | |
| 582 | + 'methods' => WP_REST_Server::READABLE, | |
| 583 | + 'callback' => __CLASS__ . '::get_recommendations_step', | |
| 584 | + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', | |
| 585 | + ), | |
| 586 | + array( | |
| 587 | + 'methods' => WP_REST_Server::EDITABLE, | |
| 588 | + 'callback' => __CLASS__ . '::update_recommendations_step', | |
| 589 | + 'permission_callback' => __CLASS__ . '::update_settings_permission_check', | |
| 590 | + 'args' => array( | |
| 591 | + 'step' => array( | |
| 592 | + 'required' => true, | |
| 593 | + 'type' => 'string', | |
| 594 | + 'validate_callback' => __CLASS__ . '::validate_string', | |
| 595 | + ), | |
| 596 | + ), | |
| 597 | + ), | |
| 598 | + ) | |
| 599 | + ); | |
| 600 | + | |
| 601 | + register_rest_route( | |
| 602 | + 'jetpack/v4', | |
| 603 | + '/recommendations/product-suggestions', | |
| 604 | + array( | |
| 605 | + array( | |
| 606 | + 'methods' => WP_REST_Server::READABLE, | |
| 607 | + 'callback' => __CLASS__ . '::get_recommendations_product_suggestions', | |
| 608 | + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', | |
| 609 | + ), | |
| 610 | + ) | |
| 611 | + ); | |
| 612 | + | |
| 613 | + register_rest_route( | |
| 614 | + 'jetpack/v4', | |
| 615 | + '/recommendations/upsell', | |
| 616 | + array( | |
| 617 | + array( | |
| 618 | + 'methods' => WP_REST_Server::READABLE, | |
| 619 | + 'callback' => __CLASS__ . '::get_recommendations_upsell', | |
| 620 | + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', | |
| 621 | + ), | |
| 622 | + ) | |
| 623 | + ); | |
| 624 | + | |
| 625 | + register_rest_route( | |
| 626 | + 'jetpack/v4', | |
| 627 | + '/recommendations/conditional', | |
| 628 | + array( | |
| 629 | + array( | |
| 630 | + 'methods' => WP_REST_Server::READABLE, | |
| 631 | + 'callback' => __CLASS__ . '::get_conditional_recommendations', | |
| 632 | + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', | |
| 633 | + ), | |
| 634 | + ) | |
| 635 | + ); | |
| 636 | + | |
| 637 | + // Get site discount. | |
| 638 | + register_rest_route( | |
| 639 | + 'jetpack/v4', | |
| 640 | + '/site/discount', | |
| 641 | + array( | |
| 642 | + 'methods' => WP_REST_Server::READABLE, | |
| 643 | + 'callback' => __CLASS__ . '::get_site_discount', | |
| 644 | + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', | |
| 645 | + ) | |
| 646 | + ); | |
| 647 | + | |
| 553 | 648 | /* |
| 554 | 649 | * Manage the Jetpack CRM plugin's integration with Jetpack contact forms. |
| 555 | 650 | */ |
| 556 | 651 | register_rest_route( |
| @@ -605,8 +700,19 @@ | ||
| 605 | 700 | 'permission_callback' => __CLASS__ . '::manage_modules_permission_check', |
| 606 | 701 | ) |
| 607 | 702 | ); |
| 608 | 703 | |
| 704 | + // Get Jetpack introduction offers | |
| 705 | + register_rest_route( | |
| 706 | + 'jetpack/v4', | |
| 707 | + '/intro-offers', | |
| 708 | + array( | |
| 709 | + 'methods' => WP_REST_Server::READABLE, | |
| 710 | + 'callback' => __CLASS__ . '::get_intro_offers', | |
| 711 | + 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', | |
| 712 | + ) | |
| 713 | + ); | |
| 714 | + | |
| 609 | 715 | // Save subscriber token and redirect |
| 610 | 716 | register_rest_route( |
| 611 | 717 | 'jetpack/v4', |
| 612 | 718 | '/subscribers/auth', |
| @@ -707,8 +813,181 @@ | ||
| 707 | 813 | return new WP_REST_Response( null, 302, array( 'location' => $redirect_url ) ); |
| 708 | 814 | } |
| 709 | 815 | |
| 710 | 816 | /** |
| 817 | + * Get the data for the recommendations | |
| 818 | + * | |
| 819 | + * @return array Recommendations data | |
| 820 | + */ | |
| 821 | + public static function get_recommendations_data() { | |
| 822 | + return Jetpack_Recommendations::get_recommendations_data(); | |
| 823 | + } | |
| 824 | + | |
| 825 | + /** | |
| 826 | + * Update the data for the recommendations | |
| 827 | + * | |
| 828 | + * @param WP_REST_Request $request The request. | |
| 829 | + * | |
| 830 | + * @return bool true | |
| 831 | + */ | |
| 832 | + public static function update_recommendations_data( $request ) { | |
| 833 | + $data = $request['data']; | |
| 834 | + Jetpack_Recommendations::update_recommendations_data( $data ); | |
| 835 | + | |
| 836 | + return true; | |
| 837 | + } | |
| 838 | + | |
| 839 | + /** | |
| 840 | + * Get the data for the recommendations | |
| 841 | + * | |
| 842 | + * @return array Recommendations data | |
| 843 | + */ | |
| 844 | + public static function get_recommendations_step() { | |
| 845 | + return Jetpack_Recommendations::get_recommendations_step(); | |
| 846 | + } | |
| 847 | + | |
| 848 | + /** | |
| 849 | + * Update the step for the recommendations | |
| 850 | + * | |
| 851 | + * @param WP_REST_Request $request The request. | |
| 852 | + * | |
| 853 | + * @return bool true | |
| 854 | + */ | |
| 855 | + public static function update_recommendations_step( $request ) { | |
| 856 | + $step = $request['step']; | |
| 857 | + Jetpack_Recommendations::update_recommendations_step( $step ); | |
| 858 | + | |
| 859 | + return true; | |
| 860 | + } | |
| 861 | + | |
| 862 | + /** | |
| 863 | + * Get product suggestions for the recommendations | |
| 864 | + * | |
| 865 | + * @return string|WP_Error The response from the wpcom product suggestions endpoint as a JSON object. | |
| 866 | + */ | |
| 867 | + public static function get_recommendations_product_suggestions() { | |
| 868 | + $blog_id = Jetpack_Options::get_option( 'id' ); | |
| 869 | + if ( ! $blog_id ) { | |
| 870 | + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); | |
| 871 | + } | |
| 872 | + | |
| 873 | + $user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected( get_current_user_id() ); | |
| 874 | + if ( ! $user_connected ) { | |
| 875 | + return wp_json_encode( array(), JSON_UNESCAPED_SLASHES ); | |
| 876 | + } | |
| 877 | + | |
| 878 | + $request_path = sprintf( '/sites/%s/jetpack-recommendations/product-suggestions?locale=' . get_user_locale(), $blog_id ); | |
| 879 | + $wpcom_request = Client::wpcom_json_api_request_as_user( | |
| 880 | + $request_path, | |
| 881 | + '2', | |
| 882 | + array( | |
| 883 | + 'method' => 'GET', | |
| 884 | + 'headers' => array( | |
| 885 | + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), | |
| 886 | + ), | |
| 887 | + ) | |
| 888 | + ); | |
| 889 | + | |
| 890 | + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); | |
| 891 | + if ( 200 === $response_code ) { | |
| 892 | + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); | |
| 893 | + } else { | |
| 894 | + return new WP_Error( | |
| 895 | + 'failed_to_fetch_data', | |
| 896 | + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), | |
| 897 | + array( 'status' => $response_code ) | |
| 898 | + ); | |
| 899 | + } | |
| 900 | + } | |
| 901 | + | |
| 902 | + /** | |
| 903 | + * Get the upsell for the recommendations | |
| 904 | + * | |
| 905 | + * @return string The response from the wpcom upsell endpoint as a JSON object | |
| 906 | + */ | |
| 907 | + public static function get_recommendations_upsell() { | |
| 908 | + $blog_id = Jetpack_Options::get_option( 'id' ); | |
| 909 | + if ( ! $blog_id ) { | |
| 910 | + return new WP_Error( 'site_not_registered', esc_html__( 'Site not registered.', 'jetpack' ) ); | |
| 911 | + } | |
| 912 | + | |
| 913 | + $user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected( get_current_user_id() ); | |
| 914 | + if ( ! $user_connected ) { | |
| 915 | + $response = array( | |
| 916 | + 'hide_upsell' => true, | |
| 917 | + ); | |
| 918 | + | |
| 919 | + return $response; | |
| 920 | + } | |
| 921 | + | |
| 922 | + $request_path = sprintf( '/sites/%s/jetpack-recommendations/upsell?locale=' . get_user_locale(), $blog_id ); | |
| 923 | + $wpcom_request = Client::wpcom_json_api_request_as_user( | |
| 924 | + $request_path, | |
| 925 | + '2', | |
| 926 | + array( | |
| 927 | + 'method' => 'GET', | |
| 928 | + 'headers' => array( | |
| 929 | + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), | |
| 930 | + ), | |
| 931 | + ) | |
| 932 | + ); | |
| 933 | + | |
| 934 | + $response_code = wp_remote_retrieve_response_code( $wpcom_request ); | |
| 935 | + if ( 200 === $response_code ) { | |
| 936 | + return json_decode( wp_remote_retrieve_body( $wpcom_request ) ); | |
| 937 | + } else { | |
| 938 | + return new WP_Error( | |
| 939 | + 'failed_to_fetch_data', | |
| 940 | + esc_html__( 'Unable to fetch the requested data.', 'jetpack' ), | |
| 941 | + array( 'status' => $response_code ) | |
| 942 | + ); | |
| 943 | + } | |
| 944 | + } | |
| 945 | + | |
| 946 | + /** | |
| 947 | + * Get conditional recommendations data. | |
| 948 | + * | |
| 949 | + * @return array Conditional recommendations data. | |
| 950 | + */ | |
| 951 | + public static function get_conditional_recommendations() { | |
| 952 | + return Jetpack_Recommendations::get_conditional_recommendations(); | |
| 953 | + } | |
| 954 | + | |
| 955 | + /** | |
| 956 | + * Validate the recommendations data | |
| 957 | + * | |
| 958 | + * @param array $value Value to check received by request. | |
| 959 | + * @param WP_REST_Request $request The request sent to the WP REST API. | |
| 960 | + * @param string $param Name of the parameter passed to endpoint holding $value. | |
| 961 | + * | |
| 962 | + * @return bool|WP_Error | |
| 963 | + */ | |
| 964 | + public static function validate_recommendations_data( $value, $request, $param ) { | |
| 965 | + if ( ! is_array( $value ) ) { | |
| 966 | + /* translators: Name of a parameter that must be an object */ | |
| 967 | + return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an object.', 'jetpack' ), $param ) ); | |
| 968 | + } | |
| 969 | + | |
| 970 | + foreach ( $value as $answer ) { | |
| 971 | + if ( is_array( $answer ) ) { | |
| 972 | + $validate = self::validate_array_of_strings( $answer, $request, $param ); | |
| 973 | + } elseif ( is_string( $answer ) ) { | |
| 974 | + $validate = self::validate_string( $answer, $request, $param ); | |
| 975 | + } elseif ( $answer === null ) { | |
| 976 | + $validate = true; | |
| 977 | + } else { | |
| 978 | + $validate = self::validate_boolean( $answer, $request, $param ); | |
| 979 | + } | |
| 980 | + | |
| 981 | + if ( is_wp_error( $validate ) ) { | |
| 982 | + return $validate; | |
| 983 | + } | |
| 984 | + } | |
| 985 | + | |
| 986 | + return true; | |
| 987 | + } | |
| 988 | + | |
| 989 | + /** | |
| 711 | 990 | * Return a purchase token used for site-connected (non user-authenticated) checkout. |
| 712 | 991 | * |
| 713 | 992 | * @return string|WP_Error The current purchase token or WP_Error with error details. |
| 714 | 993 | */ |
| @@ -1372,9 +1651,9 @@ | ||
| 1372 | 1651 | */ |
| 1373 | 1652 | public static function get_user_connection_data() { |
| 1374 | 1653 | _deprecated_function( __METHOD__, 'jetpack-10.0.0', '\Automattic\Jetpack\Connection\REST_Connector::get_user_connection_data' ); |
| 1375 | 1654 | |
| 1376 | - require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class-jetpack-redux-state-helper.php'; | |
| 1655 | + require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php'; | |
| 1377 | 1656 | |
| 1378 | 1657 | $connection_owner = ( new Connection_Manager() )->get_connection_owner(); |
| 1379 | 1658 | $owner_display_name = false === $connection_owner ? null : $connection_owner->data->display_name; |
| 1380 | 1659 | |
| @@ -1557,8 +1836,64 @@ | ||
| 1557 | 1836 | ); |
| 1558 | 1837 | } |
| 1559 | 1838 | |
| 1560 | 1839 | /** |
| 1840 | + * Fetch the discount for this site and return it. | |
| 1841 | + * | |
| 1842 | + * @since 10.8 | |
| 1843 | + * | |
| 1844 | + * @return array|WP_Error | |
| 1845 | + */ | |
| 1846 | + public static function get_site_discount() { | |
| 1847 | + $site_id = Jetpack_Options::get_option( 'id' ); | |
| 1848 | + | |
| 1849 | + if ( ! $site_id ) { | |
| 1850 | + return new WP_Error( | |
| 1851 | + 'site_id_missing', | |
| 1852 | + esc_html__( 'Site ID is missing.', 'jetpack' ), | |
| 1853 | + array( 'status' => 400 ) | |
| 1854 | + ); | |
| 1855 | + } | |
| 1856 | + | |
| 1857 | + $response = Client::wpcom_json_api_request_as_user( | |
| 1858 | + "/sites/$site_id/discount", | |
| 1859 | + '2', | |
| 1860 | + array( | |
| 1861 | + 'method' => 'GET', | |
| 1862 | + 'headers' => array( | |
| 1863 | + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), | |
| 1864 | + ), | |
| 1865 | + ) | |
| 1866 | + ); | |
| 1867 | + | |
| 1868 | + $response_code = wp_remote_retrieve_response_code( $response ); | |
| 1869 | + $data = json_decode( wp_remote_retrieve_body( $response ) ); | |
| 1870 | + | |
| 1871 | + if ( 200 !== $response_code ) { | |
| 1872 | + return new WP_Error( | |
| 1873 | + 'discount_fetch_failed', | |
| 1874 | + is_object( $data ) && property_exists( $data, 'error' ) ? $data->error : esc_html__( 'Could not retrieve site discount.', 'jetpack' ), | |
| 1875 | + array( 'status' => $response_code ) | |
| 1876 | + ); | |
| 1877 | + } | |
| 1878 | + | |
| 1879 | + if ( ! isset( $data ) ) { | |
| 1880 | + return new WP_Error( | |
| 1881 | + 'discount_parse_error', | |
| 1882 | + esc_html__( 'Could not parse discount', 'jetpack' ), | |
| 1883 | + array( 'status' => 204 ) // no content. | |
| 1884 | + ); | |
| 1885 | + } | |
| 1886 | + | |
| 1887 | + return rest_ensure_response( | |
| 1888 | + array( | |
| 1889 | + 'code' => 'success', | |
| 1890 | + 'data' => $data, | |
| 1891 | + ) | |
| 1892 | + ); | |
| 1893 | + } | |
| 1894 | + | |
| 1895 | + /** | |
| 1561 | 1896 | * Reset Jetpack options |
| 1562 | 1897 | * |
| 1563 | 1898 | * @since 4.3.0 |
| 1564 | 1899 | * |
| @@ -2824,14 +3159,14 @@ | ||
| 2824 | 3159 | * |
| 2825 | 3160 | * @return bool|WP_Error |
| 2826 | 3161 | */ |
| 2827 | 3162 | public static function validate_verification_service( $value, $request, $param ) { |
| 2828 | - if ( ! empty( $value ) && ( ! is_string( $value ) || false === jetpack_verification_validate_code( $value ) ) ) { | |
| 3163 | + if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) { | |
| 2829 | 3164 | return new WP_Error( |
| 2830 | 3165 | 'invalid_param', |
| 2831 | 3166 | sprintf( |
| 2832 | 3167 | /* Translators: Placeholder is a verification string used to verify a service like Google Webmaster Console. */ |
| 2833 | - esc_html__( '%s must be a valid verification code or verification tag.', 'jetpack' ), | |
| 3168 | + esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), | |
| 2834 | 3169 | $param |
| 2835 | 3170 | ) |
| 2836 | 3171 | ); |
| 2837 | 3172 | } |
| @@ -3981,8 +4316,65 @@ | ||
| 3981 | 4316 | public static function set_has_seen_wc_connection_modal() { |
| 3982 | 4317 | $updated_option = Jetpack_Options::update_option( 'has_seen_wc_connection_modal', true ); |
| 3983 | 4318 | |
| 3984 | 4319 | return rest_ensure_response( array( 'success' => $updated_option ) ); |
| 4320 | + } | |
| 4321 | + | |
| 4322 | + /** | |
| 4323 | + * Fetch introdution offers. | |
| 4324 | + * | |
| 4325 | + * @since 10.9 | |
| 4326 | + * | |
| 4327 | + * @return array|WP_Error | |
| 4328 | + */ | |
| 4329 | + public static function get_intro_offers() { | |
| 4330 | + $site_id = Jetpack_Options::get_option( 'id' ); | |
| 4331 | + | |
| 4332 | + if ( ! $site_id ) { | |
| 4333 | + return new WP_Error( | |
| 4334 | + 'site_id_missing', | |
| 4335 | + esc_html__( 'Site ID is missing.', 'jetpack' ), | |
| 4336 | + array( 'status' => 400 ) | |
| 4337 | + ); | |
| 4338 | + } | |
| 4339 | + | |
| 4340 | + $response = Client::wpcom_json_api_request_as_user( | |
| 4341 | + '/introductory-offers', | |
| 4342 | + '2', | |
| 4343 | + array( | |
| 4344 | + 'method' => 'GET', | |
| 4345 | + 'headers' => array( | |
| 4346 | + 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ), | |
| 4347 | + ), | |
| 4348 | + ) | |
| 4349 | + ); | |
| 4350 | + | |
| 4351 | + $response_code = wp_remote_retrieve_response_code( $response ); | |
| 4352 | + | |
| 4353 | + if ( 200 !== $response_code ) { | |
| 4354 | + return new WP_Error( | |
| 4355 | + 'intro_offers_fetch_failed', | |
| 4356 | + esc_html__( 'Could not retrieve intro offers.', 'jetpack' ), | |
| 4357 | + array( 'status' => $response_code ) | |
| 4358 | + ); | |
| 4359 | + } | |
| 4360 | + | |
| 4361 | + $data = json_decode( wp_remote_retrieve_body( $response ) ); | |
| 4362 | + | |
| 4363 | + if ( ! isset( $data ) ) { | |
| 4364 | + return new WP_Error( | |
| 4365 | + 'intro_offers_error', | |
| 4366 | + esc_html__( 'Could not parse intro offers.', 'jetpack' ), | |
| 4367 | + array( 'status' => 204 ) // no content. | |
| 4368 | + ); | |
| 4369 | + } | |
| 4370 | + | |
| 4371 | + return rest_ensure_response( | |
| 4372 | + array( | |
| 4373 | + 'code' => 'success', | |
| 4374 | + 'data' => $data, | |
| 4375 | + ) | |
| 4376 | + ); | |
| 3985 | 4377 | } |
| 3986 | 4378 | |
| 3987 | 4379 | /** |
| 3988 | 4380 | * Return the list of available features. |