3
3
namespace Give \DonorDashboards \Repositories ;
4
4
5
5
use Give \Framework \Database \DB ;
6
+ use Give \Framework \Support \ValueObjects \Money ;
6
7
use Give \Receipt \DonationReceipt ;
7
8
use Give \Receipt \LineItem ;
8
- use Give \ValueObjects \Money ;
9
9
use Give_Payment ;
10
10
11
11
/**
12
12
* @since 2.10.0
13
13
*/
14
14
class Donations
15
15
{
16
+ /**
17
+ * Array of cached donor donation ids
18
+ *
19
+ * @unreleased
20
+ *
21
+ * @var array<int, int[]>
22
+ */
23
+ private static $ donationIdsCache = [];
24
+
16
25
/**
17
26
* Get donations count for donor
18
27
*
19
28
* @since 2.10.0
20
29
*
21
30
* @param int $donorId
22
31
*
23
- * @return int
32
+ * @return int|null
24
33
*/
25
34
public function getDonationCount ($ donorId )
26
35
{
@@ -36,7 +45,7 @@ public function getDonationCount($donorId)
36
45
*
37
46
* @param int $donorId
38
47
*
39
- * @return string
48
+ * @return string|null
40
49
*/
41
50
public function getRevenue ($ donorId )
42
51
{
@@ -45,7 +54,7 @@ public function getRevenue($donorId)
45
54
46
55
return $ aggregate ?
47
56
$ this ->getAmountWithSeparators (
48
- Money:: ofMinor ($ aggregate ->result , $ currencyCode )-> getAmount (),
57
+ ( new Money ($ aggregate ->result , $ currencyCode ))-> formatToDecimal (),
49
58
$ currencyCode
50
59
) :
51
60
null ;
@@ -58,7 +67,7 @@ public function getRevenue($donorId)
58
67
*
59
68
* @param int $donorId
60
69
*
61
- * @return string
70
+ * @return string|null
62
71
*/
63
72
public function getAverageRevenue ($ donorId )
64
73
{
@@ -67,40 +76,113 @@ public function getAverageRevenue($donorId)
67
76
68
77
return $ aggregate ?
69
78
$ this ->getAmountWithSeparators (
70
- Money:: ofMinor ($ aggregate ->result , $ currencyCode )-> getAmount (),
79
+ ( new Money ($ aggregate ->result , $ currencyCode ))-> formatToDecimal (),
71
80
$ currencyCode
72
81
) :
73
82
null ;
74
83
}
75
84
76
85
/**
77
- * Generates a donation aggregate for a given donor
86
+ * Get formatted donations summary for a donor.
87
+ *
88
+ * This summary includes count, total revenue, and average donation amount.
89
+ * The revenue and average are formatted as decimal strings.
90
+ *
91
+ * @unreleased
92
+ *
93
+ * @param int $donorId
94
+ *
95
+ * @return array{count: int|null, revenue: string|null, average: string|null}
96
+ */
97
+ public function getFormattedDonationsSummary ($ donorId )
98
+ {
99
+ $ summary = $ this ->getDonationsSummary ($ donorId );
100
+ $ currencyCode = give_get_option ('currency ' );
101
+
102
+ return [
103
+ 'count ' => ($ summary ->count ?? null ),
104
+ 'revenue ' => $ summary ->revenue ? $ this ->getAmountWithSeparators (
105
+ (new Money ($ summary ->revenue , $ currencyCode ))->formatToDecimal (),
106
+ $ currencyCode
107
+ ) : null ,
108
+ 'average ' => $ summary ->average ? $ this ->getAmountWithSeparators (
109
+ (new Money ($ summary ->average , $ currencyCode ))->formatToDecimal (),
110
+ $ currencyCode
111
+ ) : null ,
112
+ ];
113
+ }
114
+
115
+ /**
116
+ * Get donations summary for a donor
117
+ *
118
+ * @unreleased
119
+ *
120
+ * @param int $donorId
121
+ *
122
+ * @return object{count: int|null, revenue: string|null, average: string|null}
123
+ */
124
+ private function getDonationsSummary ($ donorId )
125
+ {
126
+ $ summary = $ this ->queryDonationRevenueData (
127
+ $ donorId ,
128
+ 'COUNT(revenue.id) AS count, SUM(revenue.amount) AS revenue, AVG(revenue.amount) AS average '
129
+ );
130
+
131
+ return $ summary ?: (object )[
132
+ 'count ' => null ,
133
+ 'revenue ' => null ,
134
+ 'average ' => null ,
135
+ ];
136
+ }
137
+
138
+ /**
139
+ * @unreleased
78
140
*
79
- * @param string $rawAggregate raw SELECT to determine what to aggregate over
80
141
* @param int $donorId
142
+ * @param string $selectClause
81
143
*
82
- * @return object
144
+ * @return object|null
83
145
*/
84
- private function getDonationAggregate ( $ rawAggregate , $ donorId )
146
+ private function queryDonationRevenueData ( $ donorId , $ selectClause )
85
147
{
148
+ $ donationIds = $ this ->getDonationIDs ($ donorId );
149
+
150
+ if (empty ($ donationIds )) {
151
+ return null ;
152
+ }
153
+
86
154
global $ wpdb ;
87
155
156
+ $ formattedIds = implode (', ' , array_fill (0 , count ($ donationIds ), '%d ' ));
157
+ $ revenueStatuses = "'publish', 'give_subscription', 'pending' " ;
158
+
88
159
return DB ::get_row (
89
- DB :: prepare (
160
+ $ wpdb -> prepare (
90
161
"
91
- SELECT {$ rawAggregate } as result
92
- FROM {$ wpdb ->give_revenue } as revenue
93
- INNER JOIN {$ wpdb ->posts } as posts ON revenue.donation_id = posts.ID
94
- INNER JOIN {$ wpdb ->prefix }give_donationmeta as donationmeta ON revenue.donation_id = donationmeta.donation_id
95
- WHERE donationmeta.meta_key = '_give_payment_donor_id'
96
- AND donationmeta.meta_value = %d
97
- AND posts.post_status IN ( 'publish', 'give_subscription', 'pending' )
98
- " ,
99
- $ donorId
162
+ SELECT {$ selectClause }
163
+ FROM {$ wpdb ->give_revenue } as revenue
164
+ INNER JOIN {$ wpdb ->posts } as posts ON revenue.donation_id = posts.ID
165
+ WHERE posts.ID IN ( $ formattedIds )
166
+ AND posts.post_status IN ( {$ revenueStatuses } )
167
+ " ,
168
+ $ donationIds
100
169
)
101
170
);
102
171
}
103
172
173
+ /**
174
+ * Generates a donation aggregate for a given donor
175
+ *
176
+ * @param string $rawAggregate raw SELECT to determine what to aggregate over
177
+ * @param int $donorId
178
+ *
179
+ * @return object
180
+ */
181
+ private function getDonationAggregate ($ rawAggregate , $ donorId )
182
+ {
183
+ return $ this ->queryDonationRevenueData ($ donorId , "$ rawAggregate as result " );
184
+ }
185
+
104
186
/**
105
187
* Get all donation ids by donor ID
106
188
*
@@ -112,25 +194,33 @@ private function getDonationAggregate($rawAggregate, $donorId)
112
194
*/
113
195
protected function getDonationIDs ($ donorId )
114
196
{
197
+ if (isset (self ::$ donationIdsCache [$ donorId ])) {
198
+ return self ::$ donationIdsCache [$ donorId ];
199
+ }
200
+
115
201
$ statusKeys = give_get_payment_status_keys ();
116
202
$ statusQuery = "' " . implode ("',' " , $ statusKeys ) . "' " ;
117
203
118
204
global $ wpdb ;
119
205
120
- return DB ::get_col (
206
+ $ donationIds = DB ::get_col (
121
207
DB ::prepare (
122
208
"
123
- SELECT revenue.donation_id as id
124
- FROM {$ wpdb ->give_revenue } as revenue
125
- INNER JOIN {$ wpdb ->posts } as posts ON revenue.donation_id = posts.ID
126
- INNER JOIN {$ wpdb ->prefix }give_donationmeta as donationmeta ON revenue.donation_id = donationmeta.donation_id
209
+ SELECT posts.ID as id
210
+ FROM {$ wpdb ->posts } as posts
211
+ INNER JOIN {$ wpdb ->prefix }give_donationmeta as donationmeta ON posts.ID = donationmeta.donation_id
127
212
WHERE donationmeta.meta_key = '_give_payment_donor_id'
128
213
AND donationmeta.meta_value = %d
214
+ AND posts.post_type = 'give_payment'
129
215
AND posts.post_status IN ( {$ statusQuery } )
130
216
" ,
131
217
$ donorId
132
218
)
133
219
);
220
+
221
+ self ::$ donationIdsCache [$ donorId ] = $ donationIds ;
222
+
223
+ return $ donationIds ;
134
224
}
135
225
136
226
/**
@@ -145,7 +235,7 @@ protected function getDonationIDs($donorId)
145
235
*/
146
236
public function getDonations ($ donorId )
147
237
{
148
- $ ids = $ this ->getDonationIds ($ donorId );
238
+ $ ids = $ this ->getDonationIDs ($ donorId );
149
239
150
240
if (empty ($ ids )) {
151
241
return null ;
@@ -252,7 +342,7 @@ protected function getReceiptInfo($payment)
252
342
$ sectionIndex = 0 ;
253
343
foreach ($ receipt as $ section ) {
254
344
// Continue if section does not have line items.
255
- if ( ! $ section ->getLineItems ()) {
345
+ if (! $ section ->getLineItems ()) {
256
346
continue ;
257
347
}
258
348
@@ -273,7 +363,7 @@ protected function getReceiptInfo($payment)
273
363
/* @var LineItem $lineItem */
274
364
foreach ($ section as $ lineItem ) {
275
365
// Continue if line item does not have value.
276
- if ( ! $ lineItem ->value ) {
366
+ if (! $ lineItem ->value ) {
277
367
continue ;
278
368
}
279
369
0 commit comments