PluginProbe
Parse.ly / 3.19.2
Parse.ly v3.19.2
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / src / rest-api / stats / class-endpoint-post.php

class-endpoint-post.php in Parse.ly 3.19.2, at src/rest-api/stats/class-endpoint-post.php

500 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Stats API Endpoint: Post
4 *
5 * @package Parsely
6 * @since 3.17.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\REST_API\Stats;
12
13 use Parsely\REST_API\Base_Endpoint;
14 use Parsely\REST_API\Use_Post_ID_Parameter_Trait;
15 use Parsely\Services\Content_API\Content_API_Service;
16 use Parsely\Utils\Utils;
17 use stdClass;
18 use WP_Error;
19 use WP_Post;
20 use WP_REST_Request;
21 use WP_REST_Response;
22
23 /**
24 * The Stats API Post endpoint.
25 *
26 * Provides an endpoint for retrieving post details, referrers, and related
27 * posts for a given post.
28 *
29 * @since 3.17.0
30 *
31 * @phpstan-type Referrer_Data array{
32 * metrics: array{
33 * referrers_views?: int
34 * },
35 * type?: string,
36 * name?: string
37 * }
38 *
39 * @phpstan-type Referrer_Type_Data array{
40 * views: string,
41 * viewsPercentage: string
42 * }
43 *
44 * @phpstan-type Referrers_Data_Item array{
45 * views: string,
46 * viewsPercentage: string,
47 * datasetViewsPercentage: string
48 * }
49 */
50 class Endpoint_Post extends Base_Endpoint {
51 use Use_Post_ID_Parameter_Trait;
52 use Post_Data_Trait;
53 use Related_Posts_Trait;
54
55 /**
56 * The Parse.ly Content API service.
57 *
58 * @since 3.17.0
59 *
60 * @var Content_API_Service $content_api
61 */
62 public $content_api;
63
64 /**
65 * The total views of the post.
66 *
67 * @since 3.17.0
68 *
69 * @var int
70 */
71 private $total_views = 0;
72
73 /**
74 * Constructor.
75 *
76 * @since 3.17.0
77 *
78 * @param Stats_Controller $controller The controller.
79 */
80 public function __construct( Stats_Controller $controller ) {
81 parent::__construct( $controller );
82 $this->content_api = $this->parsely->get_content_api();
83 }
84
85 /**
86 * Returns the endpoint name.
87 *
88 * @since 3.17.0
89 *
90 * @return string The endpoint name.
91 */
92 public static function get_endpoint_name(): string {
93 return 'post';
94 }
95
96 /**
97 * Registers the routes for the endpoint.
98 *
99 * @since 3.17.0
100 */
101 public function register_routes(): void {
102 /**
103 * GET /stats/post/{post_id}/details
104 * Returns the analytics details of a post.
105 */
106 $this->register_rest_route_with_post_id(
107 '/details',
108 array( 'GET' ),
109 array( $this, 'get_post_details' ),
110 array_merge(
111 array(
112 'period_start' => array(
113 'description' => __( 'The start of the period.', 'wp-parsely' ),
114 'type' => 'string',
115 'required' => false,
116 ),
117 'period_end' => array(
118 'description' => __( 'The end of the period.', 'wp-parsely' ),
119 'type' => 'string',
120 'required' => false,
121 ),
122 ),
123 $this->get_itm_source_param_args()
124 )
125 );
126
127 /**
128 * GET /stats/post/{post_id}/referrers
129 * Returns the referrers of a post.
130 */
131 $this->register_rest_route_with_post_id(
132 '/referrers',
133 array( 'GET' ),
134 array( $this, 'get_post_referrers' ),
135 array(
136 'period_start' => array(
137 'description' => __( 'The start of the period.', 'wp-parsely' ),
138 'type' => 'string',
139 'required' => false,
140 ),
141 'period_end' => array(
142 'description' => __( 'The end of the period.', 'wp-parsely' ),
143 'type' => 'string',
144 'required' => false,
145 ),
146 'total_views' => array(
147 'description' => __( 'The total views of the post.', 'wp-parsely' ),
148 'type' => 'string',
149 'required' => false,
150 'default' => '0',
151 ),
152 )
153 );
154
155 /**
156 * GET /stats/post/{post_id}/related
157 * Returns the related posts of a post.
158 */
159 $this->register_rest_route_with_post_id(
160 '/related',
161 array( 'GET' ),
162 array( $this, 'get_related_posts' ),
163 $this->get_related_posts_param_args()
164 );
165 }
166
167 /**
168 * API Endpoint: GET /stats/post/{post_id}/details
169 *
170 * Gets the details of a post.
171 *
172 * @since 3.17.0
173 *
174 * @param WP_REST_Request $request The request object.
175 * @return WP_REST_Response|WP_Error The response object.
176 */
177 public function get_post_details( WP_REST_Request $request ) {
178 /**
179 * The post object.
180 *
181 * @var WP_Post $post
182 */
183 $post = $request->get_param( 'post' );
184 $permalink = get_permalink( $post->ID );
185
186 if ( ! is_string( $permalink ) ) {
187 return new WP_Error( 'invalid_post', __( 'Invalid post.', 'wp-parsely' ), array( 'status' => 404 ) );
188 }
189
190 // Set the itm_source parameter.
191 $this->set_itm_source_from_request( $request );
192
193 // Get the data from the API.
194 $analytics_request = $this->content_api->get_post_details(
195 $permalink,
196 $request->get_param( 'period_start' ),
197 $request->get_param( 'period_end' )
198 );
199
200 if ( is_wp_error( $analytics_request ) ) {
201 return $analytics_request;
202 }
203
204 $post_data = array();
205
206 /**
207 * The analytics data object.
208 *
209 * @var array<string,array<mixed>> $analytics_request
210 */
211 foreach ( $analytics_request as $data ) {
212 $post_data[] = $this->extract_post_data( $data );
213 }
214
215 $response_data = array(
216 'params' => $request->get_params(),
217 'data' => $post_data,
218 );
219
220 return new WP_REST_Response( $response_data, 200 );
221 }
222
223 /**
224 * API Endpoint: GET /stats/post/{post_id}/referrers
225 *
226 * Gets the referrers of a post.
227 *
228 * @since 3.17.0
229 *
230 * @param WP_REST_Request $request The request object.
231 * @return WP_REST_Response|WP_Error The response object.
232 */
233 public function get_post_referrers( WP_REST_Request $request ) {
234 /**
235 * The post object.
236 *
237 * @var WP_Post $post
238 */
239 $post = $request->get_param( 'post' );
240 $permalink = get_permalink( $post->ID );
241
242 if ( ! is_string( $permalink ) ) {
243 return new WP_Error( 'invalid_post', __( 'Invalid post.', 'wp-parsely' ), array( 'status' => 404 ) );
244 }
245
246 // Set the itm_source parameter.
247 $this->set_itm_source_from_request( $request );
248
249 // Get the total views.
250 $total_views = $request->get_param( 'total_views' ) ?? 0;
251
252 if ( is_string( $total_views ) ) {
253 $total_views = Utils::convert_to_positive_integer( $total_views );
254 }
255
256 $this->total_views = $total_views;
257
258 // Get the data from the API.
259 $analytics_request = $this->content_api->get_post_referrers(
260 $permalink,
261 $request->get_param( 'period_start' ),
262 $request->get_param( 'period_end' )
263 );
264
265 if ( is_wp_error( $analytics_request ) ) {
266 return $analytics_request;
267 }
268
269 /**
270 * The analytics data object.
271 *
272 * @var array<Referrer_Data> $analytics_request
273 */
274 $referrers_types = $this->generate_referrer_types_data( $analytics_request );
275 $direct_views = Utils::convert_to_positive_integer(
276 $referrers_types['direct']['views'] ?? '0'
277 );
278 $referrers_top = $this->generate_referrers_data( 5, $analytics_request, $direct_views );
279
280 $response_data = array(
281 'params' => $request->get_params(),
282 'data' => array(
283 'top' => $referrers_top,
284 'types' => $referrers_types,
285 ),
286 );
287
288 return new WP_REST_Response( $response_data, 200 );
289 }
290
291 /**
292 * API Endpoint: GET /stats/post/{post_id}/related
293 *
294 * Gets the related posts of a post.
295 *
296 * @since 3.17.0
297 *
298 * @param WP_REST_Request $request The request object.
299 * @return WP_REST_Response|WP_Error The response data.
300 */
301 public function get_related_posts( WP_REST_Request $request ) {
302 /**
303 * The post object.
304 *
305 * @var WP_Post $post
306 */
307 $post = $request->get_param( 'post' );
308
309 /**
310 * The post permalink.
311 *
312 * @var string $permalink
313 */
314 $permalink = get_permalink( $post->ID );
315
316 $related_posts = $this->get_related_posts_of_url( $request, $permalink );
317
318 $response_data = array(
319 'params' => $request->get_params(),
320 'data' => $related_posts,
321 );
322
323 return new WP_REST_Response( $response_data, 200 );
324 }
325
326 /**
327 * Generates the referrer types data.
328 *
329 * Referrer types are:
330 * - `social`: Views coming from social media.
331 * - `search`: Views coming from search engines.
332 * - `other`: Views coming from other referrers, like external websites.
333 * - `internal`: Views coming from linking pages of the same website.
334 *
335 * Returned object properties:
336 * - `views`: The number of views.
337 * - `viewsPercentage`: The number of views as a percentage, compared to the
338 * total views of all referrer types.
339 *
340 * @since 3.6.0
341 * @since 3.17.0 Moved from the `Referrers_Post_Detail_API_Proxy` class.
342 *
343 * @param array<Referrer_Data> $response The response received by the proxy.
344 * @return array<string, Referrer_Type_Data> The generated data.
345 */
346 private function generate_referrer_types_data( array $response ): array {
347 $result = array();
348 $total_referrer_views = 0; // Views from all referrer types combined.
349
350 // Set referrer type order as it is displayed in the Parse.ly dashboard.
351 $referrer_type_keys = array( 'social', 'search', 'other', 'internal', 'direct' );
352 foreach ( $referrer_type_keys as $key ) {
353 $result[ $key ] = array( 'views' => 0 );
354 }
355
356 // Set views and views totals.
357 foreach ( $response as $referrer_data ) {
358 /**
359 * @var int $current_views
360 */
361 $current_views = $referrer_data['metrics']['referrers_views'] ?? 0;
362 $total_referrer_views += $current_views;
363
364 /**
365 * @var string $current_key
366 */
367 $current_key = $referrer_data['type'] ?? '';
368 if ( '' !== $current_key ) {
369 if ( ! isset( $result[ $current_key ]['views'] ) ) {
370 $result[ $current_key ] = array( 'views' => 0 );
371 }
372
373 $result[ $current_key ]['views'] += $current_views;
374 }
375 }
376
377 // Add direct and total views to the object.
378 $result['direct']['views'] = $this->total_views - $total_referrer_views;
379 $result['totals'] = array( 'views' => $this->total_views );
380
381 // Remove referrer types without views.
382 foreach ( $referrer_type_keys as $key ) {
383 if ( 0 === $result[ $key ]['views'] ) {
384 unset( $result[ $key ] );
385 }
386 }
387
388 // Set percentage values and format numbers.
389 foreach ( $result as $key => $value ) {
390 // Set and format percentage values.
391 $result[ $key ]['viewsPercentage'] = $this->get_i18n_percentage(
392 absint( $value['views'] ),
393 $this->total_views
394 );
395
396 // Format views values.
397 $result[ $key ]['views'] = number_format_i18n( $result[ $key ]['views'] );
398 }
399
400 return $result;
401 }
402
403 /**
404 * Generates the top referrers data.
405 *
406 * Returned object properties:
407 * - `views`: The number of views.
408 * - `viewsPercentage`: The number of views as a percentage, compared to the
409 * total views of all referrer types.
410 * - `datasetViewsPercentage`: The number of views as a percentage, compared
411 * to the total views of the current dataset.
412 *
413 * @since 3.6.0
414 * @since 3.17.0 Moved from the `Referrers_Post_Detail_API_Proxy` class.
415 *
416 * @param int $limit The limit of returned referrers.
417 * @param array<Referrer_Data> $response The response received by the proxy.
418 * @param int $direct_views The count of direct views.
419 * @return array<string, Referrers_Data_Item> The generated data.
420 */
421 private function generate_referrers_data(
422 int $limit,
423 array $response,
424 int $direct_views
425 ): array {
426 $temp_views = array();
427 $totals = 0;
428 $referrer_count = count( $response );
429
430 // Set views and views totals.
431 $loop_count = $referrer_count > $limit ? $limit : $referrer_count;
432 for ( $i = 0; $i < $loop_count; $i++ ) {
433 $data = $response[ $i ];
434
435 /**
436 * @var int $referrer_views
437 */
438 $referrer_views = $data['metrics']['referrers_views'] ?? 0;
439 $totals += $referrer_views;
440 if ( isset( $data['name'] ) ) {
441 $temp_views[ $data['name'] ] = $referrer_views;
442 }
443 }
444
445 // If applicable, add the direct views.
446 if ( isset( $referrer_views ) && $direct_views >= $referrer_views ) {
447 $temp_views['direct'] = $direct_views;
448 $totals += $direct_views;
449 arsort( $temp_views );
450 if ( count( $temp_views ) > $limit ) {
451 $totals -= array_pop( $temp_views );
452 }
453 }
454
455 // Convert temporary array to result object and add totals.
456 $result = array();
457 foreach ( $temp_views as $key => $value ) {
458 $result[ $key ] = array( 'views' => $value );
459 }
460 $result['totals'] = array( 'views' => $totals );
461
462 // Set percentage values and format numbers.
463 foreach ( $result as $key => $value ) {
464 // Percentage against all referrer views, even those not included
465 // in the dataset due to the $limit argument.
466 $result[ $key ]['viewsPercentage'] = $this
467 ->get_i18n_percentage( absint( $value['views'] ), $this->total_views );
468
469 // Percentage against the current dataset that is limited due to the
470 // $limit argument.
471 $result[ $key ]['datasetViewsPercentage'] = $this
472 ->get_i18n_percentage( absint( $value['views'] ), $totals );
473
474 // Format views values.
475 $result[ $key ]['views'] = number_format_i18n( $result[ $key ]['views'] );
476 }
477
478 return $result;
479 }
480
481 /**
482 * Returns the passed number compared to the passed total, in an
483 * internationalized percentage format.
484 *
485 * @since 3.6.0
486 * @since 3.17.0 Moved from the `Referrers_Post_Detail_API_Proxy` class.
487 *
488 * @param int $number The number to be calculated as a percentage.
489 * @param int $total The total number to compare against.
490 * @return string|false The internationalized percentage or false on error.
491 */
492 private function get_i18n_percentage( int $number, int $total ) {
493 if ( 0 === $total ) {
494 return false;
495 }
496
497 return number_format_i18n( $number / $total * 100, 2 );
498 }
499 }
500