PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.3.42
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.3.42
1.4.16 1.4.15 1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 All 179 releases
← All changes | app/Features/UserLimit.php +14 -135 1.4.161.3.42 View file →
@@ -22,23 +22,8 @@
22 22 */
23 23 class UserLimit {
24 24
25 25 /**
26 - * Object cache group for campaign usage counts.
27 - */
28 - public const CACHE_GROUP = 'disco_user_limit';
29 -
30 - /**
31 - * Transient / cache key prefix for campaign usage counts.
32 - */
33 - public const CACHE_KEY_PREFIX = 'disco_campaign_usage_';
34 -
35 - /**
36 - * Default cache lifetime, in seconds, for campaign usage counts.
37 - */
38 - public const CACHE_TTL = 300;
39 -
40 - /**
41 26 * Disco start session function.
42 27 * Set campaign in WC session as an array.
43 28 *
44 29 * @param int $campaign_id Campaign ID.
@@ -67,120 +52,15 @@
67 52 // Save the updated associative array back to the session
68 53 WC()->session->set( 'disco_campaign', $disco_campaign );
69 54 }
70 55
71 - /**
72 - * Whether a campaign has reached its configured global usage limit.
73 - *
74 - * This is the entry point callers should use instead of fetching the count
75 - * directly: when a campaign has no usage limit configured (the common case)
76 - * it returns early and no order/meta query is issued at all.
77 - *
78 - * @param \Disco\App\Utility\Config|object $campaign Campaign config object.
79 - * @return bool
80 - */
81 - public function disco_is_limit_reached( $campaign ) {
82 - if ( ! $campaign instanceof \Disco\App\Utility\Config || empty( $campaign->id ) ) {
83 - return false;
84 - }
85 -
86 - $limit = $campaign->discount_max_user;
87 -
88 - // No limit configured — never query.
89 - if ( empty( $limit ) || ! is_numeric( $limit ) || (int) $limit < 0 ) {
90 - return false;
91 - }
92 -
93 - return $this->disco_get_total_applied_campaign( (int) $campaign->id ) >= (int) $limit;
94 - }
95 -
96 56 /**
97 57 * Retrieve total applied campaign by campaign id.
98 58 *
99 - * The result is memoized per request and cached (object cache + transient)
100 - * because the underlying COUNT() joins the order and order-meta tables,
101 - * which is expensive on large stores. Usage limits do not need real-time
102 - * precision at cart/fragment-refresh frequency; the cache is invalidated
103 - * when an order gains campaign meta or changes status.
104 - *
105 59 * @param int $campaign_id Campaign ID.
106 60 * @return int
107 61 */
108 - public function disco_get_total_applied_campaign( $campaign_id ) {
109 - $campaign_id = (int) $campaign_id;
110 - $cache_key = self::CACHE_KEY_PREFIX . $campaign_id;
111 - $cached = wp_cache_get( $cache_key, self::CACHE_GROUP );
112 -
113 - if ( false === $cached ) {
114 - $cached = get_transient( $cache_key );
115 - }
116 -
117 - if ( is_numeric( $cached ) ) {
118 - return (int) $cached;
119 - }
120 -
121 - $count = $this->query_total_applied_campaign( $campaign_id );
122 -
123 - /**
124 - * Filter the cache lifetime, in seconds, of campaign usage counts.
125 - *
126 - * @param int $ttl Lifetime in seconds.
127 - * @param int $campaign_id Campaign ID.
128 - */
129 - $ttl = (int) apply_filters( 'disco_campaign_usage_cache_ttl', self::CACHE_TTL, $campaign_id );
130 -
131 - if ( $ttl > 0 ) {
132 - wp_cache_set( $cache_key, $count, self::CACHE_GROUP, $ttl );
133 - set_transient( $cache_key, $count, $ttl );
134 - }
135 -
136 - return $count;
137 - }
138 -
139 - /**
140 - * Invalidate the cached usage count for a campaign, or for all campaigns.
141 - *
142 - * @param int|null $campaign_id Campaign ID, or null to flush every campaign.
143 - * @return void
144 - */
145 - public static function flush_cache( $campaign_id = null ) {
146 - if ( null !== $campaign_id ) {
147 - $campaign_id = (int) $campaign_id;
148 - $cache_key = self::CACHE_KEY_PREFIX . $campaign_id;
149 -
150 - wp_cache_delete( $cache_key, self::CACHE_GROUP );
151 - delete_transient( $cache_key );
152 -
153 - return;
154 - }
155 -
156 - if ( ! class_exists( \Disco\App\Campaign::class ) ) {
157 - return;
158 - }
159 -
160 - $campaigns = ( new \Disco\App\Campaign )->get_rows();
161 -
162 - if ( ! is_array( $campaigns ) ) {
163 - return;
164 - }
165 -
166 - foreach ( array_keys( $campaigns ) as $id ) {
167 - self::flush_cache( $id );
168 - }
169 - }
170 -
171 - /**
172 - * Run the usage count query against the order tables.
173 - *
174 - * The order-meta table is joined first (via STRAIGHT_JOIN) so MySQL resolves
175 - * the small `meta_key`/`meta_value` set before touching the orders table.
176 - * Letting the optimizer start from the orders table means scanning every
177 - * order on the store, which is what made this query pathologically slow.
178 - *
179 - * @param int $campaign_id Campaign ID.
180 - * @return int
181 - */
182 - private function query_total_applied_campaign( $campaign_id ) { // phpcs:disable
62 + public function disco_get_total_applied_campaign( $campaign_id ) { // phpcs:disable
183 63 global $wpdb;
184 64
185 65 // Filter the order statuses
186 66 $status = apply_filters(
@@ -197,21 +77,20 @@
197 77
198 78 // Check HPOS first
199 79 if ( $this->disco_is_hpos_enabled() ) {
200 80 $sql = "
201 - SELECT STRAIGHT_JOIN COUNT(DISTINCT om.order_id)
202 - FROM {$wpdb->prefix}wc_orders_meta AS om
203 - INNER JOIN {$wpdb->prefix}wc_orders AS o ON o.id = om.order_id
204 - WHERE om.meta_key = %s
81 + SELECT COUNT(DISTINCT o.id)
82 + FROM {$wpdb->prefix}wc_orders AS o
83 + INNER JOIN {$wpdb->prefix}wc_orders_meta AS om ON o.id = om.order_id
84 + WHERE o.status IN ($placeholders)
85 + AND om.meta_key = %s
205 86 AND om.meta_value = %s
206 - AND o.type = 'shop_order'
207 - AND o.status IN ($placeholders)
208 87 ";
209 88
210 89 $count = $wpdb->get_var(
211 90 $wpdb->prepare(
212 91 $sql,
213 - ...array_merge( array( 'disco_campaign', (string) $campaign_id ), $status )
92 + ...array_merge( $status, array( 'disco_campaign', (string) $campaign_id ) )
214 93 )
215 94 );
216 95
217 96 return (int) $count;
@@ -218,21 +97,21 @@
218 97 }
219 98
220 99 // Legacy query for posts table (non-HPOS only)
221 100 $sql = "
222 - SELECT STRAIGHT_JOIN COUNT(DISTINCT pm.post_id)
223 - FROM {$wpdb->postmeta} AS pm
224 - INNER JOIN {$wpdb->posts} AS p ON p.ID = pm.post_id
225 - WHERE pm.meta_key = %s
101 + SELECT COUNT(p.ID)
102 + FROM {$wpdb->posts} AS p
103 + INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
104 + WHERE p.post_type = 'shop_order'
105 + AND p.post_status IN ($placeholders)
106 + AND pm.meta_key = %s
226 107 AND pm.meta_value = %s
227 - AND p.post_type = 'shop_order'
228 - AND p.post_status IN ($placeholders)
229 108 ";
230 109
231 110 $count = $wpdb->get_var(
232 111 $wpdb->prepare(
233 112 $sql,
234 - ...array_merge( array( 'disco_campaign', (string) $campaign_id ), $status )
113 + ...array_merge( $status, array( 'disco_campaign', (string) $campaign_id ) )
235 114 )
236 115 );
237 116
238 117 return (int) $count;