PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / inc / analytics / class-merchant-analytics-data-reports.php

class-merchant-analytics-data-reports.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at inc/analytics/class-merchant-analytics-data-reports.php

930 lines 33.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Exit if accessed directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * Class Merchant_Analytics_Data
10 *
11 * This class is responsible for providing data for analytics.
12 */
13 class Merchant_Analytics_Data_Reports {
14 /**
15 * @var Merchant_Analytics_Data_Provider
16 */
17 protected $data_provider;
18
19 /**
20 * Constructor.
21 *
22 * @param Merchant_Analytics_Data_Provider|null $data_provider The data provider instance (optional).
23 */
24 public function __construct( $data_provider = null ) {
25 if ( $data_provider === null ) {
26 $this->data_provider = new Merchant_Analytics_Data_Provider();
27 } else {
28 $this->data_provider = $data_provider;
29 }
30 }
31
32 /**
33 * Get date ranges for the last 7 days and the 7 days before that.
34 *
35 * @return array[] An array containing the last 7 days and the 7 days before that date ranges.
36 */
37 public function get_last_and_previous_7_days_ranges() {
38 // Get the current date and time
39 $now = new DateTime();
40
41 // Calculate the last 7 days range
42 $last_7_days_end = clone $now;
43 $last_7_days_start = clone $now;
44 $last_7_days_start->modify( '-7 days' );
45
46 // Calculate the 7 days before the last 7 days range
47 $previous_7_days_end = clone $last_7_days_start; // End of the previous range is the start of the last 7 days
48 $previous_7_days_start = clone $last_7_days_start;
49 $previous_7_days_start->modify( '-7 days' );
50
51 // Format the dates to match the required format
52 $last_7_days_range = array(
53 'start' => $last_7_days_start->format( 'm/d/y' ),
54 'end' => $last_7_days_end->format( 'm/d/y' ),
55 );
56
57 $previous_7_days_range = array(
58 'start' => $previous_7_days_start->format( 'm/d/y' ),
59 'end' => $previous_7_days_end->format( 'm/d/y' ),
60 );
61
62 // Return the ranges as an array
63 return array(
64 'recent_period' => $last_7_days_range,
65 'last_period' => $previous_7_days_range,
66 );
67 }
68
69 /**
70 * Get the revenue card report for the given date ranges.
71 *
72 * @param array $first_period The first date range.
73 * @param array $second_period The second date range.
74 *
75 * @return array
76 */
77 public function get_reveue_card_report( $first_period, $second_period ) {
78 $this->data_provider->set_start_date( $first_period['start'] );
79 $this->data_provider->set_end_date( $first_period['end'] );
80
81 $revenue_first_period = $this->data_provider->get_revenue();
82
83 $this->data_provider->set_start_date( $second_period['start'] );
84 $this->data_provider->set_end_date( $second_period['end'] );
85
86 $revenue_second_period = $this->data_provider->get_revenue();
87
88 $net_revenue_change = $revenue_second_period - $revenue_first_period;
89 $revenue_change = $this->calculate_percentage_difference( $revenue_second_period, $revenue_first_period );
90
91 return array(
92 'net_revenue_change' => $net_revenue_change,
93 'revenue_change' => $revenue_change,
94 'revenue_first_period' => $revenue_first_period,
95 'revenue_first_period_currency' => wc_price( $revenue_first_period ),
96 'revenue_second_period' => $revenue_second_period,
97 'revenue_second_period_currency' => wc_price( $revenue_second_period ),
98 );
99 }
100
101 /**
102 * Get total new orders card report for the given date ranges.
103 *
104 * @param array $first_period The first date range.
105 * @param array $second_period The second date range.
106 *
107 * @return array
108 */
109 public function get_total_new_orders_card_report( $first_period, $second_period ) {
110 $this->data_provider->set_start_date( $first_period['start'] );
111 $this->data_provider->set_end_date( $first_period['end'] );
112
113 $orders_first_period = (int) $this->data_provider->get_orders_count();
114 $this->data_provider->set_start_date( $second_period['start'] );
115 $this->data_provider->set_end_date( $second_period['end'] );
116
117 $orders_second_period = (int) $this->data_provider->get_orders_count();
118
119 $new_orders_count = $orders_second_period - $orders_first_period;
120
121 $change = $this->calculate_percentage_difference( $orders_second_period, $orders_first_period );
122
123 return array(
124 'orders_change' => $change,
125 'new_orders_count' => $new_orders_count,
126 'orders_first_period' => $orders_first_period,
127 'orders_second_period' => $orders_second_period,
128 );
129 }
130
131 /**
132 * Get average order value (AOV) card report for the given date ranges.
133 *
134 * @param array $first_period The first date range.
135 * @param array $second_period The second date range.
136 *
137 * @return array
138 */
139 public function get_aov_card_report( $first_period, $second_period ) {
140 $this->data_provider->set_start_date( $first_period['start'] );
141 $this->data_provider->set_end_date( $first_period['end'] );
142
143 $aov_first_period = $this->data_provider->get_average_order_value();
144
145 $this->data_provider->set_start_date( $second_period['start'] );
146 $this->data_provider->set_end_date( $second_period['end'] );
147
148 $aov_second_period = $this->data_provider->get_average_order_value();
149
150 $diff = $aov_second_period - $aov_first_period;
151
152 $change = $this->calculate_percentage_difference( $aov_second_period, $aov_first_period );
153
154 return array(
155 'diff' => $diff,
156 'change' => $change,
157 'aov_first_period' => $aov_first_period,
158 'aov_first_period_currency' => wc_price( $aov_first_period ),
159 'aov_second_period' => $aov_second_period,
160 'aov_second_period_currency' => wc_price( $aov_second_period ),
161 );
162 }
163
164 /**
165 * Get conversion rate card report for the given date ranges.
166 *
167 * @param array $first_period The first date range.
168 * @param array $second_period The second date range.
169 *
170 * @return array
171 */
172 public function get_conversion_rate_card_report( $first_period, $second_period ) {
173 $this->data_provider->set_start_date( $first_period['start'] );
174 $this->data_provider->set_end_date( $first_period['end'] );
175
176 $conversion_first_period = $this->data_provider->get_conversion_rate_percentage();
177
178 $this->data_provider->set_start_date( $second_period['start'] );
179 $this->data_provider->set_end_date( $second_period['end'] );
180
181 $conversion_second_period = $this->data_provider->get_conversion_rate_percentage();
182
183 $diff = $conversion_second_period - $conversion_first_period;
184
185 $change = $this->calculate_percentage_difference( $conversion_second_period, $conversion_first_period );
186
187 return array(
188 'diff' => $diff,
189 'change' => $change,
190 'conversion_first_period' => $conversion_first_period,
191 'conversion_first_period_percentage' => wc_format_decimal( $conversion_first_period, 2 ) . '%',
192 'conversion_second_period' => $conversion_second_period,
193 'conversion_second_period_percentage' => wc_format_decimal( $conversion_second_period, 2 ) . '%',
194 );
195 }
196
197 /**
198 * Get impressions card report for the given date ranges.
199 *
200 * @param array $first_period The first date range.
201 * @param array $second_period The second date range.
202 *
203 * @return array
204 */
205 public function get_impressions_card_report( $first_period, $second_period ) {
206 $this->data_provider->set_start_date( $first_period['start'] );
207 $this->data_provider->set_end_date( $first_period['end'] );
208
209 $impressions_first_period = (int) $this->data_provider->get_total_impressions();
210
211 $this->data_provider->set_start_date( $second_period['start'] );
212 $this->data_provider->set_end_date( $second_period['end'] );
213
214 $impressions_second_period = (int) $this->data_provider->get_total_impressions();
215
216 $diff = $impressions_second_period - $impressions_first_period;
217
218 $change = $this->calculate_percentage_difference( $impressions_second_period, $impressions_first_period );
219
220 return array(
221 'diff' => $diff,
222 'change' => $change,
223 'impressions_first_period' => $impressions_first_period,
224 'impressions_second_period' => $impressions_second_period,
225 );
226 }
227
228 /**
229 * Get revenue data for the given date range.
230 *
231 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
232 * @param string $end_date End date in 'Y-m-d H:i:s' format.
233 *
234 * @return array
235 */
236 public function get_revenue_chart_report( $start_date, $end_date ) {
237 $this->data_provider->set_start_date( $start_date );
238 $this->data_provider->set_end_date( $end_date );
239
240 /**
241 * Filter the maximum number of orders to retrieve for revenue chart.
242 *
243 * @param int $limit The maximum number of orders to retrieve.
244 *
245 * @since 2.0.0
246 */
247 $limit = apply_filters( 'merchant_analytics_max_orders_limit_revenue', - 1 );
248
249 $orders = $this->data_provider->get_dated_orders_with_revenue( $limit );
250
251 // Sort orders by timestamp (ascending order)
252 $sorted_orders = $this->sort_orders_by_timestamp( $orders );
253
254 // Determine the best grouping interval based on the date range
255 $interval = $this->determine_grouping_interval( $start_date, $end_date );
256
257 // Group data into the selected interval and calculate total revenue
258 $chart_data = $this->group_data_by_interval( $sorted_orders, $interval, $start_date, $end_date, 'revenue' );
259
260 return $chart_data;
261 }
262
263 /**
264 * Get average order value (AOV) data for the given date range.
265 *
266 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
267 * @param string $end_date End date in 'Y-m-d H:i:s' format.
268 *
269 * @return array
270 */
271 public function get_aov_chart_report( $start_date, $end_date ) {
272 $this->data_provider->set_start_date( $start_date );
273 $this->data_provider->set_end_date( $end_date );
274
275 /**
276 * Filter the maximum number of orders to retrieve for AOV chart.
277 *
278 * @param int $limit The maximum number of orders to retrieve.
279 *
280 * @since 2.0.0
281 */
282 $limit = apply_filters( 'merchant_analytics_max_orders_limit_aov', - 1 );
283
284 $orders = $this->data_provider->get_dated_orders_with_revenue( $limit );
285
286 // Sort orders by timestamp (ascending order)
287 $sorted_orders = $this->sort_orders_by_timestamp( $orders );
288
289 // Determine the best grouping interval based on the date range
290 $interval = $this->determine_grouping_interval( $start_date, $end_date );
291
292 // Group data into the selected interval and calculate AOV
293 $chart_data = $this->group_data_by_interval( $sorted_orders, $interval, $start_date, $end_date, 'aov' );
294
295 return $chart_data;
296 }
297
298 /**
299 * Get impressions data for the given date range.
300 *
301 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
302 * @param string $end_date End date in 'Y-m-d H:i:s' format.
303 *
304 * @return array
305 */
306 public function get_impressions_chart_report( $start_date, $end_date ) {
307 $this->data_provider->set_start_date( $start_date );
308 $this->data_provider->set_end_date( $end_date );
309
310 /**
311 * Filter the maximum number of impressions to retrieve for the impressions chart.
312 *
313 * @param int $limit The maximum number of impressions to retrieve.
314 *
315 * @since 2.0.0
316 */
317 $limit = apply_filters( 'merchant_analytics_max_impressions_limit', - 1 );
318
319 $impressions = $this->data_provider->get_dated_impressions( $limit );
320
321 // Sort impressions by timestamp (ascending order)
322 $sorted_impressions = $this->sort_impressions_by_timestamp( $impressions );
323
324 // Determine the best grouping interval based on the date range
325 $interval = $this->determine_grouping_interval( $start_date, $end_date );
326
327 // Group data into the selected interval and calculate total impressions
328 $chart_data = $this->group_impressions_by_interval( $sorted_impressions, $interval, $start_date, $end_date );
329
330 return $chart_data;
331 }
332
333 /**
334 * Get top performing campaigns for the given date ranges.
335 *
336 * @param array $date_range The date range.
337 *
338 * @return array
339 */
340 public function get_top_performing_campaigns( $date_range ) {
341 $this->data_provider->set_start_date( $date_range['start'] );
342 $this->data_provider->set_end_date( $date_range['end'] );
343 $campaigns = array();
344 /**
345 * Filter the maximum number of top performing campaigns to retrieve.
346 *
347 * @param int $limit The maximum number of top performing campaigns to retrieve.
348 *
349 * @since 2.0.0
350 */
351 $limit = apply_filters( 'merchant_analytics_top_performing_campaigns_limit', 10 );
352 $db_campaigns = $this->data_provider->get_top_performing_campaigns( $limit );
353
354 /**
355 * Filter the top performing campaigns data.
356 *
357 * @param array $db_campaigns The top performing campaigns data.
358 * @param array $date_range The date range.
359 *
360 * @since 2.0.0
361 */
362 $db_campaigns = apply_filters( 'merchant_analytics_top_performing_campaigns', $db_campaigns, $date_range );
363 if ( ! empty( $db_campaigns ) ) {
364 foreach ( $db_campaigns as $campaign ) {
365 $module_id = $campaign['module_id'];
366 $campaign_id = $campaign['campaign_id'];
367 $campaign_info = merchant_get_campaign_data( $campaign_id, $module_id );
368 $impressions = $this->data_provider->get_campaign_impressions( $campaign_id, $module_id );
369 if ( ! empty( $campaign_info ) ) {
370 $modules = $this->get_analytics_modules();
371 if ( array_key_exists( $module_id, $modules ) ) {
372 $module = $modules[ $module_id ];
373 $campaigns[] = array(
374 'module_id' => $module_id,
375 'campaign_id' => $campaign_id,
376 'revenue' => $module['metrics']['revenue'] ? $this->data_provider->get_campaign_revenue( $campaign_id, $module_id ) : '-',
377 'orders' => $module['metrics']['orders_count'] ? $this->data_provider->get_campaign_orders_count( $campaign_id, $module_id ) : '-',
378 'aov' => $module['metrics']['aov'] ? $this->data_provider->get_campaign_average_order_value( $campaign_id, $module_id ) : '-',
379 'ctr' => $module['metrics']['ctr'] ? $this->get_campaign_ctr_change( $campaign_id, $module_id, $date_range ) : '-',
380 'clicks' => $module['metrics']['clicks'] ? $this->data_provider->get_campaign_clicks( $campaign_id, $module_id ) : '-',
381 'impressions' => $module['metrics']['impressions'] ? $impressions : '-',
382 'campaign_info' => $campaign_info,
383 );
384 }
385 }
386 }
387 }
388
389 /**
390 * Filter the top performing campaigns data.
391 *
392 * @param array $campaigns The top performing campaigns data.
393 * @param array $date_range The date range.
394 *
395 * @since 2.0.0
396 */
397 return apply_filters( 'merchant_analytics_top_performing_campaigns_data', $campaigns, $date_range );
398 }
399
400 /**
401 * Get all campaigns for the given date ranges.
402 *
403 * @param $period
404 *
405 * @return array
406 */
407 public function get_all_campaigns( $period ) {
408 $campaigns_data = array();
409 $all_modules = $this->get_analytics_modules();
410
411 if ( empty( $all_modules ) ) {
412 return $campaigns_data;
413 }
414
415 foreach ( $all_modules as $module_id => $module ) {
416 $data = array();
417 $campaigns = $module['data']['campaigns'] ?? array();
418 $this->data_provider->set_start_date( $period['start'] );
419 $this->data_provider->set_end_date( $period['end'] );
420 if ( $module['module_object']->analytics_metrics()['campaigns'] === true ) {
421 foreach ( $campaigns as $campaign_id => $campaign ) {
422 $campaign_url = add_query_arg( array( 'page' => 'merchant', 'module' => $module_id, 'campaign_id' => $campaign_id ), 'admin.php' );
423 $impressions = $this->data_provider->get_campaign_impressions( $campaign_id, $module_id );
424 $revenue = $this->data_provider->get_campaign_revenue( $campaign_id, $module_id );
425 // Prepare each campaign data
426 $data[] = array(
427 'campaign_key' => $campaign['campaign_key'] ?? '',
428 'campaign_id' => $campaign_id,
429 'title' => $campaign['campaign_title'] ?? '',
430 'status' => $campaign['campaign_status'] ?? 'active',
431 'impression' => $module['metrics']['impressions'] ? $impressions : '-',
432 'clicks' => $module['metrics']['clicks'] ? $this->data_provider->get_campaign_clicks( $campaign_id, $module_id ) : '-',
433 'revenue' => $module['metrics']['revenue'] ? wc_price( $revenue ) : '-',
434 'revenue_number' => $module['metrics']['revenue'] ? $revenue : '-',
435 'ctr' => $module['metrics']['ctr'] ? $this->get_campaign_ctr_change( $campaign_id, $module_id, $period ) : '-',
436 'orders' => $module['metrics']['orders_count'] ? $this->data_provider->get_campaign_orders_count( $campaign_id, $module_id ) : '-',
437 'url' => $campaign_url,
438 );
439 }
440 } else {
441 $module_url = add_query_arg( array( 'page' => 'merchant', 'module' => $module_id ), 'admin.php' );
442 $impressions = $this->data_provider->get_module_impressions( $module_id );
443 $revenue = $this->data_provider->get_module_revenue( $module_id );
444 // Prepare the module data
445 $data[] = array(
446 'campaign_key' => '',
447 'campaign_id' => '',
448 'title' => '-',
449 'status' => 'n\a',
450 'impression' => $module['metrics']['impressions'] ? $impressions : '-',
451 'clicks' => $module['metrics']['clicks'] ? $this->data_provider->get_module_clicks( $module_id ) : '-',
452 'revenue' => $module['metrics']['revenue'] ? wc_price( $revenue ) : '-',
453 'revenue_number' => $module['metrics']['revenue'] ? $revenue : '-',
454 'ctr' => $module['metrics']['ctr'] ? $this->get_module_ctr_change( $module_id, $period ) : '-',
455 'orders' => $module['metrics']['orders_count'] ? $this->data_provider->get_module_orders_count( $module_id ) : '-',
456 'url' => $module_url,
457 );
458 }
459
460 // Prepare the campaigns data
461 $campaigns_data[] = array(
462 'module_id' => $module_id,
463 'module_name' => esc_html( $module['data']['name'] ?? '' ),
464 'campaigns' => $data,
465 );
466 }
467
468 /**
469 * Filter all campaigns data for the campaigns report.
470 *
471 * @param array $campaigns_data All campaigns data.
472 * @param array $period The first date range.
473 *
474 * @since 2.0.0
475 */
476 return apply_filters( 'merchant_analytics_all_campaigns_data', $campaigns_data, $period );
477 }
478
479 /**
480 * Get the CTR change for the given campaign and date ranges.
481 *
482 * @param int $campaign_id The campaign ID.
483 * @param int $module_id The module ID.
484 * @param array $period The first date range.
485 *
486 * @return int The CTR change percentage.
487 */
488 public function get_campaign_ctr_change( $campaign_id, $module_id, $period ) {
489 $this->data_provider->set_start_date( $period['start'] );
490 $this->data_provider->set_end_date( $period['end'] );
491
492 return (int) $this->data_provider->get_campaign_ctr_percentage( $campaign_id, $module_id );
493 }
494
495 /**
496 * Prepare the data for the main analytics cards report component.
497 *
498 * @return array The main analytics cards report data.
499 */
500 public function main_analytics_cards_report() {
501 $date_ranges = $this->get_last_and_previous_7_days_ranges();
502 $added_revenue = $this->get_reveue_card_report( $date_ranges['last_period'], $date_ranges['recent_period'] );
503 $added_orders = $this->get_total_new_orders_card_report( $date_ranges['last_period'], $date_ranges['recent_period'] );
504 $aov_rate = $this->get_aov_card_report( $date_ranges['last_period'], $date_ranges['recent_period'] );
505 $conversion_rate = $this->get_conversion_rate_card_report( $date_ranges['last_period'], $date_ranges['recent_period'] );
506 $impressions = $this->get_impressions_card_report( $date_ranges['last_period'], $date_ranges['recent_period'] );
507
508 return array(
509 'section_title' => __( 'Merchant Analytics Dashboard', 'merchant' ), // Raw string
510 'date_ranges' => $date_ranges,
511 'action' => 'merchant_get_analytics_cards_data',
512 'cards' => array(
513 'revenue' => array(
514 'title' => __( 'Added revenue', 'merchant' ), // Raw string
515 'value' => wc_price( $added_revenue['revenue_second_period'] ), // Raw value
516 'change' => array(
517 'value' => wc_format_decimal( $added_revenue['revenue_change'][0], 2 ) . '%', // Raw value
518 'class' => $added_revenue['revenue_change'][1], // Raw value
519 ),
520 'tooltip' => __( 'Revenue added by Merchant.', 'merchant' ), // Raw string
521 ),
522 'total-orders' => array(
523 'title' => __( 'Total orders', 'merchant' ), // Raw string
524 'value' => $added_orders['orders_second_period'], // Raw value
525 'change' => array(
526 'value' => wc_format_decimal( $added_orders['orders_change'][0], 2 ) . '%', // Raw value
527 'class' => $added_orders['orders_change'][1], // Raw value
528 ),
529 'tooltip' => __( 'Total number of orders involving Merchant.', 'merchant' ), // Raw string
530 ),
531 'aov' => array(
532 'title' => __( 'Average order value', 'merchant' ), // Raw string
533 'value' => wc_price( $aov_rate['aov_second_period'] ), // Raw value
534 'change' => array(
535 'value' => wc_format_decimal( $aov_rate['change'][0], 2 ) . '%', // Raw value
536 'class' => $aov_rate['change'][1], // Raw value
537 ),
538 'tooltip' => __( 'Average order value for Merchant orders.', 'merchant' ), // Raw string
539 ),
540 'conversion-rate' => array(
541 'title' => __( 'Conversion rate', 'merchant' ), // Raw string
542 'value' => wc_format_decimal( $conversion_rate['conversion_second_period'], 2 ) . '%', // Raw value
543 'change' => array(
544 'value' => wc_format_decimal( $conversion_rate['change'][0], 2 ) . '%', // Raw value
545 'class' => $conversion_rate['change'][1], // Raw value
546 ),
547 'tooltip' => __( 'The percentage of Merchant offer viewers who made a purchase.', 'merchant' ), // Raw string
548 ),
549 'impressions' => array(
550 'title' => __( 'Impressions', 'merchant' ), // Raw string
551 'value' => $impressions['impressions_second_period'], // Raw value
552 'change' => array(
553 'value' => wc_format_decimal( $impressions['change'][0], 2 ) . '%', // Raw value
554 'class' => $impressions['change'][1], // Raw value
555 ),
556 'tooltip' => __( 'The number of times Merchant offers were seen.', 'merchant' ), // Raw string
557 ),
558 ),
559 );
560 }
561
562 /**
563 * Get the CTR change for the given module and date ranges.
564 *
565 * @param int $module_id The module ID.
566 * @param array $first_period The first date range.
567 * @param array $second_period The second date range.
568 *
569 * @return int The CTR change percentage.
570 */
571 public function get_module_ctr_change( $module_id, $first_period ) {
572 $this->data_provider->set_start_date( $first_period['start'] );
573 $this->data_provider->set_end_date( $first_period['end'] );
574
575 return (int) $this->data_provider->get_module_ctr_percentage( $module_id );
576 }
577
578 /**
579 * Get all analytics modules.
580 *
581 * @return array
582 */
583 protected function get_analytics_modules() {
584 $modules = array();
585
586 /**
587 * Filter all modules data for the campaigns report.
588 *
589 * @param array $all_modules All modules data.
590 *
591 * @since 2.0.0
592 */
593 $all_modules = apply_filters( 'merchant_analytics_all_modules_data_campaigns_table', merchant_get_modules_data() );
594 foreach ( $all_modules as $module_id => $module ) {
595 if ( Merchant_Modules::is_module_active( $module_id ) ) {
596 $module_object = Merchant_Modules::get_module( $module_id );
597 if ( $module_object && $module_object->has_analytics() ) {
598 $modules[ $module_id ] = array(
599 'module_object' => $module_object,
600 'metrics' => $module_object->analytics_metrics(),
601 'data' => $module,
602 );
603 }
604 }
605 }
606
607 return $modules;
608 }
609
610 /**
611 * Sort orders by timestamp in ascending order.
612 *
613 * @param array $orders The orders to sort.
614 *
615 * @return array
616 */
617 protected function sort_orders_by_timestamp( $orders ) {
618 usort( $orders, static function ( $a, $b ) {
619 return strtotime( $a['timestamp'] ) - strtotime( $b['timestamp'] );
620 } );
621
622 return $orders;
623 }
624
625 /**
626 * Sort impressions by timestamp in ascending order.
627 *
628 * @param array $impressions The impressions to sort.
629 *
630 * @return array
631 */
632 protected function sort_impressions_by_timestamp( $impressions ) {
633 usort( $impressions, static function ( $a, $b ) {
634 return strtotime( $a['timestamp'] ) - strtotime( $b['timestamp'] );
635 } );
636
637 return $impressions;
638 }
639
640 /**
641 * Determine the best grouping interval based on the date range.
642 *
643 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
644 * @param string $end_date End date in 'Y-m-d H:i:s' format.
645 *
646 * @return string
647 */
648 protected function determine_grouping_interval( $start_date, $end_date ) {
649 $days_between = $this->calculate_days_between_dates( $start_date, $end_date );
650
651 if ( $days_between > 365 ) {
652 return 'yearly'; // Use yearly grouping for very large date ranges
653 }
654
655 if ( $days_between > 90 ) {
656 return 'monthly'; // Use monthly grouping for large date ranges
657 }
658
659 if ( $days_between > 30 ) {
660 return 'weekly'; // Use weekly grouping (20 days) for medium date ranges
661 }
662
663 return 'daily'; // Use daily grouping for small date ranges
664 }
665
666 /**
667 * Calculate the number of days between two dates.
668 *
669 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
670 * @param string $end_date End date in 'Y-m-d H:i:s' format.
671 *
672 * @return int
673 */
674 protected function calculate_days_between_dates( $start_date, $end_date ) {
675 $start_date_time = new DateTime( $start_date );
676 $end_date_time = new DateTime( $end_date );
677
678 return $start_date_time->diff( $end_date_time )->days;
679 }
680
681 /**
682 * Group data into intervals (daily, weekly, monthly, yearly) and calculate the desired metric.
683 *
684 * @param array $orders The orders to group.
685 * @param string $interval The interval to group by ('daily', 'weekly', 'monthly', 'yearly').
686 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
687 * @param string $end_date End date in 'Y-m-d H:i:s' format.
688 * @param string $metric The metric to calculate ('revenue' or 'aov').
689 *
690 * @return array
691 */
692 protected function group_data_by_interval( $orders, $interval, $start_date, $end_date, $metric = 'revenue' ) {
693 $grouped_data = array();
694 $current_group_start = strtotime( $start_date );
695 $end_timestamp = strtotime( $end_date );
696 $previous_value = null;
697
698 // Loop through each interval
699 while ( $current_group_start <= $end_timestamp ) {
700 // Determine the interval end date
701 $interval_end = $this->calculate_interval_end( $current_group_start, $interval );
702
703 // Find orders within the current interval
704 $current_group = $this->get_orders_in_interval( $orders, $current_group_start, $interval_end );
705
706 // Calculate total revenue and orders count for the group
707 $total_revenue = array_sum( array_column( $current_group, 'revenue' ) );
708 $order_subtotal = array_sum( array_column( $current_group, 'order_subtotal' ) );
709 $orders_count = count( $current_group );
710
711 // Calculate the desired metric
712 if ( $metric === 'aov' ) {
713 $value = $orders_count > 0 ? $order_subtotal / $orders_count : 0; // AOV
714 } else {
715 $value = $total_revenue; // Total revenue
716 }
717
718 if ( $previous_value === null ) {
719 $previous_value = 0;
720 }
721
722 // Calculate percentage difference
723 list( $difference, $diff_type ) = $this->calculate_percentage_difference( $value, $previous_value );
724
725 // Format the x-axis label based on the interval
726 $x_label = $this->format_x_axis_label( $current_group_start, $interval_end, $interval );
727
728 // Add the group to the chart data
729 $grouped_data[] = array(
730 'x' => $x_label,
731 'y' => wc_format_decimal( $value ),
732 'number_currency' => wc_price( $value ),
733 'orders_count' => $orders_count,
734 'diff_type' => $diff_type,
735 'difference' => $difference,
736 'timestamp' => gmdate( 'Y-m-d H:i:s', $current_group_start ), // Start of the interval
737 );
738
739 // Update previous_value for the next group
740 $previous_value = $value;
741
742 // Move to the next interval
743 $current_group_start = $interval_end;
744 }
745
746 return $grouped_data;
747 }
748
749 /**
750 * Group impressions into intervals (daily, weekly, monthly, yearly).
751 *
752 * @param array $impressions The impressions to group.
753 * @param string $interval The interval to group by ('daily', 'weekly', 'monthly', 'yearly').
754 * @param string $start_date Start date in 'Y-m-d H:i:s' format.
755 * @param string $end_date End date in 'Y-m-d H:i:s' format.
756 *
757 * @return array
758 */
759 protected function group_impressions_by_interval( $impressions, $interval, $start_date, $end_date ) {
760 $grouped_data = array();
761 $current_group_start = strtotime( $start_date );
762 $end_timestamp = strtotime( $end_date );
763 $previous_value = null;
764
765 // Loop through each interval
766 while ( $current_group_start <= $end_timestamp ) {
767 // Determine the interval end date
768 $interval_end = $this->calculate_interval_end( $current_group_start, $interval );
769
770 // Find impressions within the current interval
771 $current_group = $this->get_impressions_in_interval( $impressions, $current_group_start, $interval_end );
772
773 // Calculate total impressions for the group
774 $total_impressions = array_sum( array_column( $current_group, 'impressions_count' ) );
775
776 // Calculate percentage difference
777 list( $difference, $diff_type ) = $this->calculate_percentage_difference( $total_impressions, $previous_value );
778
779 // Format the x-axis label based on the interval
780 $x_label = $this->format_x_axis_label( $current_group_start, $interval_end, $interval );
781
782 // Add the group to the chart data
783 $grouped_data[] = array(
784 'x' => $x_label,
785 'y' => $total_impressions,
786 'impressions_count' => count( $current_group ), // Number of impression records
787 'diff_type' => $diff_type,
788 'difference' => $difference,
789 'timestamp' => gmdate( 'Y-m-d H:i:s', $current_group_start ), // Start of the interval
790 );
791
792 // Update previous_value for the next group
793 $previous_value = $total_impressions;
794
795 // Move to the next interval
796 $current_group_start = $interval_end;
797 }
798
799 return $grouped_data;
800 }
801
802 /**
803 * Calculate the end of the interval based on the interval type.
804 *
805 * @param int $current_group_start The start timestamp of the interval.
806 * @param string $interval The interval type ('daily', 'weekly', 'monthly', 'yearly').
807 *
808 * @return int
809 */
810 protected function calculate_interval_end( $current_group_start, $interval ) {
811 if ( $interval === 'daily' ) {
812 return strtotime( '+1 day', $current_group_start );
813 }
814
815 if ( $interval === 'weekly' ) {
816 return strtotime( '+20 days', $current_group_start ); // 20-day "weekly" interval
817 }
818
819 if ( $interval === 'monthly' ) {
820 return strtotime( '+1 month', $current_group_start );
821 }
822
823 if ( $interval === 'yearly' ) {
824 return strtotime( '+1 year', $current_group_start );
825 }
826
827 return $current_group_start; // Default to start timestamp
828 }
829
830 /**
831 * Get orders within the specified interval.
832 *
833 * @param array $orders The orders to filter.
834 * @param int $interval_start The start timestamp of the interval.
835 * @param int $interval_end The end timestamp of the interval.
836 *
837 * @return array
838 */
839 protected function get_orders_in_interval( $orders, $interval_start, $interval_end ) {
840 return array_filter( $orders, function ( $order ) use ( $interval_start, $interval_end ) {
841 $timestamp = strtotime( $order['timestamp'] );
842
843 return $timestamp >= $interval_start && $timestamp < $interval_end;
844 } );
845 }
846
847 /**
848 * Get impressions within the specified interval.
849 *
850 * @param array $impressions The impressions to filter.
851 * @param int $interval_start The start timestamp of the interval.
852 * @param int $interval_end The end timestamp of the interval.
853 *
854 * @return array
855 */
856 protected function get_impressions_in_interval( $impressions, $interval_start, $interval_end ) {
857 return array_filter( $impressions, function ( $impression ) use ( $interval_start, $interval_end ) {
858 $timestamp = strtotime( $impression['timestamp'] );
859
860 return $timestamp >= $interval_start && $timestamp < $interval_end;
861 } );
862 }
863
864 /**
865 * Calculate the percentage difference between the current and previous value.
866 *
867 * @param float $current_value The current value.
868 * @param float|null $previous_value The previous value.
869 *
870 * @return array [difference, diff_type]
871 */
872 protected function calculate_percentage_difference( $current_value, $previous_value ) {
873 // Ensure numeric values
874 $current_value = (float) $current_value;
875 $previous_value = $previous_value === null ? null : (float) $previous_value;
876
877 if ( $previous_value === null ) {
878 return array( 0, 'none' ); // No previous data to compare
879 }
880
881 // Handle zero or near-zero previous value
882 if ( abs( $previous_value ) < 0.0001 ) { // Use a small threshold to catch float precision issues
883 if ( abs( $current_value ) < 0.0001 ) {
884 return array( 0, 'none' ); // Both are effectively zero, no change
885 }
886
887 // If current_value is non-zero and previous_value is zero, treat as infinite change
888 return array( $current_value > 0 ? 100 : - 100, $current_value > 0 ? 'increase' : 'decrease' );
889 }
890
891 // Safe calculation of percentage difference
892 $difference = ( ( $current_value - $previous_value ) / $previous_value ) * 100;
893 $diff_type = ( $difference >= 0 ) ? 'increase' : 'decrease';
894
895 if ( abs( $difference ) < 0.0001 ) { // Handle very small differences as no change
896 $diff_type = 'none';
897 }
898
899 return array( round( abs( $difference ), 2 ), $diff_type ); // Round to 2 decimal places
900 }
901
902 /**
903 * Format the x-axis label based on the interval.
904 *
905 * @param int $current_group_start The start timestamp of the interval.
906 * @param int $interval_end The end timestamp of the interval.
907 * @param string $interval The interval type ('daily', 'weekly', 'monthly', 'yearly').
908 *
909 * @return string
910 */
911 protected function format_x_axis_label( $current_group_start, $interval_end, $interval ) {
912 if ( $interval === 'daily' ) {
913 return gmdate( 'j M', $current_group_start ); // e.g., "1 Jan"
914 }
915
916 if ( $interval === 'weekly' ) {
917 return gmdate( 'j M', $current_group_start ) . ' - ' . gmdate( 'j M', $interval_end - 1 ); // e.g., "1 Jan - 20 Jan"
918 }
919
920 if ( $interval === 'monthly' ) {
921 return gmdate( 'F', $current_group_start ); // e.g., "January"
922 }
923
924 if ( $interval === 'yearly' ) {
925 return gmdate( 'Y', $current_group_start ); // e.g., "2024"
926 }
927
928 return gmdate( 'Y-m-d', $current_group_start ); // Default to full date
929 }
930 }