req->get_params() ); case 'country-views': return $this->wpcom_stats->get_views_by_country( $req->get_params() ); case 'referrers': return $this->wpcom_stats->get_referrers( $req->get_params() ); case 'top-posts': return $this->wpcom_stats->get_top_posts( $req->get_params() ); case 'publicize': return $this->wpcom_stats->get_publicize_followers( $req->get_params() ); case 'followers': return $this->wpcom_stats->get_followers( $req->get_params() ); case 'tags': return $this->wpcom_stats->get_tags( $req->get_params() ); case 'visits': return $this->wpcom_stats->get_visits( $req->get_params() ); case 'comments': return $this->wpcom_stats->get_top_comments( $req->get_params() ); case 'comment-followers': return $this->wpcom_stats->get_comment_followers( $req->get_params() ); case 'streak': return $this->wpcom_stats->get_streak( $req->get_params() ); case 'insights': return $this->wpcom_stats->get_insights( $req->get_params() ); case 'highlights': return $this->wpcom_stats->get_highlights( $req->get_params() ); case 'subscribers': return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/subscribers?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); default: return $this->get_forbidden_error(); } } /** * Return likes of a single post. * * @param WP_REST_Request $req The request object. */ public function get_single_post_likes( $req ) { $response = wp_remote_get( sprintf( '%s/rest/v1.2/sites/%d/posts/%d/likes?%s', Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ), Jetpack_Options::get_option( 'id' ), $req->get_param( 'resource_id' ), $this->filter_and_build_query_string( $req->get_params(), array( 'resource_id' ) ) ), array( 'timeout' => 5 ) ); $response_code = wp_remote_retrieve_response_code( $response ); $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( is_wp_error( $response ) ) { return $response; } if ( 200 !== $response_code ) { return new WP_Error( isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error', isset( $response_body['message'] ) ? $response_body['message'] : 'unknown remote error', array( 'status' => $response_code ) ); } return $response_body; } /** * Site Stats Resource endpoint. * * @param WP_REST_Request $req The request object. * @return array */ public function get_single_resource_stats( $req ) { switch ( $req->get_param( 'resource' ) ) { case 'post': return $this->wpcom_stats->get_post_views( intval( $req->get_param( 'resource_id' ) ), $req->get_params() ); case 'video': return $this->wpcom_stats->get_video_details( intval( $req->get_param( 'resource_id' ) ), $req->get_params() ); default: return $this->get_forbidden_error(); } } /** * Get brief information for a single post. * * @param WP_REST_Request $req The request object. * @return array */ public function get_single_post( $req ) { $post = get_post( intval( $req->get_param( 'resource_id' ) ), 'OBJECT', 'display' ); if ( is_wp_error( $post ) || empty( $post ) ) { return $post; } // The endpoint should be as compatible as possible with `/sites/$site_id/posts/$post_id`. // The reason we are not forwarding the request is that `/sites/$site_id/posts/$post_id` might require user tokens for private posts/sites, which is not possible for users without a WordPress.com account. // 'like_count' is not included in the response because it's available through another endpoint `/sites/$site_id/posts/$post_id/likes`. return array( 'ID' => $post->ID, 'site_ID' => Jetpack_Options::get_option( 'id' ), 'title' => $post->post_title, 'URL' => get_permalink( $post->ID ), 'type' => $post->post_type, 'status' => $post->post_status, 'discussion' => array( 'comment_count' => intval( $post->comment_count ) ), 'date' => $post->post_date, 'post_thumbnail' => array( 'URL' => get_the_post_thumbnail_url( $post->ID ) ), ); } /** * Get site stats. * * @param WP_REST_Request $req The request object. * @return array */ public function get_site_stats( $req ) { return $this->wpcom_stats->get_stats( $req->get_params() ); } /** * List posts for the site. * * @param WP_REST_Request $req The request object. * @return array */ public function get_site_posts( $req ) { // Force wpcom response. $params = array_merge( array( 'force' => 'wpcom' ), $req->get_params() ); $response = wp_remote_get( sprintf( '%s/rest/v1.1/sites/%d/posts?%s', Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ), Jetpack_Options::get_option( 'id' ), $req->get_param( 'resource_id' ), $this->filter_and_build_query_string( $params, array( 'resource_id' ) ) ), array( 'timeout' => 5 ) ); $response_code = wp_remote_retrieve_response_code( $response ); $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( is_wp_error( $response ) ) { return $response; } if ( 200 !== $response_code ) { return new WP_Error( isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error', isset( $response_body['message'] ) ? $response_body['message'] : 'unknown remote error', array( 'status' => $response_code ) ); } return $response_body; } /** * Get site subscribers counts. * * @param WP_REST_Request $req The request object. * * @return array */ public function get_site_subscribers_counts( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/subscribers/counts?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5 ), null, 'wpcom' ); } /** * Get site plan usage. * * @param WP_REST_Request $req The request object. * * @return array */ public function get_site_plan_usage( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/jetpack-stats/usage?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5 ), null, 'wpcom', false ); } /** * Post user feedback for Jetpack Stats. * * @param WP_REST_Request $req The request object. * * @return array */ public function post_user_feedback( $req ) { $current_user = wp_get_current_user(); $body_from_req = json_decode( $req->get_body(), true ); $body_data = is_array( $body_from_req ) ? $body_from_req : array(); $user_email = $current_user->user_email; return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/jetpack-stats/user-feedback?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json' ), ), wp_json_encode( array_merge( $body_data, array( 'user_email' => $user_email, ) ) ), 'wpcom' ); } /** * Whether site has never published post. * * @param WP_REST_Request $req The request object. * @return array */ public function site_has_never_published_post( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/site-has-never-published-post?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v2', array( 'timeout' => 5 ), null, 'wpcom' ); } /** * Get detailed WordAds earnings information for the site. * * @param WP_REST_Request $req The request object. * @return array */ public function get_wordads_earnings( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/wordads/earnings?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); } /** * Get WordAds stats for the site. * * @param WP_REST_Request $req The request object. * @return array */ public function get_wordads_stats( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/wordads/stats?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); } /** * Get Email stats as a list. * * @param WP_REST_Request $req The request object. * @return array */ public function get_email_stats_list( $req ) { switch ( $req->get_param( 'resource' ) ) { case 'summary': return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/emails/%s?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'resource' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); default: return $this->get_forbidden_error(); } } /** * Get Email opens stats for a single post. * * @param WP_REST_Request $req The request object. * @return array */ public function get_email_opens_stats_single( $req ) { switch ( $req->get_param( 'resource' ) ) { case 'client': case 'device': case 'country': case 'rate': return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/opens/emails/%d/%s?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'post_id' ), $req->get_param( 'resource' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); default: return $this->get_forbidden_error(); } } /** * Get Email clicks stats for a single post. * * @param WP_REST_Request $req The request object. * @return array */ public function get_email_clicks_stats_single( $req ) { switch ( $req->get_param( 'resource' ) ) { case 'client': case 'device': case 'country': case 'rate': case 'link': case 'user-content-link': return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/clicks/emails/%d/%s?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'post_id' ), $req->get_param( 'resource' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); default: return $this->get_forbidden_error(); } } /** * Get Email stats time series. * * @param WP_REST_Request $req The request object. * @return array */ public function get_email_stats_time_series( $req ) { switch ( $req->get_param( 'resource' ) ) { case 'opens': case 'clicks': return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/%s/emails/%d?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'resource' ), $req->get_param( 'post_id' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 5 ) ); default: return $this->get_forbidden_error(); } } /** * Get UTM stats time series. * * @param WP_REST_Request $req The request object. * @return array */ public function get_utm_stats_time_series( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/utm/%s?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'utm_params' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 10 ) ); } /** * Get Devices stats time series. * * @param WP_REST_Request $req The request object. * @return array */ public function get_devices_stats_time_series( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/stats/devices/%s?%s', Jetpack_Options::get_option( 'id' ), $req->get_param( 'device_property' ), $this->filter_and_build_query_string( $req->get_params() ) ), 'v1.1', array( 'timeout' => 10 ) ); } /** * Get Location stats. * * @param WP_REST_Request $req The request object. * @return array */ public function get_location_stats( $req ) { $params = $req->get_params(); $geo_mode = $params['geo_mode']; unset( $params['geo_mode'] ); return $this->wpcom_stats->get_views_by_location( $geo_mode, $params ); } /** * Dismiss or delay stats notices. * * @param WP_REST_Request $req The request object. * @return array */ public function update_notice_status( $req ) { return ( new Notices() )->update_notice( $req->get_param( 'id' ), $req->get_param( 'status' ), $req->get_param( 'postponed_for' ) ); } /** * Get stats notices. * * @return array */ public function get_notice_status() { return ( new Notices() )->get_notices_to_show(); } /** * Mark a referrer as spam. * * @param WP_REST_Request $req The request object. * @return array */ public function mark_referrer_spam( $req ) { return WPCOM_Client::request_as_blog( sprintf( '/sites/%d/stats/referrers/spam/new?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v1.1', array( 'timeout' => 5, 'method' => 'POST', ) ); } /** * Unmark a referrer as spam. * * @param WP_REST_Request $req The request object. * @return array */ public function unmark_referrer_spam( $req ) { return WPCOM_Client::request_as_blog( sprintf( '/sites/%d/stats/referrers/spam/delete?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v1.1', array( 'timeout' => 5, 'method' => 'POST', ) ); } /** * Toggle modules on dashboard. * * @param WP_REST_Request $req The request object. * @return array */ public function update_dashboard_modules( $req ) { // Clear dashboard modules cache. delete_transient( static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY ); return WPCOM_Client::request_as_blog( sprintf( '/sites/%d/jetpack-stats-dashboard/modules?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json' ), ), $req->get_body(), 'wpcom' ); } /** * Get modules on dashboard. * * @param WP_REST_Request $req The request object. * @return array */ public function get_dashboard_modules( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/jetpack-stats-dashboard/modules?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, ), null, 'wpcom', true, static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY ); } /** * Update module settings on dashboard. * * @param WP_REST_Request $req The request object. * @return array */ public function update_dashboard_module_settings( $req ) { // Clear dashboard modules cache. delete_transient( static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY ); return WPCOM_Client::request_as_blog( sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, 'method' => 'POST', 'headers' => array( 'Content-Type' => 'application/json' ), ), $req->get_body(), 'wpcom' ); } /** * Get module settings on dashboard. * * @param WP_REST_Request $req The request object. * @return array */ public function get_dashboard_module_settings( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, ), null, 'wpcom', true, static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY ); } /** * Run commercial classification. * * @param WP_REST_Request $req The request object. * @return array */ public function run_commercial_classification( $req ) { return WPCOM_Client::request_as_blog( sprintf( '/sites/%d/commercial-classification?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v2', array( 'timeout' => 5, 'method' => 'POST', ), null, 'wpcom' ); } /** * Get purchases array; I don't see anything sensetive in there, so didn't sentinizie it. * Plus it is the same case as Jetpack. * * @param WP_REST_Request $req The request object. * @return array */ public function get_site_purchases( $req ) { return WPCOM_Client::request_as_blog_cached( sprintf( '/sites/%d/purchases?%s', Jetpack_Options::get_option( 'id' ), $this->filter_and_build_query_string( $req->get_query_params() ) ), 'v1.1', array( 'timeout' => 10 ), null, 'rest', false ); } /** * Return a WP_Error object with a forbidden error. */ protected function get_forbidden_error() { $error_msg = esc_html__( 'You are not allowed to perform this action.', 'jetpack-stats-admin' ); return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) ); } /** * Filter and build query string from all the requested params. * * @param array $params The params to filter. * @param array $keys_to_unset The keys to unset from the params array. * @return string The filtered and built query string. */ protected function filter_and_build_query_string( $params, $keys_to_unset = array() ) { if ( isset( $params['rest_route'] ) ) { unset( $params['rest_route'] ); } if ( ! empty( $keys_to_unset ) && is_array( $keys_to_unset ) ) { foreach ( $keys_to_unset as $key ) { if ( isset( $params[ $key ] ) ) { unset( $params[ $key ] ); } } } return http_build_query( $params ); } }