PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.18.0
GiveWP – Donation Plugin and Fundraising Platform v4.18.0
4.18.0 4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 All 257 releases
← All changes | src/Campaigns/Repositories/CampaignsDataRepository.php +80 -49 4.16.6 → 4.18.0 View file →
@@ -39,9 +39,15 @@
39 39 */
40 40 private $subscriptionDonorsCount = [];
41 41
42 42 /**
43 + * Cached stats for the given campaigns. Campaigns missing from the cache are queried and added
44 + * to it, so a warm cache never hides a campaign that arrived after it was built.
43 45 *
46 + * The cache holds live-mode stats only. The queries filter by the site's payment mode, and the
47 + * options carry no mode, so test mode reads straight from the database and never writes.
48 + *
49 + * @since 4.18.0 Query and cache campaigns that are missing from the cache; read subscriptions from the option they are written to; bypass the cache in test mode.
44 50 * @since 4.8.0 added data caching layer
45 51 *
46 52 * @param int[] $ids
47 53 *
@@ -49,72 +55,97 @@
49 55 */
50 56 public static function campaigns(array $ids): CampaignsDataRepository
51 57 {
52 58 $self = new self();
53 - $campaignsData = get_option('give_campaigns_data', []);
54 - $campaignsSubscriptionData = get_option('give_campaigns_subscription_data', []);
59 + $emptyCache = ['amounts' => [], 'donationsCount' => [], 'donorsCount' => []];
60 + $useCache = ! give_is_test_mode();
61 + $campaignsData = $useCache
62 + ? array_merge($emptyCache, (array)get_option('give_campaigns_data', []))
63 + : $emptyCache;
64 + $campaignsSubscriptionData = $useCache
65 + ? array_merge($emptyCache, (array)get_option('give_campaigns_subscriptions_data', []))
66 + : $emptyCache;
55 67
56 - // remove cached campaign ids
57 - $campaignIds = array_filter($ids, function ($id) use ($campaignsData) {
58 - foreach ($campaignsData as $row) {
59 - foreach($row as $campaign) {
60 - if ($campaign['campaign_id'] == $id) {
61 - return false;
62 - }
63 - }
68 + $uncachedIds = self::missingIds($campaignsData, $ids);
69 +
70 + if ($uncachedIds) {
71 + $donations = CampaignsDataQuery::donations($uncachedIds);
72 +
73 + $campaignsData = [
74 + 'amounts' => array_merge($campaignsData['amounts'], self::withZeroRows($donations->collectIntendedAmounts(), $uncachedIds, 'sum')),
75 + 'donationsCount' => array_merge($campaignsData['donationsCount'], self::withZeroRows($donations->collectDonations(), $uncachedIds, 'count')),
76 + 'donorsCount' => array_merge($campaignsData['donorsCount'], self::withZeroRows($donations->collectDonors(), $uncachedIds, 'count')),
77 + ];
78 +
79 + if ($useCache) {
80 + update_option('give_campaigns_data', $campaignsData);
64 81 }
82 + }
65 83
66 - return true;
67 - });
84 + // The subscriptions cache can lag the donations cache, for example when Recurring is activated later
85 + $uncachedSubscriptionIds = defined('GIVE_RECURRING_VERSION')
86 + ? self::missingIds($campaignsSubscriptionData, $ids)
87 + : [];
68 88
69 - if (
70 - ! empty($campaignsData['donationsCount'])
71 - || ! empty($campaignsSubscriptionData['donationsCount'])
72 - ) {
73 - $self->amounts = $campaignsData['amounts'];
74 - $self->donationsCount = $campaignsData['donationsCount'];
75 - $self->donorsCount = $campaignsData['donorsCount'];
89 + if ($uncachedSubscriptionIds) {
90 + $subscriptions = CampaignsDataQuery::subscriptions($uncachedSubscriptionIds);
76 91
77 - if (defined('GIVE_RECURRING_VERSION')) {
78 - $self->subscriptionAmounts = $campaignsSubscriptionData['amounts'];
79 - $self->subscriptionDonationsCount = $campaignsSubscriptionData['donationsCount'];
80 - $self->subscriptionDonorsCount = $campaignsSubscriptionData['donorsCount'];
92 + $campaignsSubscriptionData = [
93 + 'amounts' => array_merge($campaignsSubscriptionData['amounts'], self::withZeroRows($subscriptions->collectInitialAmounts(), $uncachedSubscriptionIds, 'sum')),
94 + 'donationsCount' => array_merge($campaignsSubscriptionData['donationsCount'], self::withZeroRows($subscriptions->collectDonations(), $uncachedSubscriptionIds, 'count')),
95 + 'donorsCount' => array_merge($campaignsSubscriptionData['donorsCount'], self::withZeroRows($subscriptions->collectDonors(), $uncachedSubscriptionIds, 'count')),
96 + ];
97 +
98 + if ($useCache) {
99 + update_option('give_campaigns_subscriptions_data', $campaignsSubscriptionData);
81 100 }
101 + }
82 102
83 - return $self;
103 + $self->amounts = $campaignsData['amounts'];
104 + $self->donationsCount = $campaignsData['donationsCount'];
105 + $self->donorsCount = $campaignsData['donorsCount'];
106 +
107 + if (defined('GIVE_RECURRING_VERSION')) {
108 + $self->subscriptionAmounts = $campaignsSubscriptionData['amounts'];
109 + $self->subscriptionDonationsCount = $campaignsSubscriptionData['donationsCount'];
110 + $self->subscriptionDonorsCount = $campaignsSubscriptionData['donorsCount'];
84 111 }
85 112
86 - // Fetch data from db
87 - $donations = CampaignsDataQuery::donations($campaignIds);
113 + return $self;
114 + }
88 115
89 - $self->amounts = $donations->collectIntendedAmounts();
90 - $self->donationsCount = $donations->collectDonations();
91 - $self->donorsCount = $donations->collectDonors();
116 + /**
117 + * Ids with no row in the given cache.
118 + *
119 + * @since 4.18.0
120 + */
121 + private static function missingIds(array $cache, array $ids): array
122 + {
123 + $cachedIds = array_map('strval', array_column($cache['donationsCount'], 'campaign_id'));
92 124
93 - // cache campaigns data
94 - update_option('give_campaigns_data', [
95 - 'amounts' => array_merge($campaignsData['amounts'] ?? [], $self->amounts),
96 - 'donationsCount' => array_merge($campaignsData['donationsCount'] ?? [], $self->donationsCount),
97 - 'donorsCount' => array_merge($campaignsData['donorsCount'] ?? [], $self->donorsCount),
98 - ]);
125 + return array_values(array_filter($ids, static function ($id) use ($cachedIds) {
126 + return ! in_array((string)$id, $cachedIds, true);
127 + }));
128 + }
99 129
100 - // Set subscriptions data
101 - if (defined('GIVE_RECURRING_VERSION')) {
102 - $subscriptions = CampaignsDataQuery::subscriptions($campaignIds);
130 + /**
131 + * The aggregate queries return no row for a campaign with no donations. Add a zero row for each
132 + * such campaign so it counts as cached and is not queried again on every page load.
133 + *
134 + * @since 4.18.0
135 + */
136 + private static function withZeroRows($rows, array $ids, string $column): array
137 + {
138 + $rows = is_array($rows) ? $rows : [];
139 + $present = array_map('strval', array_column($rows, 'campaign_id'));
103 140
104 - $self->subscriptionAmounts = $subscriptions->collectInitialAmounts();
105 - $self->subscriptionDonationsCount = $subscriptions->collectDonations();
106 - $self->subscriptionDonorsCount = $subscriptions->collectDonors();
107 -
108 - // cache campaigns subscriptions data
109 - update_option('give_campaigns_subscriptions_data', [
110 - 'amounts' => array_merge($campaignsSubscriptionData['amounts'] ?? [], $self->subscriptionAmounts),
111 - 'donationsCount' => array_merge($campaignsSubscriptionData['donationsCount'] ?? [], $self->subscriptionDonationsCount),
112 - 'donorsCount' => array_merge($campaignsSubscriptionData['donorsCount'] ?? [], $self->subscriptionDonorsCount),
113 - ]);
141 + foreach ($ids as $id) {
142 + if ( ! in_array((string)$id, $present, true)) {
143 + $rows[] = ['campaign_id' => (string)$id, $column => 0];
144 + }
114 145 }
115 146
116 - return $self;
147 + return $rows;
117 148 }
118 149
119 150 /**
120 151 * @since 4.2.0 return type of float