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