Skip to content

Commit 550cb93

Browse files
committed
refactor: Optimize donor dashboard queries
1 parent ddf88a9 commit 550cb93

File tree

2 files changed

+132
-31
lines changed

2 files changed

+132
-31
lines changed

src/DonorDashboards/Repositories/Donations.php

Lines changed: 127 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,38 @@
33
namespace Give\DonorDashboards\Repositories;
44

55
use Give\Framework\Database\DB;
6+
use Give\Framework\Support\ValueObjects\Money;
67
use Give\Receipt\DonationReceipt;
7-
use Give\Receipt\LineItem;
8-
use Give\ValueObjects\Money;
98
use Give_Payment;
109

1110
/**
1211
* @since 2.10.0
1312
*/
1413
class Donations
1514
{
15+
/**
16+
* Array of cached donor donation ids
17+
*
18+
* @unreleased
19+
*
20+
* @var array<int, int[]>
21+
*/
22+
private static $donationIdsCache = [];
23+
1624
/**
1725
* Get donations count for donor
1826
*
1927
* @since 2.10.0
2028
*
2129
* @param int $donorId
2230
*
23-
* @return int
31+
* @return int|null
2432
*/
2533
public function getDonationCount($donorId)
2634
{
2735
$aggregate = $this->getDonationAggregate('count(revenue.id)', $donorId);
2836

29-
return $aggregate ? $aggregate->result : null;
37+
return $aggregate ? (int)$aggregate->result : null;
3038
}
3139

3240
/**
@@ -36,7 +44,7 @@ public function getDonationCount($donorId)
3644
*
3745
* @param int $donorId
3846
*
39-
* @return string
47+
* @return string|null
4048
*/
4149
public function getRevenue($donorId)
4250
{
@@ -45,7 +53,7 @@ public function getRevenue($donorId)
4553

4654
return $aggregate ?
4755
$this->getAmountWithSeparators(
48-
Money::ofMinor($aggregate->result, $currencyCode)->getAmount(),
56+
(new Money($aggregate->result, $currencyCode))->formatToDecimal(),
4957
$currencyCode
5058
) :
5159
null;
@@ -58,7 +66,7 @@ public function getRevenue($donorId)
5866
*
5967
* @param int $donorId
6068
*
61-
* @return string
69+
* @return string|null
6270
*/
6371
public function getAverageRevenue($donorId)
6472
{
@@ -67,12 +75,76 @@ public function getAverageRevenue($donorId)
6775

6876
return $aggregate ?
6977
$this->getAmountWithSeparators(
70-
Money::ofMinor($aggregate->result, $currencyCode)->getAmount(),
78+
(new Money((int) round((float) $aggregate->result), $currencyCode))->formatToDecimal(),
7179
$currencyCode
7280
) :
7381
null;
7482
}
7583

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+
76148
/**
77149
* Generates a donation aggregate for a given donor
78150
*
@@ -83,20 +155,42 @@ public function getAverageRevenue($donorId)
83155
*/
84156
private function getDonationAggregate($rawAggregate, $donorId)
85157
{
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+
86179
global $wpdb;
87180

181+
$formattedIds = implode(',', array_fill(0, count($donationIds), '%d'));
182+
$revenueStatuses = "'publish', 'give_subscription', 'pending'";
183+
88184
return DB::get_row(
89185
DB::prepare(
90186
"
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
100194
)
101195
);
102196
}
@@ -112,25 +206,33 @@ private function getDonationAggregate($rawAggregate, $donorId)
112206
*/
113207
protected function getDonationIDs($donorId)
114208
{
209+
if (isset(self::$donationIdsCache[$donorId])) {
210+
return self::$donationIdsCache[$donorId];
211+
}
212+
115213
$statusKeys = give_get_payment_status_keys();
116214
$statusQuery = "'" . implode("','", $statusKeys) . "'";
117215

118216
global $wpdb;
119217

120-
return DB::get_col(
218+
$donationIds = DB::get_col(
121219
DB::prepare(
122220
"
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
127224
WHERE donationmeta.meta_key = '_give_payment_donor_id'
128225
AND donationmeta.meta_value = %d
226+
AND posts.post_type = 'give_payment'
129227
AND posts.post_status IN ( {$statusQuery} )
130228
",
131229
$donorId
132230
)
133231
);
232+
233+
self::$donationIdsCache[$donorId] = $donationIds;
234+
235+
return $donationIds;
134236
}
135237

136238
/**
@@ -145,7 +247,7 @@ protected function getDonationIDs($donorId)
145247
*/
146248
public function getDonations($donorId)
147249
{
148-
$ids = $this->getDonationIds($donorId);
250+
$ids = $this->getDonationIDs($donorId);
149251

150252
if (empty($ids)) {
151253
return null;
@@ -252,7 +354,7 @@ protected function getReceiptInfo($payment)
252354
$sectionIndex = 0;
253355
foreach ($receipt as $section) {
254356
// Continue if section does not have line items.
255-
if ( ! $section->getLineItems()) {
357+
if (! $section->getLineItems()) {
256358
continue;
257359
}
258360

@@ -273,7 +375,7 @@ protected function getReceiptInfo($payment)
273375
/* @var LineItem $lineItem */
274376
foreach ($section as $lineItem) {
275377
// Continue if line item does not have value.
276-
if ( ! $lineItem->value) {
378+
if (! $lineItem->value) {
277379
continue;
278380
}
279381

src/DonorDashboards/Tabs/DonationHistoryTab/DonationsRoute.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ protected function getData(DonationsRepository $repository, $donorId)
6060
// If the provided donor ID is valid, attempt to query data
6161
try {
6262
$donations = $repository->getDonations($donorId);
63-
$count = $repository->getDonationCount($donorId);
64-
$revenue = $repository->getRevenue($donorId);
65-
$average = $repository->getAverageRevenue($donorId);
63+
$summary = $repository->getFormattedDonationsSummary($donorId);
64+
6665
$currency = [
6766
'symbol' => give_currency_symbol(give_get_currency(), true),
6867
'position' => give_get_currency_position(),
@@ -75,9 +74,9 @@ protected function getData(DonationsRepository $repository, $donorId)
7574
'body_response' => [
7675
[
7776
'donations' => $donations,
78-
'count' => $count,
79-
'revenue' => $revenue,
80-
'average' => $average,
77+
'count' => $summary['count'],
78+
'revenue' => $summary['revenue'],
79+
'average' => $summary['average'],
8180
'currency' => $currency,
8281
],
8382
],

0 commit comments

Comments
 (0)