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
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src / class-rest-controller.php

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

1,313 lines 36.0 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' => 0,
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 // Forwarded to WPCOM as-is, whose schema rejects the null an omitted param would carry.
254 'default' => 0,
255 'description' => 'Postponed for (in seconds)',
256 'minimum' => 0,
257 ),
258 ),
259 )
260 );
261
262 // Get Stats notices.
263 register_rest_route(
264 static::$namespace,
265 sprintf( '/sites/%d/jetpack-stats-dashboard/notices', Jetpack_Options::get_option( 'id' ) ),
266 array(
267 'methods' => WP_REST_Server::READABLE,
268 'callback' => array( $this, 'get_notice_status' ),
269 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
270 'args' => array(
271 'include_details' => array(
272 'type' => 'boolean',
273 'default' => false,
274 'description' => 'Return a detail record per notice instead of a flat boolean map',
275 ),
276 ),
277 )
278 );
279
280 // Get referrer spam list.
281 register_rest_route(
282 static::$namespace,
283 sprintf( '/sites/%d/stats/referrers/spam', Jetpack_Options::get_option( 'id' ) ),
284 array(
285 'methods' => WP_REST_Server::READABLE,
286 'callback' => array( $this, 'get_referrer_spam_list' ),
287 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
288 )
289 );
290
291 // Mark referrer spam.
292 register_rest_route(
293 static::$namespace,
294 sprintf( '/sites/%d/stats/referrers/spam/new', Jetpack_Options::get_option( 'id' ) ),
295 array(
296 'methods' => WP_REST_Server::EDITABLE,
297 'callback' => array( $this, 'mark_referrer_spam' ),
298 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
299 'args' => array(
300 'domain' => array(
301 'required' => true,
302 'type' => 'string',
303 'description' => 'Domain of the referrer',
304 ),
305 ),
306 )
307 );
308
309 // Unmark referrer spam.
310 register_rest_route(
311 static::$namespace,
312 sprintf( '/sites/%d/stats/referrers/spam/delete', Jetpack_Options::get_option( 'id' ) ),
313 array(
314 'methods' => WP_REST_Server::EDITABLE,
315 'callback' => array( $this, 'unmark_referrer_spam' ),
316 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
317 'args' => array(
318 'domain' => array(
319 'required' => true,
320 'type' => 'string',
321 'description' => 'Domain of the referrer',
322 ),
323 ),
324 )
325 );
326
327 // Update dashboard modules.
328 register_rest_route(
329 static::$namespace,
330 sprintf( '/sites/%d/jetpack-stats-dashboard/modules', Jetpack_Options::get_option( 'id' ) ),
331 array(
332 'methods' => WP_REST_Server::EDITABLE,
333 'callback' => array( $this, 'update_dashboard_modules' ),
334 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
335 )
336 );
337
338 // Get dashboard modules.
339 register_rest_route(
340 static::$namespace,
341 sprintf( '/sites/%d/jetpack-stats-dashboard/modules', Jetpack_Options::get_option( 'id' ) ),
342 array(
343 'methods' => WP_REST_Server::READABLE,
344 'callback' => array( $this, 'get_dashboard_modules' ),
345 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
346 )
347 );
348
349 // Update dashboard module settings.
350 register_rest_route(
351 static::$namespace,
352 sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings', Jetpack_Options::get_option( 'id' ) ),
353 array(
354 'methods' => WP_REST_Server::EDITABLE,
355 'callback' => array( $this, 'update_dashboard_module_settings' ),
356 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
357 )
358 );
359
360 // Get dashboard module settings.
361 register_rest_route(
362 static::$namespace,
363 sprintf( '/sites/%d/jetpack-stats-dashboard/module-settings', Jetpack_Options::get_option( 'id' ) ),
364 array(
365 'methods' => WP_REST_Server::READABLE,
366 'callback' => array( $this, 'get_dashboard_module_settings' ),
367 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
368 )
369 );
370
371 // Get email stats as a list.
372 register_rest_route(
373 static::$namespace,
374 sprintf( '/sites/%d/stats/emails/(?P<resource>[\-\w\d]+)', Jetpack_Options::get_option( 'id' ) ),
375 array(
376 'methods' => WP_REST_Server::READABLE,
377 'callback' => array( $this, 'get_email_stats_list' ),
378 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
379 )
380 );
381
382 // Get Email opens stats for a single post.
383 register_rest_route(
384 static::$namespace,
385 sprintf( '/sites/%d/stats/opens/emails/(?P<post_id>[\d]+)/(?P<resource>[\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
386 array(
387 'methods' => WP_REST_Server::READABLE,
388 'callback' => array( $this, 'get_email_opens_stats_single' ),
389 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
390 )
391 );
392
393 // Get Email clicks stats for a single post.
394 register_rest_route(
395 static::$namespace,
396 sprintf( '/sites/%d/stats/clicks/emails/(?P<post_id>[\d]+)/(?P<resource>[\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
397 array(
398 'methods' => WP_REST_Server::READABLE,
399 'callback' => array( $this, 'get_email_clicks_stats_single' ),
400 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
401 )
402 );
403
404 // Get Email stats time series.
405 register_rest_route(
406 static::$namespace,
407 sprintf( '/sites/%d/stats/(?P<resource>[\-\w]+)/emails/(?P<post_id>[\d]+)', Jetpack_Options::get_option( 'id' ) ),
408 array(
409 'methods' => WP_REST_Server::READABLE,
410 'callback' => array( $this, 'get_email_stats_time_series' ),
411 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
412 )
413 );
414
415 // Get UTM stats time series.
416 register_rest_route(
417 static::$namespace,
418 // /stats/utm/utm_campaign,utm_source,utm_medium
419 sprintf( '/sites/%d/stats/utm/(?P<utm_params>[_,\-\w]+)', Jetpack_Options::get_option( 'id' ) ),
420 array(
421 'methods' => WP_REST_Server::READABLE,
422 'callback' => array( $this, 'get_utm_stats_time_series' ),
423 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
424 )
425 );
426
427 // Get Devices stats time series.
428 register_rest_route(
429 static::$namespace,
430 // /stats/devices/screensize
431 sprintf( '/sites/%d/stats/devices/(?P<device_property>[\w]+)', Jetpack_Options::get_option( 'id' ) ),
432 array(
433 'methods' => WP_REST_Server::READABLE,
434 'callback' => array( $this, 'get_devices_stats_time_series' ),
435 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
436 )
437 );
438
439 // Rerun commercial classificiation.
440 register_rest_route(
441 static::$namespace,
442 sprintf( '/sites/%d/commercial-classification', Jetpack_Options::get_option( 'id' ) ),
443 array(
444 'methods' => WP_REST_Server::EDITABLE,
445 'callback' => array( $this, 'run_commercial_classification' ),
446 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
447 )
448 );
449
450 // Purchases endpoint.
451 register_rest_route(
452 static::$namespace,
453 sprintf( '/sites/%d/purchases', Jetpack_Options::get_option( 'id' ) ),
454 array(
455 'methods' => WP_REST_Server::READABLE,
456 'callback' => array( $this, 'get_site_purchases' ),
457 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
458 )
459 );
460
461 // Get Location stats.
462 register_rest_route(
463 static::$namespace,
464 sprintf( '/sites/%d/stats/location-views/(?P<geo_mode>country|region|city)', Jetpack_Options::get_option( 'id' ) ),
465 array(
466 'methods' => WP_REST_Server::READABLE,
467 'callback' => array( $this, 'get_location_stats' ),
468 'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
469 )
470 );
471 }
472
473 /**
474 * Only administrators or users with capability `view_stats` can access the API.
475 *
476 * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
477 */
478 public function can_user_view_general_stats_callback() {
479 if ( current_user_can( 'manage_options' ) || current_user_can( 'view_stats' ) ) {
480 return true;
481 }
482
483 return $this->get_forbidden_error();
484 }
485
486 /**
487 * Only administrators or users with capability `activate_wordads` can access the API.
488 */
489 public function can_user_view_wordads_stats_callback() {
490 // phpcs:ignore WordPress.WP.Capabilities.Unknown
491 if ( current_user_can( 'manage_options' ) || current_user_can( 'activate_wordads' ) ) {
492 return true;
493 }
494
495 return $this->get_forbidden_error();
496 }
497
498 /**
499 * Stats resource endpoint.
500 *
501 * @param WP_REST_Request $req The request object.
502 * @return array
503 */
504 public function get_stats_resource( $req ) {
505 switch ( $req->get_param( 'resource' ) ) {
506 case 'file-downloads':
507 return $this->wpcom_stats->get_file_downloads( $req->get_params() );
508
509 case 'video-plays':
510 return $this->wpcom_stats->get_video_plays( $req->get_params() );
511
512 case 'clicks':
513 return $this->wpcom_stats->get_clicks( $req->get_params() );
514
515 case 'search-terms':
516 return $this->wpcom_stats->get_search_terms( $req->get_params() );
517
518 case 'top-authors':
519 return $this->wpcom_stats->get_top_authors( $req->get_params() );
520
521 case 'country-views':
522 return $this->wpcom_stats->get_views_by_country( $req->get_params() );
523
524 case 'referrers':
525 return $this->wpcom_stats->get_referrers( $req->get_params() );
526
527 case 'top-posts':
528 return $this->wpcom_stats->get_top_posts( $req->get_params() );
529
530 case 'archives':
531 return $this->wpcom_stats->get_archives( $req->get_params() );
532
533 case 'publicize':
534 return $this->wpcom_stats->get_publicize_followers( $req->get_params() );
535
536 case 'followers':
537 return $this->wpcom_stats->get_followers( $req->get_params() );
538
539 case 'tags':
540 return $this->wpcom_stats->get_tags( $req->get_params() );
541
542 case 'visits':
543 return $this->wpcom_stats->get_visits( $req->get_params() );
544
545 case 'comments':
546 return $this->wpcom_stats->get_top_comments( $req->get_params() );
547
548 case 'comment-followers':
549 return $this->wpcom_stats->get_comment_followers( $req->get_params() );
550
551 case 'streak':
552 return $this->wpcom_stats->get_streak( $req->get_params() );
553
554 case 'insights':
555 return $this->wpcom_stats->get_insights( $req->get_params() );
556
557 case 'highlights':
558 return $this->wpcom_stats->get_highlights( $req->get_params() );
559
560 case 'subscribers':
561 return WPCOM_Client::request_as_blog_cached(
562 sprintf(
563 '/sites/%d/stats/subscribers?%s',
564 Jetpack_Options::get_option( 'id' ),
565 $this->filter_and_build_query_string(
566 $req->get_query_params()
567 )
568 ),
569 'v1.1',
570 array( 'timeout' => 5 )
571 );
572
573 default:
574 return $this->get_forbidden_error();
575 }
576 }
577
578 /**
579 * Return likes of a single post.
580 *
581 * @param WP_REST_Request $req The request object.
582 */
583 public function get_single_post_likes( $req ) {
584 $response = wp_remote_get(
585 sprintf(
586 '%s/rest/v1.2/sites/%d/posts/%d/likes?%s',
587 Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
588 Jetpack_Options::get_option( 'id' ),
589 $req->get_param( 'resource_id' ),
590 $this->filter_and_build_query_string(
591 $req->get_params(),
592 array( 'resource_id' )
593 )
594 ),
595 array( 'timeout' => 5 )
596 );
597
598 $response_code = wp_remote_retrieve_response_code( $response );
599 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
600
601 if ( is_wp_error( $response ) ) {
602 return $response;
603 }
604
605 if ( 200 !== $response_code ) {
606 return new WP_Error(
607 isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error',
608 $response_body['message'] ?? 'unknown remote error',
609 array( 'status' => $response_code )
610 );
611 }
612
613 return $response_body;
614 }
615
616 /**
617 * Site Stats Resource endpoint.
618 *
619 * @param WP_REST_Request $req The request object.
620 * @return array
621 */
622 public function get_single_resource_stats( $req ) {
623 switch ( $req->get_param( 'resource' ) ) {
624 case 'post':
625 return $this->wpcom_stats->get_post_views(
626 intval( $req->get_param( 'resource_id' ) ),
627 $req->get_params()
628 );
629
630 case 'video':
631 return $this->wpcom_stats->get_video_details(
632 intval( $req->get_param( 'resource_id' ) ),
633 $req->get_params()
634 );
635
636 default:
637 return $this->get_forbidden_error();
638 }
639 }
640
641 /**
642 * Get brief information for a single post.
643 *
644 * @param WP_REST_Request $req The request object.
645 * @return array
646 */
647 public function get_single_post( $req ) {
648 $post = get_post( intval( $req->get_param( 'resource_id' ) ), 'OBJECT', 'display' );
649 if ( is_wp_error( $post ) || empty( $post ) ) {
650 return $post;
651 }
652
653 // The endpoint should be as compatible as possible with `/sites/$site_id/posts/$post_id`.
654 // 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.
655 // 'like_count' is not included in the response because it's available through another endpoint `/sites/$site_id/posts/$post_id/likes`.
656 return array(
657 'ID' => $post->ID,
658 'site_ID' => Jetpack_Options::get_option( 'id' ),
659 'title' => $post->post_title,
660 'URL' => get_permalink( $post->ID ),
661 'type' => $post->post_type,
662 'status' => $post->post_status,
663 'discussion' => array( 'comment_count' => intval( $post->comment_count ) ),
664 'date' => $post->post_date,
665 'post_thumbnail' => array( 'URL' => get_the_post_thumbnail_url( $post->ID ) ),
666 );
667 }
668
669 /**
670 * Get site stats.
671 *
672 * @param WP_REST_Request $req The request object.
673 * @return array
674 */
675 public function get_site_stats( $req ) {
676 return $this->wpcom_stats->get_stats( $req->get_params() );
677 }
678
679 /**
680 * List posts for the site.
681 *
682 * @param WP_REST_Request $req The request object.
683 * @return array
684 */
685 public function get_site_posts( $req ) {
686 // Force wpcom response.
687 $params = array_merge( array( 'force' => 'wpcom' ), $req->get_params() );
688 $response = wp_remote_get(
689 sprintf(
690 '%s/rest/v1.1/sites/%d/posts?%s',
691 Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
692 Jetpack_Options::get_option( 'id' ),
693 $req->get_param( 'resource_id' ),
694 $this->filter_and_build_query_string( $params, array( 'resource_id' ) )
695 ),
696 array( 'timeout' => 5 )
697 );
698
699 $response_code = wp_remote_retrieve_response_code( $response );
700 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
701
702 if ( is_wp_error( $response ) ) {
703 return $response;
704 }
705
706 if ( 200 !== $response_code ) {
707 return new WP_Error(
708 isset( $response_body['error'] ) ? 'remote-error-' . $response_body['error'] : 'remote-error',
709 $response_body['message'] ?? 'unknown remote error',
710 array( 'status' => $response_code )
711 );
712 }
713
714 return $response_body;
715 }
716
717 /**
718 * Get site subscribers counts.
719 *
720 * @param WP_REST_Request $req The request object.
721 *
722 * @return array
723 */
724 public function get_site_subscribers_counts( $req ) {
725 return WPCOM_Client::request_as_blog_cached(
726 sprintf(
727 '/sites/%d/subscribers/counts?%s',
728 Jetpack_Options::get_option( 'id' ),
729 $this->filter_and_build_query_string(
730 $req->get_query_params()
731 )
732 ),
733 'v2',
734 array( 'timeout' => 5 ),
735 null,
736 'wpcom'
737 );
738 }
739
740 /**
741 * Get site plan usage.
742 *
743 * @param WP_REST_Request $req The request object.
744 *
745 * @return array
746 */
747 public function get_site_plan_usage( $req ) {
748 return WPCOM_Client::request_as_blog_cached(
749 sprintf(
750 '/sites/%d/jetpack-stats/usage?%s',
751 Jetpack_Options::get_option( 'id' ),
752 $this->filter_and_build_query_string(
753 $req->get_query_params()
754 )
755 ),
756 'v2',
757 array( 'timeout' => 5 ),
758 null,
759 'wpcom',
760 false
761 );
762 }
763
764 /**
765 * Post user feedback for Jetpack Stats.
766 *
767 * @param WP_REST_Request $req The request object.
768 *
769 * @return array
770 */
771 public function post_user_feedback( $req ) {
772 $current_user = wp_get_current_user();
773 $body_from_req = json_decode( $req->get_body(), true );
774 $body_data = is_array( $body_from_req ) ? $body_from_req : array();
775 $user_email = $current_user->user_email;
776
777 return WPCOM_Client::request_as_blog_cached(
778 sprintf(
779 '/sites/%d/jetpack-stats/user-feedback?%s',
780 Jetpack_Options::get_option( 'id' ),
781 $this->filter_and_build_query_string(
782 $req->get_query_params()
783 )
784 ),
785 'v2',
786 array(
787 'timeout' => 5,
788 'method' => 'POST',
789 'headers' => array( 'Content-Type' => 'application/json' ),
790 ),
791 wp_json_encode(
792 array_merge(
793 $body_data,
794 array(
795 'user_email' => $user_email,
796 )
797 ),
798 JSON_UNESCAPED_SLASHES
799 ),
800 'wpcom'
801 );
802 }
803
804 /**
805 * Whether site has never published post.
806 *
807 * @param WP_REST_Request $req The request object.
808 * @return array
809 */
810 public function site_has_never_published_post( $req ) {
811 return WPCOM_Client::request_as_blog_cached(
812 sprintf(
813 '/sites/%d/site-has-never-published-post?%s',
814 Jetpack_Options::get_option( 'id' ),
815 $this->filter_and_build_query_string(
816 $req->get_params()
817 )
818 ),
819 'v2',
820 array( 'timeout' => 5 ),
821 null,
822 'wpcom'
823 );
824 }
825
826 /**
827 * Get detailed WordAds earnings information for the site.
828 *
829 * @param WP_REST_Request $req The request object.
830 * @return array
831 */
832 public function get_wordads_earnings( $req ) {
833 return WPCOM_Client::request_as_blog_cached(
834 sprintf(
835 '/sites/%d/wordads/earnings?%s',
836 Jetpack_Options::get_option( 'id' ),
837 $this->filter_and_build_query_string(
838 $req->get_params()
839 )
840 ),
841 'v1.1',
842 array( 'timeout' => 5 )
843 );
844 }
845
846 /**
847 * Get WordAds stats for the site.
848 *
849 * @param WP_REST_Request $req The request object.
850 * @return array
851 */
852 public function get_wordads_stats( $req ) {
853 return WPCOM_Client::request_as_blog_cached(
854 sprintf(
855 '/sites/%d/wordads/stats?%s',
856 Jetpack_Options::get_option( 'id' ),
857 $this->filter_and_build_query_string(
858 $req->get_params()
859 )
860 ),
861 'v1.1',
862 array( 'timeout' => 5 )
863 );
864 }
865
866 /**
867 * Get Email stats as a list.
868 *
869 * @param WP_REST_Request $req The request object.
870 * @return array
871 */
872 public function get_email_stats_list( $req ) {
873 switch ( $req->get_param( 'resource' ) ) {
874 case 'summary':
875 return WPCOM_Client::request_as_blog_cached(
876 sprintf(
877 '/sites/%d/stats/emails/%s?%s',
878 Jetpack_Options::get_option( 'id' ),
879 $req->get_param( 'resource' ),
880 $this->filter_and_build_query_string(
881 $req->get_params()
882 )
883 ),
884 'v1.1',
885 array( 'timeout' => 5 )
886 );
887 default:
888 return $this->get_forbidden_error();
889 }
890 }
891
892 /**
893 * Get Email opens stats for a single post.
894 *
895 * @param WP_REST_Request $req The request object.
896 * @return array
897 */
898 public function get_email_opens_stats_single( $req ) {
899 switch ( $req->get_param( 'resource' ) ) {
900 case 'client':
901 case 'device':
902 case 'country':
903 case 'rate':
904 return WPCOM_Client::request_as_blog_cached(
905 sprintf(
906 '/sites/%d/stats/opens/emails/%d/%s?%s',
907 Jetpack_Options::get_option( 'id' ),
908 $req->get_param( 'post_id' ),
909 $req->get_param( 'resource' ),
910 $this->filter_and_build_query_string(
911 $req->get_params()
912 )
913 ),
914 'v1.1',
915 array( 'timeout' => 5 )
916 );
917 default:
918 return $this->get_forbidden_error();
919 }
920 }
921
922 /**
923 * Get Email clicks stats for a single post.
924 *
925 * @param WP_REST_Request $req The request object.
926 * @return array
927 */
928 public function get_email_clicks_stats_single( $req ) {
929 switch ( $req->get_param( 'resource' ) ) {
930 case 'client':
931 case 'device':
932 case 'country':
933 case 'rate':
934 case 'link':
935 case 'user-content-link':
936 return WPCOM_Client::request_as_blog_cached(
937 sprintf(
938 '/sites/%d/stats/clicks/emails/%d/%s?%s',
939 Jetpack_Options::get_option( 'id' ),
940 $req->get_param( 'post_id' ),
941 $req->get_param( 'resource' ),
942 $this->filter_and_build_query_string(
943 $req->get_params()
944 )
945 ),
946 'v1.1',
947 array( 'timeout' => 5 )
948 );
949 default:
950 return $this->get_forbidden_error();
951 }
952 }
953
954 /**
955 * Get Email stats time series.
956 *
957 * @param WP_REST_Request $req The request object.
958 * @return array
959 */
960 public function get_email_stats_time_series( $req ) {
961 switch ( $req->get_param( 'resource' ) ) {
962 case 'opens':
963 case 'clicks':
964 return WPCOM_Client::request_as_blog_cached(
965 sprintf(
966 '/sites/%d/stats/%s/emails/%d?%s',
967 Jetpack_Options::get_option( 'id' ),
968 $req->get_param( 'resource' ),
969 $req->get_param( 'post_id' ),
970 $this->filter_and_build_query_string(
971 $req->get_params()
972 )
973 ),
974 'v1.1',
975 array( 'timeout' => 5 )
976 );
977 default:
978 return $this->get_forbidden_error();
979 }
980 }
981
982 /**
983 * Get UTM stats time series.
984 *
985 * @param WP_REST_Request $req The request object.
986 * @return array
987 */
988 public function get_utm_stats_time_series( $req ) {
989 return WPCOM_Client::request_as_blog_cached(
990 sprintf(
991 '/sites/%d/stats/utm/%s?%s',
992 Jetpack_Options::get_option( 'id' ),
993 $req->get_param( 'utm_params' ),
994 $this->filter_and_build_query_string(
995 $req->get_params()
996 )
997 ),
998 'v1.1',
999 array( 'timeout' => 10 )
1000 );
1001 }
1002
1003 /**
1004 * Get Devices stats time series.
1005 *
1006 * @param WP_REST_Request $req The request object.
1007 * @return array
1008 */
1009 public function get_devices_stats_time_series( $req ) {
1010 return WPCOM_Client::request_as_blog_cached(
1011 sprintf(
1012 '/sites/%d/stats/devices/%s?%s',
1013 Jetpack_Options::get_option( 'id' ),
1014 $req->get_param( 'device_property' ),
1015 $this->filter_and_build_query_string(
1016 $req->get_params()
1017 )
1018 ),
1019 'v1.1',
1020 array( 'timeout' => 10 )
1021 );
1022 }
1023
1024 /**
1025 * Get Location stats.
1026 *
1027 * @param WP_REST_Request $req The request object.
1028 * @return array
1029 */
1030 public function get_location_stats( $req ) {
1031 $params = $req->get_params();
1032 $geo_mode = $params['geo_mode'];
1033 unset( $params['geo_mode'] );
1034
1035 return $this->wpcom_stats->get_views_by_location( $geo_mode, $params );
1036 }
1037
1038 /**
1039 * Dismiss or delay stats notices.
1040 *
1041 * @param WP_REST_Request $req The request object.
1042 * @return array
1043 */
1044 public function update_notice_status( $req ) {
1045 return ( new Notices() )->update_notice( $req->get_param( 'id' ), $req->get_param( 'status' ), $req->get_param( 'postponed_for' ) );
1046 }
1047
1048 /**
1049 * Get stats notices.
1050 *
1051 * @param WP_REST_Request $req The request object.
1052 * @return array
1053 */
1054 public function get_notice_status( $req ) {
1055 return ( new Notices() )->get_notices_to_show( (bool) $req->get_param( 'include_details' ) );
1056 }
1057
1058 /**
1059 * Get the list of spam referrers.
1060 *
1061 * @return array
1062 */
1063 public function get_referrer_spam_list() {
1064 return WPCOM_Client::request_as_blog(
1065 sprintf(
1066 '/sites/%d/stats/referrers/spam',
1067 Jetpack_Options::get_option( 'id' )
1068 ),
1069 'v1.1',
1070 array(
1071 'timeout' => 5,
1072 'method' => 'GET',
1073 )
1074 );
1075 }
1076
1077 /**
1078 * Mark a referrer as spam.
1079 *
1080 * @param WP_REST_Request $req The request object.
1081 * @return array
1082 */
1083 public function mark_referrer_spam( $req ) {
1084 return WPCOM_Client::request_as_blog(
1085 sprintf(
1086 '/sites/%d/stats/referrers/spam/new?%s',
1087 Jetpack_Options::get_option( 'id' ),
1088 $this->filter_and_build_query_string(
1089 $req->get_query_params()
1090 )
1091 ),
1092 'v1.1',
1093 array(
1094 'timeout' => 5,
1095 'method' => 'POST',
1096 )
1097 );
1098 }
1099
1100 /**
1101 * Unmark a referrer as spam.
1102 *
1103 * @param WP_REST_Request $req The request object.
1104 * @return array
1105 */
1106 public function unmark_referrer_spam( $req ) {
1107 return WPCOM_Client::request_as_blog(
1108 sprintf(
1109 '/sites/%d/stats/referrers/spam/delete?%s',
1110 Jetpack_Options::get_option( 'id' ),
1111 $this->filter_and_build_query_string(
1112 $req->get_query_params()
1113 )
1114 ),
1115 'v1.1',
1116 array(
1117 'timeout' => 5,
1118 'method' => 'POST',
1119 )
1120 );
1121 }
1122
1123 /**
1124 * Toggle modules on dashboard.
1125 *
1126 * @param WP_REST_Request $req The request object.
1127 * @return array
1128 */
1129 public function update_dashboard_modules( $req ) {
1130 // Clear dashboard modules cache.
1131 delete_transient( static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY );
1132 return WPCOM_Client::request_as_blog(
1133 sprintf(
1134 '/sites/%d/jetpack-stats-dashboard/modules?%s',
1135 Jetpack_Options::get_option( 'id' ),
1136 $this->filter_and_build_query_string(
1137 $req->get_query_params()
1138 )
1139 ),
1140 'v2',
1141 array(
1142 'timeout' => 5,
1143 'method' => 'POST',
1144 'headers' => array( 'Content-Type' => 'application/json' ),
1145 ),
1146 $req->get_body(),
1147 'wpcom'
1148 );
1149 }
1150
1151 /**
1152 * Get modules on dashboard.
1153 *
1154 * @param WP_REST_Request $req The request object.
1155 * @return array
1156 */
1157 public function get_dashboard_modules( $req ) {
1158 return WPCOM_Client::request_as_blog_cached(
1159 sprintf(
1160 '/sites/%d/jetpack-stats-dashboard/modules?%s',
1161 Jetpack_Options::get_option( 'id' ),
1162 $this->filter_and_build_query_string(
1163 $req->get_query_params()
1164 )
1165 ),
1166 'v2',
1167 array(
1168 'timeout' => 5,
1169 ),
1170 null,
1171 'wpcom',
1172 true,
1173 static::JETPACK_STATS_DASHBOARD_MODULES_CACHE_KEY
1174 );
1175 }
1176
1177 /**
1178 * Update module settings on dashboard.
1179 *
1180 * @param WP_REST_Request $req The request object.
1181 * @return array
1182 */
1183 public function update_dashboard_module_settings( $req ) {
1184 // Clear dashboard modules cache.
1185 delete_transient( static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY );
1186 return WPCOM_Client::request_as_blog(
1187 sprintf(
1188 '/sites/%d/jetpack-stats-dashboard/module-settings?%s',
1189 Jetpack_Options::get_option( 'id' ),
1190 $this->filter_and_build_query_string(
1191 $req->get_query_params()
1192 )
1193 ),
1194 'v2',
1195 array(
1196 'timeout' => 5,
1197 'method' => 'POST',
1198 'headers' => array( 'Content-Type' => 'application/json' ),
1199 ),
1200 $req->get_body(),
1201 'wpcom'
1202 );
1203 }
1204
1205 /**
1206 * Get module settings on dashboard.
1207 *
1208 * @param WP_REST_Request $req The request object.
1209 * @return array
1210 */
1211 public function get_dashboard_module_settings( $req ) {
1212 return WPCOM_Client::request_as_blog_cached(
1213 sprintf(
1214 '/sites/%d/jetpack-stats-dashboard/module-settings?%s',
1215 Jetpack_Options::get_option( 'id' ),
1216 $this->filter_and_build_query_string(
1217 $req->get_query_params()
1218 )
1219 ),
1220 'v2',
1221 array(
1222 'timeout' => 5,
1223 ),
1224 null,
1225 'wpcom',
1226 true,
1227 static::JETPACK_STATS_DASHBOARD_MODULE_SETTINGS_CACHE_KEY
1228 );
1229 }
1230
1231 /**
1232 * Run commercial classification.
1233 *
1234 * @param WP_REST_Request $req The request object.
1235 * @return array
1236 */
1237 public function run_commercial_classification( $req ) {
1238 return WPCOM_Client::request_as_blog(
1239 sprintf(
1240 '/sites/%d/commercial-classification?%s',
1241 Jetpack_Options::get_option( 'id' ),
1242 $this->filter_and_build_query_string(
1243 $req->get_query_params()
1244 )
1245 ),
1246 'v2',
1247 array(
1248 'timeout' => 5,
1249 'method' => 'POST',
1250 ),
1251 null,
1252 'wpcom'
1253 );
1254 }
1255
1256 /**
1257 * Get purchases array; I don't see anything sensetive in there, so didn't sentinizie it.
1258 * Plus it is the same case as Jetpack.
1259 *
1260 * @param WP_REST_Request $req The request object.
1261 * @return array
1262 */
1263 public function get_site_purchases( $req ) {
1264 return WPCOM_Client::request_as_blog_cached(
1265 sprintf(
1266 '/upgrades?site=%d&%s',
1267 Jetpack_Options::get_option( 'id' ),
1268 $this->filter_and_build_query_string(
1269 $req->get_query_params()
1270 )
1271 ),
1272 'v1.2',
1273 array( 'timeout' => 10 ),
1274 null,
1275 'rest',
1276 false
1277 );
1278 }
1279
1280 /**
1281 * Return a WP_Error object with a forbidden error.
1282 */
1283 protected function get_forbidden_error() {
1284 $error_msg = esc_html__(
1285 'You are not allowed to perform this action.',
1286 'jetpack-stats-admin'
1287 );
1288
1289 return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
1290 }
1291
1292 /**
1293 * Filter and build query string from all the requested params.
1294 *
1295 * @param array $params The params to filter.
1296 * @param array $keys_to_unset The keys to unset from the params array.
1297 * @return string The filtered and built query string.
1298 */
1299 protected function filter_and_build_query_string( $params, $keys_to_unset = array() ) {
1300 if ( isset( $params['rest_route'] ) ) {
1301 unset( $params['rest_route'] );
1302 }
1303 if ( ! empty( $keys_to_unset ) && is_array( $keys_to_unset ) ) {
1304 foreach ( $keys_to_unset as $key ) {
1305 if ( isset( $params[ $key ] ) ) {
1306 unset( $params[ $key ] );
1307 }
1308 }
1309 }
1310 return http_build_query( $params );
1311 }
1312 }
1313