Skip to content

Commit 8c96700

Browse files
committed
refactor: Optimize donor dashboard queries
1 parent ddf88a9 commit 8c96700

File tree

2 files changed

+124
-34
lines changed

2 files changed

+124
-34
lines changed

src/DonorDashboards/Repositories/Donations.php

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,33 @@
33
namespace Give\DonorDashboards\Repositories;
44

55
use Give\Framework\Database\DB;
6+
use Give\Framework\Support\ValueObjects\Money;
67
use Give\Receipt\DonationReceipt;
78
use Give\Receipt\LineItem;
8-
use Give\ValueObjects\Money;
99
use Give_Payment;
1010

1111
/**
1212
* @since 2.10.0
1313
*/
1414
class Donations
1515
{
16+
/**
17+
* Array of cached donor donation ids
18+
*
19+
* @unreleased
20+
*
21+
* @var array<int, int[]>
22+
*/
23+
private static $donationIdsCache = [];
24+
1625
/**
1726
* Get donations count for donor
1827
*
1928
* @since 2.10.0
2029
*
2130
* @param int $donorId
2231
*
23-
* @return int
32+
* @return int|null
2433
*/
2534
public function getDonationCount($donorId)
2635
{
@@ -36,7 +45,7 @@ public function getDonationCount($donorId)
3645
*
3746
* @param int $donorId
3847
*
39-
* @return string
48+
* @return string|null
4049
*/
4150
public function getRevenue($donorId)
4251
{
@@ -45,7 +54,7 @@ public function getRevenue($donorId)
4554

4655
return $aggregate ?
4756
$this->getAmountWithSeparators(
48-
Money::ofMinor($aggregate->result, $currencyCode)->getAmount(),
57+
(new Money($aggregate->result, $currencyCode))->formatToDecimal(),
4958
$currencyCode
5059
) :
5160
null;
@@ -58,7 +67,7 @@ public function getRevenue($donorId)
5867
*
5968
* @param int $donorId
6069
*
61-
* @return string
70+
* @return string|null
6271
*/
6372
public function getAverageRevenue($donorId)
6473
{
@@ -67,40 +76,113 @@ public function getAverageRevenue($donorId)
6776

6877
return $aggregate ?
6978
$this->getAmountWithSeparators(
70-
Money::ofMinor($aggregate->result, $currencyCode)->getAmount(),
79+
(new Money($aggregate->result, $currencyCode))->formatToDecimal(),
7180
$currencyCode
7281
) :
7382
null;
7483
}
7584

7685
/**
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
78140
*
79-
* @param string $rawAggregate raw SELECT to determine what to aggregate over
80141
* @param int $donorId
142+
* @param string $selectClause
81143
*
82-
* @return object
144+
* @return object|null
83145
*/
84-
private function getDonationAggregate($rawAggregate, $donorId)
146+
private function queryDonationRevenueData($donorId, $selectClause)
85147
{
148+
$donationIds = $this->getDonationIDs($donorId);
149+
150+
if (empty($donationIds)) {
151+
return null;
152+
}
153+
86154
global $wpdb;
87155

156+
$formattedIds = implode(',', array_fill(0, count($donationIds), '%d'));
157+
$revenueStatuses = "'publish', 'give_subscription', 'pending'";
158+
88159
return DB::get_row(
89-
DB::prepare(
160+
$wpdb->prepare(
90161
"
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
100169
)
101170
);
102171
}
103172

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+
104186
/**
105187
* Get all donation ids by donor ID
106188
*
@@ -112,25 +194,33 @@ private function getDonationAggregate($rawAggregate, $donorId)
112194
*/
113195
protected function getDonationIDs($donorId)
114196
{
197+
if (isset(self::$donationIdsCache[$donorId])) {
198+
return self::$donationIdsCache[$donorId];
199+
}
200+
115201
$statusKeys = give_get_payment_status_keys();
116202
$statusQuery = "'" . implode("','", $statusKeys) . "'";
117203

118204
global $wpdb;
119205

120-
return DB::get_col(
206+
$donationIds = DB::get_col(
121207
DB::prepare(
122208
"
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
127212
WHERE donationmeta.meta_key = '_give_payment_donor_id'
128213
AND donationmeta.meta_value = %d
214+
AND posts.post_type = 'give_payment'
129215
AND posts.post_status IN ( {$statusQuery} )
130216
",
131217
$donorId
132218
)
133219
);
220+
221+
self::$donationIdsCache[$donorId] = $donationIds;
222+
223+
return $donationIds;
134224
}
135225

136226
/**
@@ -145,7 +235,7 @@ protected function getDonationIDs($donorId)
145235
*/
146236
public function getDonations($donorId)
147237
{
148-
$ids = $this->getDonationIds($donorId);
238+
$ids = $this->getDonationIDs($donorId);
149239

150240
if (empty($ids)) {
151241
return null;
@@ -252,7 +342,7 @@ protected function getReceiptInfo($payment)
252342
$sectionIndex = 0;
253343
foreach ($receipt as $section) {
254344
// Continue if section does not have line items.
255-
if ( ! $section->getLineItems()) {
345+
if (! $section->getLineItems()) {
256346
continue;
257347
}
258348

@@ -273,7 +363,7 @@ protected function getReceiptInfo($payment)
273363
/* @var LineItem $lineItem */
274364
foreach ($section as $lineItem) {
275365
// Continue if line item does not have value.
276-
if ( ! $lineItem->value) {
366+
if (! $lineItem->value) {
277367
continue;
278368
}
279369

src/DonorDashboards/Tabs/DonationHistoryTab/DonationsRoute.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Give\DonorDashboards\Repositories\Donations as DonationsRepository;
66
use Give\DonorDashboards\Tabs\Contracts\Route as RouteAbstract;
7+
use Give\Framework\Support\ValueObjects\Money;
78
use Give\Log\Log;
89
use WP_REST_Request;
910
use WP_REST_Response;
@@ -60,9 +61,8 @@ protected function getData(DonationsRepository $repository, $donorId)
6061
// If the provided donor ID is valid, attempt to query data
6162
try {
6263
$donations = $repository->getDonations($donorId);
63-
$count = $repository->getDonationCount($donorId);
64-
$revenue = $repository->getRevenue($donorId);
65-
$average = $repository->getAverageRevenue($donorId);
64+
$summary = $repository->getFormattedDonationsSummary($donorId);
65+
6666
$currency = [
6767
'symbol' => give_currency_symbol(give_get_currency(), true),
6868
'position' => give_get_currency_position(),
@@ -75,9 +75,9 @@ protected function getData(DonationsRepository $repository, $donorId)
7575
'body_response' => [
7676
[
7777
'donations' => $donations,
78-
'count' => $count,
79-
'revenue' => $revenue,
80-
'average' => $average,
78+
'count' => $summary['count'],
79+
'revenue' => $summary['revenue'],
80+
'average' => $summary['average'],
8181
'currency' => $currency,
8282
],
8383
],

0 commit comments

Comments
 (0)