PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src / class-rest-controller.php
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src Last commit date
class-admin-post-list-column.php 6 days ago class-dashboard.php 6 days ago class-main.php 6 days ago class-notices.php 8 months ago class-odyssey-assets.php 3 weeks ago class-odyssey-config-data.php 6 days ago class-rest-controller.php 2 months ago class-wp-dashboard-odyssey-widget.php 2 years ago class-wpcom-client.php 6 days ago
class-rest-controller.php
1304 lines
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