PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.0
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.0
2.4.1 2.4.0 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 All 68 releases
better-payment / includes / Campaign / CampaignStats.php

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

139 lines 5.1 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\Classes\Helper;
6 use Better_Payment\Lite\Controller;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Computes and caches campaign stats (total raised, donor count, progress %).
14 *
15 * `donor_count` is the count of approved donation *transactions*, not of unique
16 * donors — it must agree with the per-transaction list the Donors Wall renders
17 * (one card per approved row). Counting distinct emails made the summary read
18 * "3 Donors" above a 5-card list; a repeat donor (or a row with an empty email,
19 * which `COUNT(DISTINCT email)` drops entirely) is one transaction here.
20 *
21 * Which rows count, and what they add, is Helper::approved_donation_sql() and
22 * Helper::COUNTABLE_AMOUNT_SQL — the one definition every aggregation shares.
23 *
24 * A confirmed donation that charged nothing — a 100%-off promotion code, a subscription trial —
25 * is a donor too: Handler::confirm_stripe_session() stores it `paid` with its real amount, 0, so
26 * it adds one to `donor_count` and nothing to `total_raised`. Nothing here filters on the amount.
27 * Rows confirmed before 2.4.0 stored such a session as `no_payment_required` at the form's
28 * amount: one that was confirmed counts as a donor and adds 0 (its stored amount was never
29 * charged); one that never was (Stripe reports the same status for a free checkout at creation)
30 * is not counted at all. See Helper::LEGACY_CONFIRMATION_MARKER.
31 */
32 class CampaignStats extends Controller {
33
34 private static int $cache_ttl = 2 * MINUTE_IN_SECONDS;
35
36 /**
37 * Get stats for a campaign, using a 2-minute transient cache.
38 */
39 public static function get_stats( int $campaign_id, bool $use_cache = true ): array {
40 $transient_key = 'bpc_stats_' . $campaign_id;
41
42 if ( $use_cache ) {
43 $cached = get_transient( $transient_key );
44 if ( $cached !== false ) {
45 return $cached;
46 }
47 }
48
49 $raw = self::query_stats( $campaign_id );
50
51 $goal = (float) get_post_meta( $campaign_id, '_bpc_goal_amount', true );
52 $raised = $raw['total_raised'];
53
54 $progress = ( $goal > 0 ) ? min( 100.0, round( ( $raised / $goal ) * 100, 1 ) ) : 0.0;
55
56 $end_date = get_post_meta( $campaign_id, '_bpc_end_date', true );
57 $days_remaining = null;
58
59 if ( $end_date ) {
60 $diff = ( strtotime( $end_date ) - current_time( 'timestamp' ) );
61 $days_remaining = max( 0, (int) ceil( $diff / DAY_IN_SECONDS ) );
62 }
63
64 $stats = apply_filters( 'better_payment/campaign/stats', array_merge( $raw, [
65 'goal' => $goal,
66 'progress' => $progress,
67 'days_remaining' => $days_remaining,
68 ] ), $campaign_id );
69
70 set_transient( $transient_key, $stats, self::$cache_ttl );
71
72 return $stats;
73 }
74
75 /**
76 * Bust the stats cache for a campaign — call after a new payment is linked.
77 */
78 public static function bust_cache( int $campaign_id ) {
79 delete_transient( 'bpc_stats_' . $campaign_id );
80 }
81
82 /**
83 * Register WordPress hooks.
84 * Called once from the plugin bootstrap.
85 */
86 public static function register_hooks(): void {
87 // Bust cache when a payment is confirmed (status updated to a successful state).
88 // Fires from Handler.php after each gateway's wpdb->update() call.
89 add_action( 'better_payment/payment_confirmed', [ static::class, 'on_payment_confirmed' ] );
90 }
91
92 /**
93 * Look up the campaign_id on the confirmed payment row and bust its stats cache.
94 *
95 * @param int $payment_id Row ID in wp_better_payment.
96 */
97 public static function on_payment_confirmed( int $payment_id ): void {
98 global $wpdb;
99 $campaign_id = (string) $wpdb->get_var(
100 $wpdb->prepare(
101 "SELECT campaign_id FROM {$wpdb->prefix}better_payment WHERE id = %d LIMIT 1",
102 $payment_id
103 )
104 );
105 if ( $campaign_id !== '' && $campaign_id !== '0' ) {
106 self::bust_cache( (int) $campaign_id );
107 }
108 }
109
110 private static function query_stats( int $campaign_id ): array {
111 global $wpdb;
112
113 $payment_table = $wpdb->prefix . 'better_payment';
114 $approved = Helper::approved_donation_sql();
115 $amount = Helper::COUNTABLE_AMOUNT_SQL;
116
117 // Both fragments are Helper constants (no request data, no `%`).
118 $row = $wpdb->get_row(
119 $wpdb->prepare(
120 "SELECT
121 COALESCE(SUM({$amount}), 0) AS total_raised,
122 COUNT(*) AS donor_count,
123 MAX(payment_date) AS last_donation_date
124 FROM `{$payment_table}`
125 WHERE campaign_id = %s
126 AND {$approved}",
127 (string) $campaign_id
128 ),
129 ARRAY_A
130 );
131
132 return [
133 'total_raised' => (float) ( $row['total_raised'] ?? 0 ),
134 'donor_count' => (int) ( $row['donor_count'] ?? 0 ),
135 'last_donation_date' => $row['last_donation_date'] ?? null,
136 ];
137 }
138 }
139