PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / trunk
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / Campaign / CampaignStats.php

CampaignStats.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More trunk, at includes/Campaign/CampaignStats.php

135 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Campaign;
4
5 use Better_Payment\Lite\Controller;
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
11 /**
12 * Computes and caches campaign stats (total raised, donor count, progress %).
13 *
14 * `donor_count` is the count of approved donation *transactions*, not of unique
15 * donors — it must agree with the per-transaction list the Donors Wall renders
16 * (one card per approved row). Counting distinct emails made the summary read
17 * "3 Donors" above a 5-card list; a repeat donor (or a row with an empty email,
18 * which `COUNT(DISTINCT email)` drops entirely) is one transaction here.
19 */
20 class CampaignStats extends Controller {
21
22 private static int $cache_ttl = 2 * MINUTE_IN_SECONDS;
23
24 /**
25 * Get stats for a campaign, using a 2-minute transient cache.
26 */
27 public static function get_stats( int $campaign_id, bool $use_cache = true ): array {
28 $transient_key = 'bpc_stats_' . $campaign_id;
29
30 if ( $use_cache ) {
31 $cached = get_transient( $transient_key );
32 if ( $cached !== false ) {
33 return $cached;
34 }
35 }
36
37 $raw = self::query_stats( $campaign_id );
38
39 $goal = (float) get_post_meta( $campaign_id, '_bpc_goal_amount', true );
40 $raised = $raw['total_raised'];
41
42 $progress = ( $goal > 0 ) ? min( 100.0, round( ( $raised / $goal ) * 100, 1 ) ) : 0.0;
43
44 $end_date = get_post_meta( $campaign_id, '_bpc_end_date', true );
45 $days_remaining = null;
46
47 if ( $end_date ) {
48 $diff = ( strtotime( $end_date ) - current_time( 'timestamp' ) );
49 $days_remaining = max( 0, (int) ceil( $diff / DAY_IN_SECONDS ) );
50 }
51
52 $stats = apply_filters( 'better_payment/campaign/stats', array_merge( $raw, [
53 'goal' => $goal,
54 'progress' => $progress,
55 'days_remaining' => $days_remaining,
56 ] ), $campaign_id );
57
58 set_transient( $transient_key, $stats, self::$cache_ttl );
59
60 return $stats;
61 }
62
63 /**
64 * Bust the stats cache for a campaign — call after a new payment is linked.
65 */
66 public static function bust_cache( int $campaign_id ) {
67 delete_transient( 'bpc_stats_' . $campaign_id );
68 }
69
70 /**
71 * Register WordPress hooks.
72 * Called once from the plugin bootstrap.
73 */
74 public static function register_hooks(): void {
75 // Bust cache when a payment is confirmed (status updated to a successful state).
76 // Fires from Handler.php after each gateway's wpdb->update() call.
77 add_action( 'better_payment/payment_confirmed', [ static::class, 'on_payment_confirmed' ] );
78 }
79
80 /**
81 * Look up the campaign_id on the confirmed payment row and bust its stats cache.
82 *
83 * @param int $payment_id Row ID in wp_better_payment.
84 */
85 public static function on_payment_confirmed( int $payment_id ): void {
86 global $wpdb;
87 $campaign_id = (string) $wpdb->get_var(
88 $wpdb->prepare(
89 "SELECT campaign_id FROM {$wpdb->prefix}better_payment WHERE id = %d LIMIT 1",
90 $payment_id
91 )
92 );
93 if ( $campaign_id !== '' && $campaign_id !== '0' ) {
94 self::bust_cache( (int) $campaign_id );
95 }
96 }
97
98 private static function query_stats( int $campaign_id ): array {
99 global $wpdb;
100
101 $payment_table = $wpdb->prefix . 'better_payment';
102 $approved_statuses = "'" . implode( "','", array_map( 'esc_sql', self::approved_statuses() ) ) . "'";
103
104 $row = $wpdb->get_row(
105 $wpdb->prepare(
106 "SELECT
107 COALESCE(SUM(amount), 0) AS total_raised,
108 COUNT(*) AS donor_count,
109 MAX(payment_date) AS last_donation_date
110 FROM `{$payment_table}`
111 WHERE campaign_id = %s
112 AND status IN ({$approved_statuses})",
113 (string) $campaign_id
114 ),
115 ARRAY_A
116 );
117
118 return [
119 'total_raised' => (float) ( $row['total_raised'] ?? 0 ),
120 'donor_count' => (int) ( $row['donor_count'] ?? 0 ),
121 'last_donation_date' => $row['last_donation_date'] ?? null,
122 ];
123 }
124
125 /**
126 * `success` is the status Paystack verification writes. It was missing, so Paystack
127 * donations never counted toward a campaign's total or donor count — even though the
128 * transactions screen and analytics (Admin\DB) already treat it as paid. Only a
129 * transaction Paystack reported as succeeded is ever stored as `success`.
130 */
131 private static function approved_statuses(): array {
132 return [ 'Completed', 'paid', 'complete', 'succeeded', 'success' ];
133 }
134 }
135