PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | includes/Services/QuoteNumberService.php +54 -2 2.3.62.4.0 View file →
@@ -38,9 +38,9 @@
38 38 // wp_postmeta yet), and both emit the same quote number.
39 39 $lock_acquired = $this->acquireNumberLock();
40 40 try {
41 41 // Get the next number to use
42 - $next_number = get_option('easy_invoice_next_quote_number', 1);
42 + $next_number = $this->freshCounter();
43 43
44 44 // Find the next unique number
45 45 $unique_number = $this->findNextUniqueNumber($next_number, $prefix);
46 46
@@ -140,9 +140,9 @@
140 140 // entry points kept for backward-compat; both need the lock.
141 141 $lock_acquired = $this->acquireNumberLock();
142 142 try {
143 143 // Get the next number to use from the current settings
144 - $next_number = get_option('easy_invoice_next_quote_number', 1);
144 + $next_number = $this->freshCounter();
145 145
146 146 // Find the next unique number
147 147 $unique_number = $this->findNextUniqueNumber($next_number, $prefix);
148 148
@@ -164,8 +164,60 @@
164 164 * through to the unsynchronised path — the secondary
165 165 * numberExists() check in findNextUniqueNumber() still defends
166 166 * against the worst case).
167 167 */
168 + /**
169 + * Keep a number a form submitted, or hand out a fresh one.
170 + *
171 + * The builder pre-fills its number field with the next number without
172 + * taking it, so two people who opened "New" at the same time both post
173 + * the same number. Under the number lock: a number nobody uses yet is
174 + * kept, and if it is the counter's next value the counter moves past
175 + * it; a number already in use is replaced by a freshly generated one.
176 + *
177 + * @param string $requested The number the form sent, possibly empty.
178 + * @return string The number to save.
179 + */
180 + public function claimOrGenerate(string $requested): string {
181 + $requested = trim($requested);
182 + if ($requested === '') {
183 + return $this->generateUniqueNumber();
184 + }
185 + $lock_acquired = $this->acquireNumberLock();
186 + try {
187 + if ($this->numberExists($requested)) {
188 + return $this->generateUniqueNumber();
189 + }
190 + $prefix = get_option('easy_invoice_quote_prefix', 'QT-');
191 + $next = $this->freshCounter();
192 + if ($requested === $prefix . str_pad((string) $next, 6, '0', STR_PAD_LEFT)) {
193 + update_option('easy_invoice_next_quote_number', $next + 1);
194 + }
195 + return $requested;
196 + } finally {
197 + if ($lock_acquired) {
198 + $this->releaseNumberLock();
199 + }
200 + }
201 + }
202 +
203 + /**
204 + * The counter as the database holds it right now.
205 + *
206 + * Every request loads the options table into memory at boot, before
207 + * it queues for the number lock, so a plain get_option() inside the
208 + * lock returns whatever the counter was when *this* request started —
209 + * and twelve simultaneous saves all "uniquely" took the same number.
210 + * Drop the cached copy and read it again once the lock is held.
211 + *
212 + * @return int
213 + */
214 + private function freshCounter(): int {
215 + wp_cache_delete('easy_invoice_next_quote_number', 'options');
216 + wp_cache_delete('alloptions', 'options');
217 + return (int) get_option('easy_invoice_next_quote_number', 1);
218 + }
219 +
168 220 private function acquireNumberLock(): bool {
169 221 global $wpdb;
170 222 $result = $wpdb->get_var($wpdb->prepare(
171 223 'SELECT GET_LOCK(%s, %d)',