PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
16.3-a.5 16.3-a.7 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 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src / class-rest-controller.php

class-rest-controller.php in Jetpack – WP Security, Backup, Speed, & Growth 16.2, at jetpack_vendor/automattic/jetpack-stats-admin/src/class-rest-controller.php

1,304 lines 35.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Stats Rest Controller class.
4 * Registers the REST routes for Odyssey Stats.
5 *
6 * @package automattic/jetpack-stats-admin
7 */
8
9 namespace Automattic\Jetpack\Stats_Admin;
10
11 use Automattic\Jetpack\Constants;
12 use Automattic\Jetpack\Stats\WPCOM_Stats;
13 use Jetpack_Options;
14 use WP_Error;
15 use WP_REST_Request;
16 use WP_REST_Server;
17
18 /**
19 * Registers the REST routes for Stats.
20 * It bascially forwards the requests to the WordPress.com REST API.
21 */
22 class REST_Controller {
23 const JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY = 'jetpack_stats_dashboard_modules_cache_key';
24 const JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY = 'jetpack_stats_dashboard_module_settings_cache_key';
25
26 /**
27 * Namespace for the REST API.
28 *
29 * @var string
30 */
31 public static $namespace = 'jetpack/v4/stats-app';
32
33 /**
34 * Hold an instance of WPCOM_Stats.
35 *
36 * @var WPCOM_Stats
37 */
38 protected $wpcom_stats;
39
40 /**
41 * Constructor
42 */
43 public function __construct() {
44 $this->wpcom_stats = new WPCOM_Stats();
45 }
46
47 /**
48 * Registers the REST routes on the `rest_api_init` hook.
49 *
50 * Instantiated here, rather than eagerly, so the controller class only loads
51 * on requests that reach `rest_api_init`. Static so the callback can be
52 * unregistered.
53 *
54 * @access public
55 */
56 public static function register() {
57 ( new self() )->register_rest_routes();
58 }
59
60 /**
61 * Registers the REST routes for Odyssey Stats.
62 *
63 * Odyssey Stats is built from `wp-calypso`, which leverages the `public-api.wordpress.com` API.
64 * The current Site ID is added as part of the route, so that the front end doesn't have to handle the differences.
65 *
66 * @access public
67 * @static
68 */
69 public function register_rest_routes() {
70 // Stats for single resource type.
71 register_rest_route(
72 static::$namespace,
73 sprintf( '/sites/%d/stats/(?P<resource>[\-\w]+)/(?P<resource_id>[\d]+)', Jetpack_Options::get_option( 'id' ) ),
74 array(
75 'methods' => WP_REST_Server::READABLE,
76 'callback' => array( $this, 'get_single_resource_stats' ),
77 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
78 )
79 );
80
81 // Stats for a resource type.
82 register_rest_route(
83 static::$namespace,
84 sprintf( '/sites/%d/stats/(?P<resource>[\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
85 array(
86 'methods' => WP_REST_Server::READABLE,
87 'callback' => array( $this, 'get_stats_resource' ),
88 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
89 )
90 );
91
92 // Single post info.
93 register_rest_route(
94 static::$namespace,
95 sprintf( '/sites/%d/posts/(?P<resource_id>[\d]+)', Jetpack_Options::get_option( 'id' ) ),
96 array(
97 'methods' => WP_REST_Server::READABLE,
98 'callback' => array( $this, 'get_single_post' ),
99 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
100 )
101 );
102
103 // Single post likes.
104 register_rest_route(
105 static::$namespace,
106 sprintf( '/sites/%d/posts/(?P<resource_id>[\d]+)/likes', Jetpack_Options::get_option( 'id' ) ),
107 array(
108 'methods' => WP_REST_Server::READABLE,
109 'callback' => array( $this, 'get_single_post_likes' ),
110 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
111 )
112 );
113
114 // General stats for the site.
115 register_rest_route(
116 static::$namespace,
117 sprintf( '/sites/%d/stats', Jetpack_Options::get_option( 'id' ) ),
118 array(
119 'methods' => WP_REST_Server::READABLE,
120 'callback' => array( $this, 'get_site_stats' ),
121 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
122 )
123 );
124
125 // Whether site has never published post / page.
126 register_rest_route(
127 static::$namespace,
128 sprintf( '/sites/%d/site-has-never-published-post', Jetpack_Options::get_option( 'id' ) ),
129 array(
130 'methods' => WP_REST_Server::READABLE,
131 'callback' => array( $this, 'site_has_never_published_post' ),
132 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
133 )
134 );
135
136 // List posts.
137 register_rest_route(
138 static::$namespace,
139 sprintf( '/sites/%d/posts', Jetpack_Options::get_option( 'id' ) ),
140 array(
141 'methods' => WP_REST_Server::READABLE,
142 'callback' => array( $this, 'get_site_posts' ),
143 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
144 )
145 );
146
147 // Subscribers counts.
148 register_rest_route(
149 static::$namespace,
150 sprintf( '/sites/%d/subscribers/counts', Jetpack_Options::get_option( 'id' ) ),
151 array(
152 'methods' => WP_REST_Server::READABLE,
153 'callback' => array( $this, 'get_site_subscribers_counts' ),
154 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
155 )
156 );
157
158 // Stats Plan Usage.
159 register_rest_route(
160 static::$namespace,
161 sprintf( '/sites/%d/jetpack-stats/usage', Jetpack_Options::get_option( 'id' ) ),
162 array(
163 'methods' => WP_REST_Server::READABLE,
164 'callback' => array( $this, 'get_site_plan_usage' ),
165 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
166 )
167 );
168
169 // User feedback endpoint.
170 register_rest_route(
171 static::$namespace,
172 sprintf( '/sites/%d/jetpack-stats/user-feedback', Jetpack_Options::get_option( 'id' ) ),
173 array(
174 'methods' => WP_REST_Server::CREATABLE,
175 'callback' => array( $this, 'post_user_feedback' ),
176 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
177 )
178 );
179
180 // WordAds Earnings.
181 register_rest_route(
182 static::$namespace,
183 sprintf( '/sites/%d/wordads/earnings', Jetpack_Options::get_option( 'id' ) ),
184 array(
185 'methods' => WP_REST_Server::READABLE,
186 'callback' => array( $this, 'get_wordads_earnings' ),
187 'permission_callback' => array( $this, 'can_user_view_wordads_stats_callback' ),
188 )
189 );
190
191 // WordAds Stats.
192 register_rest_route(
193 static::$namespace,
194 sprintf( '/sites/%d/wordads/stats', Jetpack_Options::get_option( 'id' ) ),
195 array(
196 'methods' => WP_REST_Server::READABLE,
197 'callback' => array( $this, 'get_wordads_stats' ),
198 'permission_callback' => array( $this, 'can_user_view_wordads_stats_callback' ),
199 )
200 );
201
202 // Legacy: Update Stats notices.
203 // TODO: remove this in the next release.
204 register_rest_route(
205 static::$namespace,
206 '/stats/notices',
207 array(
208 'methods' => WP_REST_Server::EDITABLE,
209 'callback' => array( $this, 'update_notice_status' ),
210 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
211 'args' => array(
212 'id' => array(
213 'required' => true,
214 'type' => 'string',
215 'description' => 'ID of the notice',
216 ),
217 'status' => array(
218 'required' => true,
219 'type' => 'string',
220 'description' => 'Status of the notice',
221 ),
222 'postponed_for' => array(
223 'type' => 'number',
224 'default' => null,
225 'description' => 'Postponed for (in seconds)',
226 'minimum' => 0,
227 ),
228 ),
229 )
230 );
231
232 // Update Stats notices.
233 register_rest_route(
234 static::$namespace,
235 sprintf( '/sites/%d/jetpack-stats-dashboard/notices', Jetpack_Options::get_option( 'id' ) ),
236 array(
237 'methods' => WP_REST_Server::EDITABLE,
238 'callback' => array( $this, 'update_notice_status' ),
239 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
240 'args' => array(
241 'id' => array(
242 'required' => true,
243 'type' => 'string',
244 'description' => 'ID of the notice',
245 ),
246 'status' => array(
247 'required' => true,
248 'type' => 'string',
249 'description' => 'Status of the notice',
250 ),
251 'postponed_for' => array(
252 'type' => 'number',
253 'default' => null,
254 'description' => 'Postponed for (in seconds)',
255 'minimum' => 0,
256 ),
257 ),
258 )
259 );
260
261 // Get Stats notices.
262 register_rest_route(
263 static::$namespace,
264 sprintf( '/sites/%d/jetpack-stats-dashboard/notices', Jetpack_Options::get_option( 'id' ) ),
265 array(
266 'methods' => WP_REST_Server::READABLE,
267 'callback' => array( $this, 'get_notice_status' ),
268 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
269 )
270 );
271
272 // Get referrer spam list.
273 register_rest_route(
274 static::$namespace,
275 sprintf( '/sites/%d/stats/referrers/spam', Jetpack_Options::get_option( 'id' ) ),
276 array(
277 'methods' => WP_REST_Server::READABLE,
278 'callback' => array( $this, 'get_referrer_spam_list' ),
279 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
280 )
281 );
282
283 // Mark referrer spam.
284 register_rest_route(
285 static::$namespace,
286 sprintf( '/sites/%d/stats/referrers/spam/new', Jetpack_Options::get_option( 'id' ) ),
287 array(
288 'methods' => WP_REST_Server::EDITABLE,
289 'callback' => array( $this, 'mark_referrer_spam' ),
290 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
291 'args' => array(
292 'domain' => array(
293 'required' => true,
294 'type' => 'string',
295 'description' => 'Domain of the referrer',
296 ),
297 ),
298 )
299 );
300
301 // Unmark referrer spam.
302 register_rest_route(
303 static::$namespace,
304 sprintf( '/sites/%d/stats/referrers/spam/delete', Jetpack_Options::get_option( 'id' ) ),
305 array(
306 'methods' => WP_REST_Server::EDITABLE,
307 'callback' => array( $this, 'unmark_referrer_spam' ),
308 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
309 'args' => array(
310 'domain' => array(
311 'required' => true,
312 'type' => 'string',
313 'description' => 'Domain of the referrer',
314 ),
315 ),
316 )
317 );
318
319 // Update dashboard modules.
320 register_rest_route(
321 static::$namespace,
322 sprintf( '/sites/%d/jetpack-stats-dashboard/modules', Jetpack_Options::get_option( 'id' ) ),
323 array(
324 'methods' => WP_REST_Server::EDITABLE,
325 'callback' => array( $this, 'update_dashboard_modules' ),
326 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
327 )
328 );
329
330 // Get dashboard modules.
331 register_rest_route(
332 static::$namespace,
333 sprintf( '/sites/%d/jetpack-stats-dashboard/modules', Jetpack_Options::get_option( 'id' ) ),
334 array(
335 'methods' => WP_REST_Server::READABLE,
336 'callback' => array( $this, 'get_dashboard_modules' ),
337 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
338 )
339 );
340
341 // Update dashboard module settings.
342 register_rest_route(
343 static::$namespace,
344 sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings', Jetpack_Options::get_option( 'id' ) ),
345 array(
346 'methods' => WP_REST_Server::EDITABLE,
347 'callback' => array( $this, 'update_dashboard_module_settings' ),
348 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
349 )
350 );
351
352 // Get dashboard module settings.
353 register_rest_route(
354 static::$namespace,
355 sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings', Jetpack_Options::get_option( 'id' ) ),
356 array(
357 'methods' => WP_REST_Server::READABLE,
358 'callback' => array( $this, 'get_dashboard_module_settings' ),
359 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
360 )
361 );
362
363 // Get email stats as a list.
364 register_rest_route(
365 static::$namespace,
366 sprintf( '/sites/%d/stats/emails/(?P<resource>[\-\w\d]+)', Jetpack_Options::get_option( 'id' ) ),
367 array(
368 'methods' => WP_REST_Server::READABLE,
369 'callback' => array( $this, 'get_email_stats_list' ),
370 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
371 )
372 );
373
374 // Get Email opens stats for a single post.
375 register_rest_route(
376 static::$namespace,
377 sprintf( '/sites/%d/stats/opens/emails/(?P<post_id>[\d]+)/(?P<resource>[\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
378 array(
379 'methods' => WP_REST_Server::READABLE,
380 'callback' => array( $this, 'get_email_opens_stats_single' ),
381 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
382 )
383 );
384
385 // Get Email clicks stats for a single post.
386 register_rest_route(
387 static::$namespace,
388 sprintf( '/sites/%d/stats/clicks/emails/(?P<post_id>[\d]+)/(?P<resource>[\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
389 array(
390 'methods' => WP_REST_Server::READABLE,
391 'callback' => array( $this, 'get_email_clicks_stats_single' ),
392 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
393 )
394 );
395
396 // Get Email stats time series.
397 register_rest_route(
398 static::$namespace,
399 sprintf( '/sites/%d/stats/(?P<resource>[\-\w]+)/emails/(?P<post_id>[\d]+)', Jetpack_Options::get_option( 'id' ) ),
400 array(
401 'methods' => WP_REST_Server::READABLE,
402 'callback' => array( $this, 'get_email_stats_time_series' ),
403 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
404 )
405 );
406
407 // Get UTM stats time series.
408 register_rest_route(
409 static::$namespace,
410 // /stats/utm/utm_campaign,utm_source,utm_medium
411 sprintf( '/sites/%d/stats/utm/(?P<utm_params>[_,\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
412 array(
413 'methods' => WP_REST_Server::READABLE,
414 'callback' => array( $this, 'get_utm_stats_time_series' ),
415 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
416 )
417 );
418
419 // Get Devices stats time series.
420 register_rest_route(
421 static::$namespace,
422 // /stats/devices/screensize
423 sprintf( '/sites/%d/stats/devices/(?P<device_property>[\w]+)', Jetpack_Options::get_option( 'id' ) ),
424 array(
425 'methods' => WP_REST_Server::READABLE,
426 'callback' => array( $this, 'get_devices_stats_time_series' ),
427 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
428 )
429 );
430
431 // Rerun commercial classificiation.
432 register_rest_route(
433 static::$namespace,
434 sprintf( '/sites/%d/commercial-classification', Jetpack_Options::get_option( 'id' ) ),
435 array(
436 'methods' => WP_REST_Server::EDITABLE,
437 'callback' => array( $this, 'run_commercial_classification' ),
438 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
439 )
440 );
441
442 // Purchases endpoint.
443 register_rest_route(
444 static::$namespace,
445 sprintf( '/sites/%d/purchases', Jetpack_Options::get_option( 'id' ) ),
446 array(
447 'methods' => WP_REST_Server::READABLE,
448 'callback' => array( $this, 'get_site_purchases' ),
449 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
450 )
451 );
452
453 // Get Location stats.
454 register_rest_route(
455 static::$namespace,
456 sprintf( '/sites/%d/stats/location-views/(?P<geo_mode>country|region|city)', Jetpack_Options::get_option( 'id' ) ),
457 array(
458 'methods' => WP_REST_Server::READABLE,
459 'callback' => array( $this, 'get_location_stats' ),
460 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
461 )
462 );
463 }
464
465 /**
466 * Only administrators or users with capability `view_stats` can access the API.
467 *
468 * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
469 */
470 public function can_user_view_general_stats_callback() {
471 if ( current_user_can( 'manage_options' ) || current_user_can( 'view_stats' ) ) {
472 return true;
473 }
474
475 return $this->get_forbidden_error();
476 }
477
478 /**
479 * Only administrators or users with capability `activate_wordads` can access the API.
480 */
481 public function can_user_view_wordads_stats_callback() {
482 // phpcs:ignore WordPress.WP.Capabilities.Unknown
483 if ( current_user_can( 'manage_options' ) || current_user_can( 'activate_wordads' ) ) {
484 return true;
485 }
486
487 return $this->get_forbidden_error();
488 }
489
490 /**
491 * Stats resource endpoint.
492 *
493 * @param WP_REST_Request $req The request object.
494 * @return array
495 */
496 public function get_stats_resource( $req ) {
497 switch ( $req->get_param( 'resource' ) ) {
498 case 'file-downloads':
499 return $this->wpcom_stats->get_file_downloads( $req->get_params() );
500
501 case 'video-plays':
502 return $this->wpcom_stats->get_video_plays( $req->get_params() );
503
504 case 'clicks':
505 return $this->wpcom_stats->get_clicks( $req->get_params() );
506
507 case 'search-terms':
508 return $this->wpcom_stats->get_search_terms( $req->get_params() );
509
510 case 'top-authors':
511 return $this->wpcom_stats->get_top_authors( $req->get_params() );
512
513 case 'country-views':
514 return $this->wpcom_stats->get_views_by_country( $req->get_params() );
515
516 case 'referrers':
517 return $this->wpcom_stats->get_referrers( $req->get_params() );
518
519 case 'top-posts':
520 return $this->wpcom_stats->get_top_posts( $req->get_params() );
521
522 case 'archives':
523 return $this->wpcom_stats->get_archives( $req->get_params() );
524
525 case 'publicize':
526 return $this->wpcom_stats->get_publicize_followers( $req->get_params() );
527
528 case 'followers':
529 return $this->wpcom_stats->get_followers( $req->get_params() );
530
531 case 'tags':
532 return $this->wpcom_stats->get_tags( $req->get_params() );
533
534 case 'visits':
535 return $this->wpcom_stats->get_visits( $req->get_params() );
536
537 case 'comments':
538 return $this->wpcom_stats->get_top_comments( $req->get_params() );
539
540 case 'comment-followers':
541 return $this->wpcom_stats->get_comment_followers( $req->get_params() );
542
543 case 'streak':
544 return $this->wpcom_stats->get_streak( $req->get_params() );
545
546 case 'insights':
547 return $this->wpcom_stats->get_insights( $req->get_params() );
548
549 case 'highlights':
550 return $this->wpcom_stats->get_highlights( $req->get_params() );
551
552 case 'subscribers':
553 return WPCOM_Client::request_as_blog_cached(
554 sprintf(
555 '/sites/%d/stats/subscribers?%s',
556 Jetpack_Options::get_option( 'id' ),
557 $this->filter_and_build_query_string(
558 $req->get_query_params()
559 )
560 ),
561 'v1.1',
562 array( 'timeout' => 5 )
563 );
564
565 default:
566 return $this->get_forbidden_error();
567 }
568 }
569
570 /**
571 * Return likes of a single post.
572 *
573 * @param WP_REST_Request $req The request object.
574 */
575 public function get_single_post_likes( $req ) {
576 $response = wp_remote_get(
577 sprintf(
578 '%s/rest/v1.2/sites/%d/posts/%d/likes?%s',
579 Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
580 Jetpack_Options::get_option( 'id' ),
581 $req->get_param( 'resource_id' ),
582 $this->filter_and_build_query_string(
583 $req->get_params(),
584 array( 'resource_id' )
585 )
586 ),
587 array( 'timeout' => 5 )
588 );
589
590 $response_code = wp_remote_retrieve_response_code( $response );
591 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
592
593 if ( is_wp_error( $response ) ) {
594 return $response;
595 }
596
597 if ( 200 !== $response_code ) {
598 return new WP_Error(
599 isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error',
600 $response_body['message'] ?? 'unknown remote error',
601 array( 'status' => $response_code )
602 );
603 }
604
605 return $response_body;
606 }
607
608 /**
609 * Site Stats Resource endpoint.
610 *
611 * @param WP_REST_Request $req The request object.
612 * @return array
613 */
614 public function get_single_resource_stats( $req ) {
615 switch ( $req->get_param( 'resource' ) ) {
616 case 'post':
617 return $this->wpcom_stats->get_post_views(
618 intval( $req->get_param( 'resource_id' ) ),
619 $req->get_params()
620 );
621
622 case 'video':
623 return $this->wpcom_stats->get_video_details(
624 intval( $req->get_param( 'resource_id' ) ),
625 $req->get_params()
626 );
627
628 default:
629 return $this->get_forbidden_error();
630 }
631 }
632
633 /**
634 * Get brief information for a single post.
635 *
636 * @param WP_REST_Request $req The request object.
637 * @return array
638 */
639 public function get_single_post( $req ) {
640 $post = get_post( intval( $req->get_param( 'resource_id' ) ), 'OBJECT', 'display' );
641 if ( is_wp_error( $post ) || empty( $post ) ) {
642 return $post;
643 }
644
645 // The endpoint should be as compatible as possible with `/sites/$site_id/posts/$post_id`.
646 // 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.
647 // 'like_count' is not included in the response because it's available through another endpoint `/sites/$site_id/posts/$post_id/likes`.
648 return array(
649 'ID' => $post->ID,
650 'site_ID' => Jetpack_Options::get_option( 'id' ),
651 'title' => $post->post_title,
652 'URL' => get_permalink( $post->ID ),
653 'type' => $post->post_type,
654 'status' => $post->post_status,
655 'discussion' => array( 'comment_count' => intval( $post->comment_count ) ),
656 'date' => $post->post_date,
657 'post_thumbnail' => array( 'URL' => get_the_post_thumbnail_url( $post->ID ) ),
658 );
659 }
660
661 /**
662 * Get site stats.
663 *
664 * @param WP_REST_Request $req The request object.
665 * @return array
666 */
667 public function get_site_stats( $req ) {
668 return $this->wpcom_stats->get_stats( $req->get_params() );
669 }
670
671 /**
672 * List posts for the site.
673 *
674 * @param WP_REST_Request $req The request object.
675 * @return array
676 */
677 public function get_site_posts( $req ) {
678 // Force wpcom response.
679 $params = array_merge( array( 'force' => 'wpcom' ), $req->get_params() );
680 $response = wp_remote_get(
681 sprintf(
682 '%s/rest/v1.1/sites/%d/posts?%s',
683 Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
684 Jetpack_Options::get_option( 'id' ),
685 $req->get_param( 'resource_id' ),
686 $this->filter_and_build_query_string( $params, array( 'resource_id' ) )
687 ),
688 array( 'timeout' => 5 )
689 );
690
691 $response_code = wp_remote_retrieve_response_code( $response );
692 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
693
694 if ( is_wp_error( $response ) ) {
695 return $response;
696 }
697
698 if ( 200 !== $response_code ) {
699 return new WP_Error(
700 isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error',
701 $response_body['message'] ?? 'unknown remote error',
702 array( 'status' => $response_code )
703 );
704 }
705
706 return $response_body;
707 }
708
709 /**
710 * Get site subscribers counts.
711 *
712 * @param WP_REST_Request $req The request object.
713 *
714 * @return array
715 */
716 public function get_site_subscribers_counts( $req ) {
717 return WPCOM_Client::request_as_blog_cached(
718 sprintf(
719 '/sites/%d/subscribers/counts?%s',
720 Jetpack_Options::get_option( 'id' ),
721 $this->filter_and_build_query_string(
722 $req->get_query_params()
723 )
724 ),
725 'v2',
726 array( 'timeout' => 5 ),
727 null,
728 'wpcom'
729 );
730 }
731
732 /**
733 * Get site plan usage.
734 *
735 * @param WP_REST_Request $req The request object.
736 *
737 * @return array
738 */
739 public function get_site_plan_usage( $req ) {
740 return WPCOM_Client::request_as_blog_cached(
741 sprintf(
742 '/sites/%d/jetpack-stats/usage?%s',
743 Jetpack_Options::get_option( 'id' ),
744 $this->filter_and_build_query_string(
745 $req->get_query_params()
746 )
747 ),
748 'v2',
749 array( 'timeout' => 5 ),
750 null,
751 'wpcom',
752 false
753 );
754 }
755
756 /**
757 * Post user feedback for Jetpack Stats.
758 *
759 * @param WP_REST_Request $req The request object.
760 *
761 * @return array
762 */
763 public function post_user_feedback( $req ) {
764 $current_user = wp_get_current_user();
765 $body_from_req = json_decode( $req->get_body(), true );
766 $body_data = is_array( $body_from_req ) ? $body_from_req : array();
767 $user_email = $current_user->user_email;
768
769 return WPCOM_Client::request_as_blog_cached(
770 sprintf(
771 '/sites/%d/jetpack-stats/user-feedback?%s',
772 Jetpack_Options::get_option( 'id' ),
773 $this->filter_and_build_query_string(
774 $req->get_query_params()
775 )
776 ),
777 'v2',
778 array(
779 'timeout' => 5,
780 'method' => 'POST',
781 'headers' => array( 'Content-Type' => 'application/json' ),
782 ),
783 wp_json_encode(
784 array_merge(
785 $body_data,
786 array(
787 'user_email' => $user_email,
788 )
789 ),
790 JSON_UNESCAPED_SLASHES
791 ),
792 'wpcom'
793 );
794 }
795
796 /**
797 * Whether site has never published post.
798 *
799 * @param WP_REST_Request $req The request object.
800 * @return array
801 */
802 public function site_has_never_published_post( $req ) {
803 return WPCOM_Client::request_as_blog_cached(
804 sprintf(
805 '/sites/%d/site-has-never-published-post?%s',
806 Jetpack_Options::get_option( 'id' ),
807 $this->filter_and_build_query_string(
808 $req->get_params()
809 )
810 ),
811 'v2',
812 array( 'timeout' => 5 ),
813 null,
814 'wpcom'
815 );
816 }
817
818 /**
819 * Get detailed WordAds earnings information for the site.
820 *
821 * @param WP_REST_Request $req The request object.
822 * @return array
823 */
824 public function get_wordads_earnings( $req ) {
825 return WPCOM_Client::request_as_blog_cached(
826 sprintf(
827 '/sites/%d/wordads/earnings?%s',
828 Jetpack_Options::get_option( 'id' ),
829 $this->filter_and_build_query_string(
830 $req->get_params()
831 )
832 ),
833 'v1.1',
834 array( 'timeout' => 5 )
835 );
836 }
837
838 /**
839 * Get WordAds stats for the site.
840 *
841 * @param WP_REST_Request $req The request object.
842 * @return array
843 */
844 public function get_wordads_stats( $req ) {
845 return WPCOM_Client::request_as_blog_cached(
846 sprintf(
847 '/sites/%d/wordads/stats?%s',
848 Jetpack_Options::get_option( 'id' ),
849 $this->filter_and_build_query_string(
850 $req->get_params()
851 )
852 ),
853 'v1.1',
854 array( 'timeout' => 5 )
855 );
856 }
857
858 /**
859 * Get Email stats as a list.
860 *
861 * @param WP_REST_Request $req The request object.
862 * @return array
863 */
864 public function get_email_stats_list( $req ) {
865 switch ( $req->get_param( 'resource' ) ) {
866 case 'summary':
867 return WPCOM_Client::request_as_blog_cached(
868 sprintf(
869 '/sites/%d/stats/emails/%s?%s',
870 Jetpack_Options::get_option( 'id' ),
871 $req->get_param( 'resource' ),
872 $this->filter_and_build_query_string(
873 $req->get_params()
874 )
875 ),
876 'v1.1',
877 array( 'timeout' => 5 )
878 );
879 default:
880 return $this->get_forbidden_error();
881 }
882 }
883
884 /**
885 * Get Email opens stats for a single post.
886 *
887 * @param WP_REST_Request $req The request object.
888 * @return array
889 */
890 public function get_email_opens_stats_single( $req ) {
891 switch ( $req->get_param( 'resource' ) ) {
892 case 'client':
893 case 'device':
894 case 'country':
895 case 'rate':
896 return WPCOM_Client::request_as_blog_cached(
897 sprintf(
898 '/sites/%d/stats/opens/emails/%d/%s?%s',
899 Jetpack_Options::get_option( 'id' ),
900 $req->get_param( 'post_id' ),
901 $req->get_param( 'resource' ),
902 $this->filter_and_build_query_string(
903 $req->get_params()
904 )
905 ),
906 'v1.1',
907 array( 'timeout' => 5 )
908 );
909 default:
910 return $this->get_forbidden_error();
911 }
912 }
913
914 /**
915 * Get Email clicks stats for a single post.
916 *
917 * @param WP_REST_Request $req The request object.
918 * @return array
919 */
920 public function get_email_clicks_stats_single( $req ) {
921 switch ( $req->get_param( 'resource' ) ) {
922 case 'client':
923 case 'device':
924 case 'country':
925 case 'rate':
926 case 'link':
927 case 'user-content-link':
928 return WPCOM_Client::request_as_blog_cached(
929 sprintf(
930 '/sites/%d/stats/clicks/emails/%d/%s?%s',
931 Jetpack_Options::get_option( 'id' ),
932 $req->get_param( 'post_id' ),
933 $req->get_param( 'resource' ),
934 $this->filter_and_build_query_string(
935 $req->get_params()
936 )
937 ),
938 'v1.1',
939 array( 'timeout' => 5 )
940 );
941 default:
942 return $this->get_forbidden_error();
943 }
944 }
945
946 /**
947 * Get Email stats time series.
948 *
949 * @param WP_REST_Request $req The request object.
950 * @return array
951 */
952 public function get_email_stats_time_series( $req ) {
953 switch ( $req->get_param( 'resource' ) ) {
954 case 'opens':
955 case 'clicks':
956 return WPCOM_Client::request_as_blog_cached(
957 sprintf(
958 '/sites/%d/stats/%s/emails/%d?%s',
959 Jetpack_Options::get_option( 'id' ),
960 $req->get_param( 'resource' ),
961 $req->get_param( 'post_id' ),
962 $this->filter_and_build_query_string(
963 $req->get_params()
964 )
965 ),
966 'v1.1',
967 array( 'timeout' => 5 )
968 );
969 default:
970 return $this->get_forbidden_error();
971 }
972 }
973
974 /**
975 * Get UTM stats time series.
976 *
977 * @param WP_REST_Request $req The request object.
978 * @return array
979 */
980 public function get_utm_stats_time_series( $req ) {
981 return WPCOM_Client::request_as_blog_cached(
982 sprintf(
983 '/sites/%d/stats/utm/%s?%s',
984 Jetpack_Options::get_option( 'id' ),
985 $req->get_param( 'utm_params' ),
986 $this->filter_and_build_query_string(
987 $req->get_params()
988 )
989 ),
990 'v1.1',
991 array( 'timeout' => 10 )
992 );
993 }
994
995 /**
996 * Get Devices stats time series.
997 *
998 * @param WP_REST_Request $req The request object.
999 * @return array
1000 */
1001 public function get_devices_stats_time_series( $req ) {
1002 return WPCOM_Client::request_as_blog_cached(
1003 sprintf(
1004 '/sites/%d/stats/devices/%s?%s',
1005 Jetpack_Options::get_option( 'id' ),
1006 $req->get_param( 'device_property' ),
1007 $this->filter_and_build_query_string(
1008 $req->get_params()
1009 )
1010 ),
1011 'v1.1',
1012 array( 'timeout' => 10 )
1013 );
1014 }
1015
1016 /**
1017 * Get Location stats.
1018 *
1019 * @param WP_REST_Request $req The request object.
1020 * @return array
1021 */
1022 public function get_location_stats( $req ) {
1023 $params = $req->get_params();
1024 $geo_mode = $params['geo_mode'];
1025 unset( $params['geo_mode'] );
1026
1027 return $this->wpcom_stats->get_views_by_location( $geo_mode, $params );
1028 }
1029
1030 /**
1031 * Dismiss or delay stats notices.
1032 *
1033 * @param WP_REST_Request $req The request object.
1034 * @return array
1035 */
1036 public function update_notice_status( $req ) {
1037 return ( new Notices() )->update_notice( $req->get_param( 'id' ), $req->get_param( 'status' ), $req->get_param( 'postponed_for' ) );
1038 }
1039
1040 /**
1041 * Get stats notices.
1042 *
1043 * @return array
1044 */
1045 public function get_notice_status() {
1046 return ( new Notices() )->get_notices_to_show();
1047 }
1048
1049 /**
1050 * Get the list of spam referrers.
1051 *
1052 * @return array
1053 */
1054 public function get_referrer_spam_list() {
1055 return WPCOM_Client::request_as_blog(
1056 sprintf(
1057 '/sites/%d/stats/referrers/spam',
1058 Jetpack_Options::get_option( 'id' )
1059 ),
1060 'v1.1',
1061 array(
1062 'timeout' => 5,
1063 'method' => 'GET',
1064 )
1065 );
1066 }
1067
1068 /**
1069 * Mark a referrer as spam.
1070 *
1071 * @param WP_REST_Request $req The request object.
1072 * @return array
1073 */
1074 public function mark_referrer_spam( $req ) {
1075 return WPCOM_Client::request_as_blog(
1076 sprintf(
1077 '/sites/%d/stats/referrers/spam/new?%s',
1078 Jetpack_Options::get_option( 'id' ),
1079 $this->filter_and_build_query_string(
1080 $req->get_query_params()
1081 )
1082 ),
1083 'v1.1',
1084 array(
1085 'timeout' => 5,
1086 'method' => 'POST',
1087 )
1088 );
1089 }
1090
1091 /**
1092 * Unmark a referrer as spam.
1093 *
1094 * @param WP_REST_Request $req The request object.
1095 * @return array
1096 */
1097 public function unmark_referrer_spam( $req ) {
1098 return WPCOM_Client::request_as_blog(
1099 sprintf(
1100 '/sites/%d/stats/referrers/spam/delete?%s',
1101 Jetpack_Options::get_option( 'id' ),
1102 $this->filter_and_build_query_string(
1103 $req->get_query_params()
1104 )
1105 ),
1106 'v1.1',
1107 array(
1108 'timeout' => 5,
1109 'method' => 'POST',
1110 )
1111 );
1112 }
1113
1114 /**
1115 * Toggle modules on dashboard.
1116 *
1117 * @param WP_REST_Request $req The request object.
1118 * @return array
1119 */
1120 public function update_dashboard_modules( $req ) {
1121 // Clear dashboard modules cache.
1122 delete_transient( static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY );
1123 return WPCOM_Client::request_as_blog(
1124 sprintf(
1125 '/sites/%d/jetpack-stats-dashboard/modules?%s',
1126 Jetpack_Options::get_option( 'id' ),
1127 $this->filter_and_build_query_string(
1128 $req->get_query_params()
1129 )
1130 ),
1131 'v2',
1132 array(
1133 'timeout' => 5,
1134 'method' => 'POST',
1135 'headers' => array( 'Content-Type' => 'application/json' ),
1136 ),
1137 $req->get_body(),
1138 'wpcom'
1139 );
1140 }
1141
1142 /**
1143 * Get modules on dashboard.
1144 *
1145 * @param WP_REST_Request $req The request object.
1146 * @return array
1147 */
1148 public function get_dashboard_modules( $req ) {
1149 return WPCOM_Client::request_as_blog_cached(
1150 sprintf(
1151 '/sites/%d/jetpack-stats-dashboard/modules?%s',
1152 Jetpack_Options::get_option( 'id' ),
1153 $this->filter_and_build_query_string(
1154 $req->get_query_params()
1155 )
1156 ),
1157 'v2',
1158 array(
1159 'timeout' => 5,
1160 ),
1161 null,
1162 'wpcom',
1163 true,
1164 static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY
1165 );
1166 }
1167
1168 /**
1169 * Update module settings on dashboard.
1170 *
1171 * @param WP_REST_Request $req The request object.
1172 * @return array
1173 */
1174 public function update_dashboard_module_settings( $req ) {
1175 // Clear dashboard modules cache.
1176 delete_transient( static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY );
1177 return WPCOM_Client::request_as_blog(
1178 sprintf(
1179 '/sites/%d/jetpack-stats-dashboard/module-settings?%s',
1180 Jetpack_Options::get_option( 'id' ),
1181 $this->filter_and_build_query_string(
1182 $req->get_query_params()
1183 )
1184 ),
1185 'v2',
1186 array(
1187 'timeout' => 5,
1188 'method' => 'POST',
1189 'headers' => array( 'Content-Type' => 'application/json' ),
1190 ),
1191 $req->get_body(),
1192 'wpcom'
1193 );
1194 }
1195
1196 /**
1197 * Get module settings on dashboard.
1198 *
1199 * @param WP_REST_Request $req The request object.
1200 * @return array
1201 */
1202 public function get_dashboard_module_settings( $req ) {
1203 return WPCOM_Client::request_as_blog_cached(
1204 sprintf(
1205 '/sites/%d/jetpack-stats-dashboard/module-settings?%s',
1206 Jetpack_Options::get_option( 'id' ),
1207 $this->filter_and_build_query_string(
1208 $req->get_query_params()
1209 )
1210 ),
1211 'v2',
1212 array(
1213 'timeout' => 5,
1214 ),
1215 null,
1216 'wpcom',
1217 true,
1218 static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY
1219 );
1220 }
1221
1222 /**
1223 * Run commercial classification.
1224 *
1225 * @param WP_REST_Request $req The request object.
1226 * @return array
1227 */
1228 public function run_commercial_classification( $req ) {
1229 return WPCOM_Client::request_as_blog(
1230 sprintf(
1231 '/sites/%d/commercial-classification?%s',
1232 Jetpack_Options::get_option( 'id' ),
1233 $this->filter_and_build_query_string(
1234 $req->get_query_params()
1235 )
1236 ),
1237 'v2',
1238 array(
1239 'timeout' => 5,
1240 'method' => 'POST',
1241 ),
1242 null,
1243 'wpcom'
1244 );
1245 }
1246
1247 /**
1248 * Get purchases array; I don't see anything sensetive in there, so didn't sentinizie it.
1249 * Plus it is the same case as Jetpack.
1250 *
1251 * @param WP_REST_Request $req The request object.
1252 * @return array
1253 */
1254 public function get_site_purchases( $req ) {
1255 return WPCOM_Client::request_as_blog_cached(
1256 sprintf(
1257 '/upgrades?site=%d&%s',
1258 Jetpack_Options::get_option( 'id' ),
1259 $this->filter_and_build_query_string(
1260 $req->get_query_params()
1261 )
1262 ),
1263 'v1.2',
1264 array( 'timeout' => 10 ),
1265 null,
1266 'rest',
1267 false
1268 );
1269 }
1270
1271 /**
1272 * Return a WP_Error object with a forbidden error.
1273 */
1274 protected function get_forbidden_error() {
1275 $error_msg = esc_html__(
1276 'You are not allowed to perform this action.',
1277 'jetpack-stats-admin'
1278 );
1279
1280 return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
1281 }
1282
1283 /**
1284 * Filter and build query string from all the requested params.
1285 *
1286 * @param array $params The params to filter.
1287 * @param array $keys_to_unset The keys to unset from the params array.
1288 * @return string The filtered and built query string.
1289 */
1290 protected function filter_and_build_query_string( $params, $keys_to_unset = array() ) {
1291 if ( isset( $params['rest_route'] ) ) {
1292 unset( $params['rest_route'] );
1293 }
1294 if ( ! empty( $keys_to_unset ) && is_array( $keys_to_unset ) ) {
1295 foreach ( $keys_to_unset as $key ) {
1296 if ( isset( $params[ $key ] ) ) {
1297 unset( $params[ $key ] );
1298 }
1299 }
1300 }
1301 return http_build_query( $params );
1302 }
1303 }
1304