Completed
Branch BUG/3560-ticket-taxes (e83204)
by
unknown
03:32 queued 44s
created
core/services/payment_methods/gateways/GatewayDataFormatterInterface.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -13,63 +13,63 @@
 block discarded – undo
13 13
 interface GatewayDataFormatterInterface
14 14
 {
15 15
 
16
-    /**
17
-     * Gets the text to use for a gateway's line item name when this is a partial payment
18
-     *
19
-     * @param \EEI_Payment $payment
20
-     * @return string
21
-     */
22
-    public function formatPartialPaymentLineItemName(\EEI_Payment $payment);
16
+	/**
17
+	 * Gets the text to use for a gateway's line item name when this is a partial payment
18
+	 *
19
+	 * @param \EEI_Payment $payment
20
+	 * @return string
21
+	 */
22
+	public function formatPartialPaymentLineItemName(\EEI_Payment $payment);
23 23
 
24 24
 
25 25
 
26
-    /**
27
-     * Gets the text to use for a gateway's line item description when this is a partial payment
28
-     *
29
-     * @param \EEI_Payment $payment
30
-     * @return string
31
-     */
32
-    public function formatPartialPaymentLineItemDesc(\EEI_Payment $payment);
26
+	/**
27
+	 * Gets the text to use for a gateway's line item description when this is a partial payment
28
+	 *
29
+	 * @param \EEI_Payment $payment
30
+	 * @return string
31
+	 */
32
+	public function formatPartialPaymentLineItemDesc(\EEI_Payment $payment);
33 33
 
34 34
 
35 35
 
36
-    /**
37
-     * Gets the name to use for a line item when sending line items to the gateway
38
-     *
39
-     * @param \EE_Line_Item $line_item
40
-     * @param \EEI_Payment   $payment
41
-     * @return string
42
-     */
43
-    public function formatLineItemName(\EE_Line_Item $line_item, \EEI_Payment $payment);
36
+	/**
37
+	 * Gets the name to use for a line item when sending line items to the gateway
38
+	 *
39
+	 * @param \EE_Line_Item $line_item
40
+	 * @param \EEI_Payment   $payment
41
+	 * @return string
42
+	 */
43
+	public function formatLineItemName(\EE_Line_Item $line_item, \EEI_Payment $payment);
44 44
 
45 45
 
46 46
 
47
-    /**
48
-     * Gets the description to use for a line item when sending line items to the gateway
49
-     *
50
-     * @param \EE_Line_Item $line_item
51
-     * @param \EEI_Payment   $payment
52
-     * @return string
53
-     */
54
-    public function formatLineItemDesc(\EE_Line_Item $line_item, \EEI_Payment $payment);
47
+	/**
48
+	 * Gets the description to use for a line item when sending line items to the gateway
49
+	 *
50
+	 * @param \EE_Line_Item $line_item
51
+	 * @param \EEI_Payment   $payment
52
+	 * @return string
53
+	 */
54
+	public function formatLineItemDesc(\EE_Line_Item $line_item, \EEI_Payment $payment);
55 55
 
56 56
 
57 57
 
58
-    /**
59
-     * Gets the order description that should generally be sent to gateways
60
-     *
61
-     * @param \EEI_Payment $payment
62
-     * @return string
63
-     */
64
-    public function formatOrderDescription(\EEI_Payment $payment);
58
+	/**
59
+	 * Gets the order description that should generally be sent to gateways
60
+	 *
61
+	 * @param \EEI_Payment $payment
62
+	 * @return string
63
+	 */
64
+	public function formatOrderDescription(\EEI_Payment $payment);
65 65
 
66 66
 
67 67
 
68
-    /**
69
-     * Formats the amount so it can generally be sent to gateways
70
-     *
71
-     * @param float $amount
72
-     * @return string
73
-     */
74
-    public function formatCurrency($amount);
68
+	/**
69
+	 * Formats the amount so it can generally be sent to gateways
70
+	 *
71
+	 * @param float $amount
72
+	 * @return string
73
+	 */
74
+	public function formatCurrency($amount);
75 75
 }
Please login to merge, or discard this patch.
core/services/calculators/LineItemCalculator.php 2 patches
Indentation   +789 added lines, -789 removed lines patch added patch discarded remove patch
@@ -22,793 +22,793 @@
 block discarded – undo
22 22
 class LineItemCalculator
23 23
 {
24 24
 
25
-    /**
26
-     * @var bool
27
-     */
28
-    private $debug = false;  // true  false
29
-
30
-
31
-    /**
32
-     * number of decimal places to round numbers to when performing calculations
33
-     *
34
-     * @var integer
35
-     */
36
-    protected $decimal_precision = 6;
37
-
38
-    /**
39
-     * number of decimal places to round numbers to for display
40
-     *
41
-     * @var integer
42
-     */
43
-    protected $locale_precision = 6;
44
-
45
-
46
-    /**
47
-     * @param EE_Currency_Config $currency_config
48
-     */
49
-    public function __construct(EE_Currency_Config $currency_config)
50
-    {
51
-        $this->locale_precision = $currency_config->dec_plc;
52
-    }
53
-
54
-
55
-    /**
56
-     * Gets the final total on this item, taking taxes into account.
57
-     * Has the side-effect of setting the sub-total as it was just calculated.
58
-     * If this is used on a grand-total line item, also updates the transaction's
59
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
60
-     * want to change a persistable transaction with info from a non-persistent line item)
61
-     *
62
-     * @param EE_Line_Item $line_item
63
-     * @param bool         $update_txn_status
64
-     * @return float
65
-     * @throws EE_Error
66
-     * @throws ReflectionException
67
-     */
68
-    public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
69
-    {
70
-        $this->debug(null, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
71
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
72
-        [$total, $pretax] = $this->recalculateLineItemTotals($line_item);
73
-        $total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
74
-        // no negative totals plz
75
-        $grand_total  = max($pretax + $total_tax, 0);
76
-        $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
77
-        $grand_total  = $this->updateTotal($line_item, $grand_total, true);
78
-        $this->debug($line_item, $pretax, '$pretax total', __FILE__, __LINE__, 2);
79
-        $this->debug($line_item, $total_tax, '$total_tax', __FILE__, __LINE__, 2);
80
-        $this->debug($line_item, $grand_total, 'grand $total', __FILE__, __LINE__, 2);
81
-        $this->updateTransaction($line_item, $grand_total, $update_txn_status);
82
-        return $grand_total;
83
-    }
84
-
85
-
86
-    /**
87
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
88
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
89
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
90
-     * when this is called on the grand total
91
-     *
92
-     * @param EE_Line_Item $line_item
93
-     * @param float        $total
94
-     * @param float        $pretax
95
-     * @return array
96
-     * @throws EE_Error
97
-     * @throws ReflectionException
98
-     */
99
-    public function recalculateLineItemTotals(
100
-        EE_Line_Item $line_item,
101
-        float $total = 0,
102
-        float $pretax = 0
103
-    ): array {
104
-        $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 1);
105
-        $new_total = $new_pretax = 0;
106
-        switch ($line_item->type()) {
107
-            case EEM_Line_Item::type_total:
108
-            case EEM_Line_Item::type_sub_total:
109
-                [$new_total, $new_pretax] = $this->recalculateSubTotal($line_item, $total, $pretax);
110
-                break;
111
-
112
-            case EEM_Line_Item::type_line_item:
113
-                [$new_total, $new_pretax] = $this->recalculateLineItem($line_item, $total, $pretax);
114
-                break;
115
-
116
-            case EEM_Line_Item::type_sub_line_item:
117
-                // sub line items operate on the total and update both the total AND the pre-tax total
118
-                $new_total = $new_pretax = $this->recalculateSubLineItem($line_item, $total);
119
-                break;
120
-
121
-            case EEM_Line_Item::type_sub_tax:
122
-                // sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
123
-                $new_total = $this->recalculateSubTax($line_item, $pretax);
124
-                break;
125
-
126
-            case EEM_Line_Item::type_tax_sub_total:
127
-            case EEM_Line_Item::type_tax:
128
-            case EEM_Line_Item::type_cancellation:
129
-                // completely ignore tax totals, tax sub-totals, and cancelled line items
130
-                // when calculating the pre-tax-total
131
-                break;
132
-        }
133
-        $this->debug($line_item, $new_pretax, 'PRETAX', __FILE__, __LINE__, 4);
134
-        $this->debug($line_item, $new_total, '+++ TAX', __FILE__, __LINE__, 4);
135
-        return [$total + $new_total, $pretax + $new_pretax];
136
-    }
137
-
138
-
139
-    /**
140
-     * @param EE_Line_Item $line_item
141
-     * @param float        $total
142
-     * @param float        $pretax
143
-     * @return array
144
-     * @throws EE_Error
145
-     * @throws ReflectionException
146
-     */
147
-    private function recalculateSubTotal(
148
-        EE_Line_Item $line_item,
149
-        float $total = 0,
150
-        float $pretax = 0
151
-    ): array {
152
-        $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
153
-        if ($line_item->is_total()) {
154
-            // if this is the grand total line item
155
-            // then first update ALL of the line item quantities (if need be)
156
-            $this->updateLineItemQuantities($line_item);
157
-        }
158
-        // recursively loop through children and recalculate their totals
159
-        $children = $line_item->children();
160
-        foreach ($children as $child_line_item) {
161
-            [$total, $pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
162
-        }
163
-        // for the actual pre-tax sub total line item, we want to save the pretax value for everything
164
-        if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
165
-            $this->updateUnitPrice($line_item, $pretax);
166
-            $this->updateTotal($line_item, $pretax, true);
167
-        } else {
168
-            $this->updateUnitPrice($line_item, $total);
169
-            $total = $this->updateTotal($line_item, $total, true);
170
-        }
171
-        $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
172
-        return [$total, $pretax];
173
-    }
174
-
175
-
176
-    /**
177
-     * @param EE_Line_Item $line_item
178
-     * @param float        $total
179
-     * @param float        $pretax
180
-     * @return array
181
-     * @throws EE_Error
182
-     * @throws ReflectionException
183
-     */
184
-    private function recalculateLineItem(
185
-        EE_Line_Item $line_item,
186
-        float $total = 0,
187
-        float $pretax = 0
188
-    ): array {
189
-        $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
190
-        if ($line_item->is_percent()) {
191
-            $total = $this->calculatePercentage($total, $line_item->percent());
192
-        } else {
193
-            // recursively loop through children and recalculate their totals
194
-            $children = $line_item->children();
195
-            foreach ($children as $child_line_item) {
196
-                [$total, $pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
197
-            }
198
-        }
199
-        $total  = $this->updateTotal($line_item, $total, true);
200
-        $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
201
-        return [$total, $pretax];
202
-    }
203
-
204
-
205
-    /**
206
-     * @param EE_Line_Item $line_item
207
-     * @param float|int    $total
208
-     * @return float
209
-     * @throws EE_Error
210
-     * @throws ReflectionException
211
-     */
212
-    private function recalculateSubLineItem(EE_Line_Item $line_item, float $total = 0): float
213
-    {
214
-        $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
215
-        $total = $line_item->is_percent()
216
-            ? $this->calculatePercentage($total, $line_item->percent())
217
-            : $this->calculateTotal($line_item);
218
-        $this->debug($line_item, $total, '$total', __FILE__, __LINE__, 4);
219
-        return $this->updateTotal($line_item, $total);
220
-    }
221
-
222
-
223
-    /**
224
-     * @param EE_Line_Item $line_item
225
-     * @param float|int    $total
226
-     * @return float
227
-     * @throws EE_Error
228
-     * @throws ReflectionException
229
-     */
230
-    private function recalculateSubTax(EE_Line_Item $line_item, float $total = 0): float
231
-    {
232
-        $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
233
-        $total = $this->calculatePercentage($total, $line_item->percent());
234
-        return $this->updateTotal($line_item, $total);
235
-    }
236
-
237
-
238
-    /**
239
-     * recursively loops through the entire line item tree updating line item quantities accordingly.
240
-     * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
241
-     *
242
-     * @param EE_Line_Item $line_item
243
-     * @param int          $quantity
244
-     * @return int
245
-     * @throws EE_Error
246
-     * @throws ReflectionException
247
-     */
248
-    private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
249
-    {
250
-        $count = 0;
251
-        switch ($line_item->type()) {
252
-            case EEM_Line_Item::type_total:
253
-            case EEM_Line_Item::type_sub_total:
254
-            case EEM_Line_Item::type_tax_sub_total:
255
-                // first, loop through children and set their quantities
256
-                $children = $line_item->children();
257
-                foreach ($children as $child_line_item) {
258
-                    $count += $this->updateLineItemQuantities($child_line_item);
259
-                }
260
-                // $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
261
-                // totals and subtotals should have a quantity of 1
262
-                // unless their children have all been removed, in which case we can set them to 0
263
-                $quantity = $count > 0 ? 1 : 0;
264
-                $this->updateQuantity($line_item, $quantity);
265
-                return $quantity;
266
-
267
-            case EEM_Line_Item::type_line_item:
268
-                // line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
269
-                // but if this is a percentage based line item, then ensure its quantity is 1
270
-                if ($line_item->is_percent()) {
271
-                    // $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
272
-                    $this->updateQuantity($line_item, 1);
273
-                }
274
-                // and we also need to loop through all of the sub items and ensure those quantities match this parent.
275
-                $children = $line_item->children();
276
-                foreach ($children as $child_line_item) {
277
-                    $count += $this->updateLineItemQuantities($child_line_item, $line_item->quantity());
278
-                }
279
-                return $count;
280
-
281
-            case EEM_Line_Item::type_sub_line_item:
282
-                // percentage based items need their quantity set to 1,
283
-                // all others use the incoming value from the parent line item
284
-                $quantity = $line_item->is_percent() ? 1 : $quantity;
285
-                // $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
286
-                $this->updateQuantity($line_item, $quantity);
287
-                return $quantity;
288
-
289
-            case EEM_Line_Item::type_tax:
290
-            case EEM_Line_Item::type_sub_tax:
291
-                // $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
292
-                // taxes should have a quantity of 1
293
-                $this->updateQuantity($line_item, 1);
294
-                return $quantity;
295
-
296
-            case EEM_Line_Item::type_cancellation:
297
-                // cancellations will be ignored for all calculations
298
-                // so assume that they are already set correctly, not that it matters
299
-                break;
300
-        }
301
-        return 0;
302
-    }
303
-
304
-
305
-    /**
306
-     * @param float $total
307
-     * @param float $percent
308
-     * @param bool  $round
309
-     * @return float
310
-     */
311
-    private function calculatePercentage(float $total, float $percent, bool $round = false): float
312
-    {
313
-        $amount = $total * $percent / 100;
314
-        return $this->roundNumericValue($amount, $round);
315
-    }
316
-
317
-
318
-    /**
319
-     * @param EE_Line_Item $line_item
320
-     * @return float
321
-     * @throws EE_Error
322
-     * @throws ReflectionException
323
-     */
324
-    private function calculateTotal(EE_Line_Item $line_item): float
325
-    {
326
-        $total = $line_item->unit_price() * $line_item->quantity();
327
-        return $this->roundNumericValue($total);
328
-    }
329
-
330
-
331
-    /**
332
-     * @param EE_Line_Item $line_item
333
-     * @param float        $percent
334
-     * @throws EE_Error
335
-     * @throws ReflectionException
336
-     */
337
-    private function updatePercent(EE_Line_Item $line_item, float $percent)
338
-    {
339
-        // update and save new percent only if incoming value does not match existing value
340
-        if ($line_item->percent() !== $percent) {
341
-            $this->debug($line_item, $percent, 'SET LINE ITEM %: ', __FILE__, __LINE__, 3);
342
-            $line_item->set_percent($percent);
343
-            $line_item->maybe_save();
344
-        }
345
-    }
346
-
347
-
348
-    /**
349
-     * @param EE_Line_Item $line_item
350
-     * @param int          $quantity
351
-     * @throws EE_Error
352
-     * @throws ReflectionException
353
-     */
354
-    private function updateQuantity(EE_Line_Item $line_item, int $quantity)
355
-    {
356
-        // update and save new quantity only if incoming value does not match existing value
357
-        if ($line_item->quantity() !== $quantity) {
358
-            $this->debug($line_item, $quantity, 'SET LINE ITEM QTY: ', __FILE__, __LINE__, 3);
359
-            $line_item->set_quantity($quantity);
360
-            $line_item->maybe_save();
361
-        }
362
-    }
363
-
364
-
365
-    /**
366
-     * @param EE_Line_Item $line_item
367
-     * @param float        $pretax_total
368
-     * @param bool         $round
369
-     * @return float
370
-     * @throws EE_Error
371
-     * @throws ReflectionException
372
-     */
373
-    private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
374
-    {
375
-        $pretax_total = $this->roundNumericValue($pretax_total, $round);
376
-        // update and save new total only if incoming value does not match existing value
377
-        if ($line_item->preTaxTotal() !== $pretax_total) {
378
-            $this->debug($line_item, $pretax_total, 'SET LINE ITEM PRETAX TOTAL: ', __FILE__, __LINE__, 3);
379
-            $line_item->setPreTaxTotal($pretax_total);
380
-            $line_item->maybe_save();
381
-        }
382
-        return $pretax_total;
383
-    }
384
-
385
-
386
-    /**
387
-     * @param EE_Line_Item $line_item
388
-     * @param float        $total
389
-     * @param bool         $round
390
-     * @return float
391
-     * @throws EE_Error
392
-     * @throws ReflectionException
393
-     */
394
-    private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
395
-    {
396
-        $total = $this->roundNumericValue($total, $round);
397
-        // update and save new total only if incoming value does not match existing value
398
-        if ($line_item->total() !== $total) {
399
-            $this->debug($line_item, $total, 'SET LINE ITEM TOTAL: ', __FILE__, __LINE__, 3);
400
-            $line_item->set_total($total);
401
-            $line_item->maybe_save();
402
-        }
403
-        return $total;
404
-    }
405
-
406
-
407
-    /**
408
-     * @param EE_Line_Item $line_item
409
-     * @param float        $total
410
-     * @param bool         $update_status
411
-     * @return void
412
-     * @throws EE_Error
413
-     * @throws ReflectionException
414
-     */
415
-    private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
416
-    {
417
-        // only update the related transaction's total
418
-        // if we intend to save this line item and its a grand total
419
-        if ($line_item->allow_persist()) {
420
-            $transaction = $line_item->transaction();
421
-            if ($transaction instanceof EE_Transaction) {
422
-                $transaction->set_total($total);
423
-                if ($update_status) {
424
-                    // don't save the TXN because that will be done below
425
-                    // and the following method only saves if the status changes
426
-                    $transaction->update_status_based_on_total_paid(false);
427
-                }
428
-                if ($transaction->ID()) {
429
-                    $transaction->save();
430
-                }
431
-            }
432
-        }
433
-    }
434
-
435
-
436
-    /**
437
-     * @param EE_Line_Item $line_item
438
-     * @param float        $total
439
-     * @return void
440
-     * @throws EE_Error
441
-     * @throws ReflectionException
442
-     */
443
-    private function updateUnitPrice(EE_Line_Item $line_item, float $total)
444
-    {
445
-        $quantity = $line_item->quantity();
446
-        // don't divide by zero else you'll create a singularity and implode the interweb
447
-        $new_unit_price = $quantity !== 0 ? $total / $quantity : 0;
448
-        $new_unit_price = $this->roundNumericValue($new_unit_price);
449
-        // update and save new total only if incoming value does not match existing value
450
-        if ($line_item->unit_price() !== $new_unit_price) {
451
-            $this->debug($line_item, $new_unit_price, 'SET LINE ITEM UNIT PRICE: ', __FILE__, __LINE__, 3);
452
-            $line_item->set_unit_price($new_unit_price);
453
-            $line_item->maybe_save();
454
-        }
455
-    }
456
-
457
-
458
-    /**
459
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
460
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
461
-     * and tax sub-total if already in the DB
462
-     *
463
-     * @param EE_Line_Item $total_line_item
464
-     * @return float
465
-     * @throws EE_Error
466
-     * @throws ReflectionException
467
-     */
468
-    public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
469
-    {
470
-        $this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
471
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
472
-        // calculate the total taxable amount for globally applied taxes
473
-        $taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
474
-        $this->debug($total_line_item, "$taxable_total", '$taxable_total', __FILE__, __LINE__);
475
-        $tax_total     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
476
-        $this->debug($total_line_item, "$tax_total", '$tax_total', __FILE__, __LINE__);
477
-        $non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
478
-        $tax_total        = $this->applyNonGlobalTaxes($total_line_item, $tax_total, $non_global_taxes);
479
-        $this->debug($total_line_item, "$tax_total", 'FINAL $tax_total', __FILE__, __LINE__);
480
-        $this->recalculateTaxSubTotal($total_line_item);
481
-        return $tax_total;
482
-    }
483
-
484
-
485
-    /**
486
-     * @param EE_Line_Item $total_line_item
487
-     * @param float        $taxable_total
488
-     * @return float
489
-     * @throws EE_Error
490
-     * @throws ReflectionException
491
-     */
492
-    private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
493
-    {
494
-        $this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
495
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
496
-        $total_tax = 0;
497
-        $this->debug($total_line_item, $taxable_total, '$taxable_total', __FILE__, __LINE__);
498
-        // loop through all global taxes all taxes
499
-        $taxes = $total_line_item->tax_descendants();
500
-        foreach ($taxes as $tax) {
501
-            $this->debug($tax, $tax->percent(), 'GLOBAL', __FILE__, __LINE__);
502
-            $tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
503
-
504
-            $this->debug(null, $tax_total, '$tax_total', __FILE__, __LINE__);
505
-            $tax_total = $this->updateTotal($tax, $tax_total, true);
506
-            $total_tax += $tax_total;
507
-        }
508
-        return $this->roundNumericValue($total_tax, true);
509
-    }
510
-
511
-
512
-    /**
513
-     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
514
-     *
515
-     * @param EE_Line_Item $line_item
516
-     * @return void
517
-     * @throws EE_Error
518
-     * @throws ReflectionException
519
-     */
520
-    private function recalculateTaxSubTotal(EE_Line_Item $line_item)
521
-    {
522
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
523
-        foreach ($line_item->children() as $maybe_tax_subtotal) {
524
-            if (
525
-                $this->validateLineItemAndType($maybe_tax_subtotal)
526
-                && $maybe_tax_subtotal->is_tax_sub_total()
527
-            ) {
528
-                $total         = 0;
529
-                $total_percent = 0;
530
-                // simply loop through all its children (which should be taxes) and sum their total
531
-                foreach ($maybe_tax_subtotal->children() as $child_tax) {
532
-                    $this->debug($child_tax, $child_tax->total(), '$child_tax->total()', __FILE__, __LINE__);
533
-                    $this->debug($child_tax, $child_tax->percent(), '$child_tax->percent()', __FILE__, __LINE__);
534
-                    if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
535
-                        $total         += $child_tax->total();
536
-                        $total_percent += $child_tax->percent();
537
-                    }
538
-                }
539
-                $this->updateTotal($maybe_tax_subtotal, $total, true);
540
-                $this->updatePercent($maybe_tax_subtotal, $total_percent);
541
-                $this->debug(
542
-                    $line_item,
543
-                    __FUNCTION__,
544
-                    EEH_Debug_Tools::shortClassName(__CLASS__),
545
-                    __FILE__,
546
-                    __LINE__,
547
-                    1
548
-                );
549
-                $this->debug($maybe_tax_subtotal, $total, 'TOTAL TAX', __FILE__, __LINE__);
550
-                $this->debug($maybe_tax_subtotal, $total_percent, 'TOTAL TAX %', __FILE__, __LINE__);
551
-                $this->debug($maybe_tax_subtotal, $total, $maybe_tax_subtotal->total() . ' === ', __FILE__, __LINE__, 1);
552
-            }
553
-        }
554
-    }
555
-
556
-
557
-    /**
558
-     * returns an array of tax details like:
559
-     *  [
560
-     *      'GST_7' => [
561
-     *          'name'  => 'GST',
562
-     *          'rate'  => float(7),
563
-     *          'total' => float(4.9),
564
-     *      ]
565
-     *  ]
566
-     *
567
-     * @param EE_Line_Item $total_line_item
568
-     * @param array        $non_global_taxes
569
-     * @param float        $line_item_total
570
-     * @return array
571
-     * @throws EE_Error
572
-     * @throws ReflectionException
573
-     */
574
-    private function calculateNonGlobalTaxes(
575
-        EE_Line_Item $total_line_item,
576
-        array $non_global_taxes = [],
577
-        float $line_item_total = 0
578
-    ): array {
579
-        foreach ($total_line_item->children() as $line_item) {
580
-            if ($this->validateLineItemAndType($line_item)) {
581
-                if ($line_item->is_sub_total()) {
582
-                    $non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
583
-                } elseif ($line_item->is_line_item()) {
584
-                    $this->debug($line_item, $line_item->type(), ' ~ ~ TYPE', __FILE__, __LINE__);
585
-                    $non_global_taxes = $this->calculateNonGlobalTaxes(
586
-                        $line_item,
587
-                        $non_global_taxes,
588
-                        $line_item->pretaxTotal()
589
-                    );
590
-                } elseif ($line_item->isSubTax()) {
591
-                    $this->debug($line_item, $line_item->type(), ' ~ ~ TYPE', __FILE__, __LINE__);
592
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
593
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
594
-                        $this->debug($line_item, $tax_ID, '$tax_ID', __FILE__, __LINE__);
595
-                        $non_global_taxes[ $tax_ID ] = [
596
-                            'name'  => $line_item->name(),
597
-                            'rate'  => $line_item->percent(),
598
-                            'total' => 0,
599
-                        ];
600
-                    }
601
-                    $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
602
-                    $this->debug($line_item, "$tax", '$tax', __FILE__, __LINE__);
603
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
604
-                    $this->debug($line_item, $non_global_taxes, '$non_global_taxes', __FILE__, __LINE__);
605
-                }
606
-            }
607
-        }
608
-        return $non_global_taxes;
609
-    }
610
-
611
-
612
-    /**
613
-     * @param EE_Line_Item $total_line_item
614
-     * @param float        $tax_total
615
-     * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
616
-     * @return float
617
-     * @throws EE_Error
618
-     * @throws ReflectionException
619
-     */
620
-    private function applyNonGlobalTaxes(
621
-        EE_Line_Item $total_line_item,
622
-        float $tax_total,
623
-        array $non_global_taxes
624
-    ): float {
625
-        $this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
626
-        $global_taxes   = $total_line_item->tax_descendants();
627
-        $taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
628
-        foreach ($non_global_taxes as $non_global_tax) {
629
-            $this->debug($total_line_item, $non_global_tax, '$non_global_tax', __FILE__, __LINE__);
630
-            $found = false;
631
-            foreach ($global_taxes as $global_tax) {
632
-                $this->debug($global_tax, "{$global_tax->OBJ_ID()}", 'global_tax ID', __FILE__, __LINE__);
633
-                $this->debug($global_tax, $global_tax->name(), 'global_tax name', __FILE__, __LINE__);
634
-                $this->debug($global_tax, $global_tax->percent(), 'global_tax %', __FILE__, __LINE__);
635
-                if (
636
-                    $this->validateLineItemAndType($global_tax)
637
-                    && $non_global_tax['name'] === $global_tax->name()
638
-                    && $non_global_tax['rate'] === $global_tax->percent()
639
-                ) {
640
-                    $found = true;
641
-                    $this->debug($global_tax, $global_tax->total(), '$global_tax->total()', __FILE__, __LINE__);
642
-                    $this->debug($global_tax, $non_global_tax['total'], '$non_global_tax[total]', __FILE__, __LINE__);
643
-
644
-                    $new_total = $global_tax->total() + $non_global_tax['total'];
645
-                    $this->debug($global_tax, $new_total, '$new_total', __FILE__, __LINE__);
646
-                    // add non global tax to matching global tax AND the tax total
647
-                    $global_tax->set_total($new_total);
648
-                    $global_tax->maybe_save();
649
-                    $tax_total += $non_global_tax['total'];
650
-                    $this->debug($global_tax, "$tax_total", ' +++ $tax_total', __FILE__, __LINE__, 2);
651
-                }
652
-            }
653
-            if (! $found) {
654
-                // add a new line item for this non global tax
655
-                $this->debug($total_line_item, $non_global_tax, '$non_global_tax', __FILE__, __LINE__);
656
-                $taxes_subtotal->add_child_line_item(
657
-                    EE_Line_Item::new_instance(
658
-                        [
659
-                            'LIN_name'       => $non_global_tax['name'],
660
-                            'LIN_percent'    => $non_global_tax['rate'],
661
-                            'LIN_is_taxable' => false,
662
-                            'LIN_total'      => $non_global_tax['total'],
663
-                            'LIN_type'       => EEM_Line_Item::type_tax,
664
-                        ]
665
-                    )
666
-                );
667
-                $tax_total += $non_global_tax['total'];
668
-                $this->debug($total_line_item, "$tax_total", ' +++ $tax_total', __FILE__, __LINE__, 2);
669
-            }
670
-        }
671
-        return $this->roundNumericValue($tax_total, true);
672
-    }
673
-
674
-
675
-    /**
676
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
677
-     * recalculate_taxes_and_total
678
-     *
679
-     * @param EE_Line_Item $line_item
680
-     * @return float
681
-     * @throws EE_Error
682
-     * @throws ReflectionException
683
-     */
684
-    public function getTotalTax(EE_Line_Item $line_item): float
685
-    {
686
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
687
-        $this->recalculateTaxSubTotal($line_item);
688
-        $total = 0;
689
-        foreach ($line_item->tax_descendants() as $tax_line_item) {
690
-            if ($this->validateLineItemAndType($tax_line_item)) {
691
-                $total += $tax_line_item->total();
692
-            }
693
-        }
694
-        return $this->roundNumericValue($total, true);
695
-    }
696
-
697
-
698
-    /**
699
-     * Returns the amount taxable among this line item's children (or if it has no children,
700
-     * how much of it is taxable). Does not recalculate totals or subtotals.
701
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
702
-     * but there is a "Taxable" discount), returns 0.
703
-     *
704
-     * @param EE_Line_Item|null $line_item
705
-     * @return float
706
-     * @throws EE_Error
707
-     * @throws ReflectionException
708
-     */
709
-    public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
710
-    {
711
-        $total      = 0;
712
-        $child_line_items = $line_item->children();
713
-        foreach ($child_line_items as $child_line_item) {
714
-            $this->validateLineItemAndType($child_line_item);
715
-            if ($child_line_item->is_sub_total()) {
716
-                $this->debug($child_line_item, $child_line_item->name(), 'ST', __FILE__, __LINE__);
717
-                $total += $this->taxableAmountForGlobalTaxes($child_line_item);
718
-            } elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
719
-                $this->debug($child_line_item, $child_line_item->name(), 'LI', __FILE__, __LINE__);
720
-                // if it's a percent item, only take into account
721
-                // the percentage that's taxable (the taxable total so far)
722
-                $total += $child_line_item->is_percent()
723
-                    ? $this->calculatePercentage($total, $child_line_item->percent(), true)
724
-                    : $child_line_item->pretaxTotal();
725
-            }
726
-        }
727
-        return max($total, 0);
728
-    }
729
-
730
-
731
-    /**
732
-     * strips formatting, rounds the provided number, and returns a float
733
-     * if $round is set to true, then the decimal precision for the site locale will be used,
734
-     * otherwise the default decimal precision of 6 will be used
735
-     *
736
-     * @param float|int|string $number unformatted number value, ex: 1234.5678956789
737
-     * @param bool             $round  whether to round the price off according to the locale settings
738
-     * @return float                      rounded value, ex: 1,234.567896
739
-     */
740
-    private function roundNumericValue($number, bool $round = false): float
741
-    {
742
-        $precision = $round ? $this->locale_precision : $this->decimal_precision;
743
-        return round(
744
-            $this->filterNumericValue($number),
745
-            $precision ?? $this->decimal_precision,
746
-            PHP_ROUND_HALF_UP
747
-        );
748
-    }
749
-
750
-
751
-    /**
752
-     * Removes all characters except digits, +- and .
753
-     *
754
-     * @param float|int|string $number
755
-     * @return float
756
-     */
757
-    private function filterNumericValue($number): float
758
-    {
759
-        return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
760
-    }
761
-
762
-
763
-    /**
764
-     * @param EE_Line_Item|null $line_item
765
-     * @param string|null       $type
766
-     * @return bool
767
-     * @throws EE_Error
768
-     * @throws ReflectionException
769
-     */
770
-    private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
771
-    {
772
-        if (! $line_item instanceof EE_Line_Item) {
773
-            throw new DomainException(
774
-                esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
775
-            );
776
-        }
777
-        if ($type && $line_item->type() !== $type) {
778
-            throw new DomainException(
779
-                sprintf(
780
-                    esc_html__(
781
-                        'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
782
-                        'event_espresso'
783
-                    ),
784
-                    $line_item->type(),
785
-                    $type
786
-                )
787
-            );
788
-        }
789
-        return true;
790
-    }
791
-
792
-
793
-    /**
794
-     * @param EE_Line_Item|null $line_item
795
-     * @param                   $value
796
-     * @param string            $msg
797
-     * @param string            $file
798
-     * @param string            $line
799
-     * @param int               $heading
800
-     * @throws EE_Error
801
-     * @throws ReflectionException
802
-     */
803
-    private function debug(?EE_Line_Item $line_item, $value, string $msg, string $file, string $line, int $heading = 5)
804
-    {
805
-        if ($this->debug) {
806
-            $type = $line_item ? $line_item->type() : '';
807
-            $name = $line_item ? $line_item->name() : '';
808
-            $val  = $value ?? $name;
809
-            $msg  = $msg ? "{$msg} : " : '';
810
-            $msg  = $value ? "{$msg}{$type} {$name}" : "{$msg}{$type}";
811
-            EEH_Debug_Tools::printr($val, $msg, $file, $line, $heading);
812
-        }
813
-    }
25
+	/**
26
+	 * @var bool
27
+	 */
28
+	private $debug = false;  // true  false
29
+
30
+
31
+	/**
32
+	 * number of decimal places to round numbers to when performing calculations
33
+	 *
34
+	 * @var integer
35
+	 */
36
+	protected $decimal_precision = 6;
37
+
38
+	/**
39
+	 * number of decimal places to round numbers to for display
40
+	 *
41
+	 * @var integer
42
+	 */
43
+	protected $locale_precision = 6;
44
+
45
+
46
+	/**
47
+	 * @param EE_Currency_Config $currency_config
48
+	 */
49
+	public function __construct(EE_Currency_Config $currency_config)
50
+	{
51
+		$this->locale_precision = $currency_config->dec_plc;
52
+	}
53
+
54
+
55
+	/**
56
+	 * Gets the final total on this item, taking taxes into account.
57
+	 * Has the side-effect of setting the sub-total as it was just calculated.
58
+	 * If this is used on a grand-total line item, also updates the transaction's
59
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
60
+	 * want to change a persistable transaction with info from a non-persistent line item)
61
+	 *
62
+	 * @param EE_Line_Item $line_item
63
+	 * @param bool         $update_txn_status
64
+	 * @return float
65
+	 * @throws EE_Error
66
+	 * @throws ReflectionException
67
+	 */
68
+	public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
69
+	{
70
+		$this->debug(null, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
71
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
72
+		[$total, $pretax] = $this->recalculateLineItemTotals($line_item);
73
+		$total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
74
+		// no negative totals plz
75
+		$grand_total  = max($pretax + $total_tax, 0);
76
+		$pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
77
+		$grand_total  = $this->updateTotal($line_item, $grand_total, true);
78
+		$this->debug($line_item, $pretax, '$pretax total', __FILE__, __LINE__, 2);
79
+		$this->debug($line_item, $total_tax, '$total_tax', __FILE__, __LINE__, 2);
80
+		$this->debug($line_item, $grand_total, 'grand $total', __FILE__, __LINE__, 2);
81
+		$this->updateTransaction($line_item, $grand_total, $update_txn_status);
82
+		return $grand_total;
83
+	}
84
+
85
+
86
+	/**
87
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
88
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
89
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
90
+	 * when this is called on the grand total
91
+	 *
92
+	 * @param EE_Line_Item $line_item
93
+	 * @param float        $total
94
+	 * @param float        $pretax
95
+	 * @return array
96
+	 * @throws EE_Error
97
+	 * @throws ReflectionException
98
+	 */
99
+	public function recalculateLineItemTotals(
100
+		EE_Line_Item $line_item,
101
+		float $total = 0,
102
+		float $pretax = 0
103
+	): array {
104
+		$this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 1);
105
+		$new_total = $new_pretax = 0;
106
+		switch ($line_item->type()) {
107
+			case EEM_Line_Item::type_total:
108
+			case EEM_Line_Item::type_sub_total:
109
+				[$new_total, $new_pretax] = $this->recalculateSubTotal($line_item, $total, $pretax);
110
+				break;
111
+
112
+			case EEM_Line_Item::type_line_item:
113
+				[$new_total, $new_pretax] = $this->recalculateLineItem($line_item, $total, $pretax);
114
+				break;
115
+
116
+			case EEM_Line_Item::type_sub_line_item:
117
+				// sub line items operate on the total and update both the total AND the pre-tax total
118
+				$new_total = $new_pretax = $this->recalculateSubLineItem($line_item, $total);
119
+				break;
120
+
121
+			case EEM_Line_Item::type_sub_tax:
122
+				// sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
123
+				$new_total = $this->recalculateSubTax($line_item, $pretax);
124
+				break;
125
+
126
+			case EEM_Line_Item::type_tax_sub_total:
127
+			case EEM_Line_Item::type_tax:
128
+			case EEM_Line_Item::type_cancellation:
129
+				// completely ignore tax totals, tax sub-totals, and cancelled line items
130
+				// when calculating the pre-tax-total
131
+				break;
132
+		}
133
+		$this->debug($line_item, $new_pretax, 'PRETAX', __FILE__, __LINE__, 4);
134
+		$this->debug($line_item, $new_total, '+++ TAX', __FILE__, __LINE__, 4);
135
+		return [$total + $new_total, $pretax + $new_pretax];
136
+	}
137
+
138
+
139
+	/**
140
+	 * @param EE_Line_Item $line_item
141
+	 * @param float        $total
142
+	 * @param float        $pretax
143
+	 * @return array
144
+	 * @throws EE_Error
145
+	 * @throws ReflectionException
146
+	 */
147
+	private function recalculateSubTotal(
148
+		EE_Line_Item $line_item,
149
+		float $total = 0,
150
+		float $pretax = 0
151
+	): array {
152
+		$this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
153
+		if ($line_item->is_total()) {
154
+			// if this is the grand total line item
155
+			// then first update ALL of the line item quantities (if need be)
156
+			$this->updateLineItemQuantities($line_item);
157
+		}
158
+		// recursively loop through children and recalculate their totals
159
+		$children = $line_item->children();
160
+		foreach ($children as $child_line_item) {
161
+			[$total, $pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
162
+		}
163
+		// for the actual pre-tax sub total line item, we want to save the pretax value for everything
164
+		if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
165
+			$this->updateUnitPrice($line_item, $pretax);
166
+			$this->updateTotal($line_item, $pretax, true);
167
+		} else {
168
+			$this->updateUnitPrice($line_item, $total);
169
+			$total = $this->updateTotal($line_item, $total, true);
170
+		}
171
+		$pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
172
+		return [$total, $pretax];
173
+	}
174
+
175
+
176
+	/**
177
+	 * @param EE_Line_Item $line_item
178
+	 * @param float        $total
179
+	 * @param float        $pretax
180
+	 * @return array
181
+	 * @throws EE_Error
182
+	 * @throws ReflectionException
183
+	 */
184
+	private function recalculateLineItem(
185
+		EE_Line_Item $line_item,
186
+		float $total = 0,
187
+		float $pretax = 0
188
+	): array {
189
+		$this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
190
+		if ($line_item->is_percent()) {
191
+			$total = $this->calculatePercentage($total, $line_item->percent());
192
+		} else {
193
+			// recursively loop through children and recalculate their totals
194
+			$children = $line_item->children();
195
+			foreach ($children as $child_line_item) {
196
+				[$total, $pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
197
+			}
198
+		}
199
+		$total  = $this->updateTotal($line_item, $total, true);
200
+		$pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
201
+		return [$total, $pretax];
202
+	}
203
+
204
+
205
+	/**
206
+	 * @param EE_Line_Item $line_item
207
+	 * @param float|int    $total
208
+	 * @return float
209
+	 * @throws EE_Error
210
+	 * @throws ReflectionException
211
+	 */
212
+	private function recalculateSubLineItem(EE_Line_Item $line_item, float $total = 0): float
213
+	{
214
+		$this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
215
+		$total = $line_item->is_percent()
216
+			? $this->calculatePercentage($total, $line_item->percent())
217
+			: $this->calculateTotal($line_item);
218
+		$this->debug($line_item, $total, '$total', __FILE__, __LINE__, 4);
219
+		return $this->updateTotal($line_item, $total);
220
+	}
221
+
222
+
223
+	/**
224
+	 * @param EE_Line_Item $line_item
225
+	 * @param float|int    $total
226
+	 * @return float
227
+	 * @throws EE_Error
228
+	 * @throws ReflectionException
229
+	 */
230
+	private function recalculateSubTax(EE_Line_Item $line_item, float $total = 0): float
231
+	{
232
+		$this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
233
+		$total = $this->calculatePercentage($total, $line_item->percent());
234
+		return $this->updateTotal($line_item, $total);
235
+	}
236
+
237
+
238
+	/**
239
+	 * recursively loops through the entire line item tree updating line item quantities accordingly.
240
+	 * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
241
+	 *
242
+	 * @param EE_Line_Item $line_item
243
+	 * @param int          $quantity
244
+	 * @return int
245
+	 * @throws EE_Error
246
+	 * @throws ReflectionException
247
+	 */
248
+	private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
249
+	{
250
+		$count = 0;
251
+		switch ($line_item->type()) {
252
+			case EEM_Line_Item::type_total:
253
+			case EEM_Line_Item::type_sub_total:
254
+			case EEM_Line_Item::type_tax_sub_total:
255
+				// first, loop through children and set their quantities
256
+				$children = $line_item->children();
257
+				foreach ($children as $child_line_item) {
258
+					$count += $this->updateLineItemQuantities($child_line_item);
259
+				}
260
+				// $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
261
+				// totals and subtotals should have a quantity of 1
262
+				// unless their children have all been removed, in which case we can set them to 0
263
+				$quantity = $count > 0 ? 1 : 0;
264
+				$this->updateQuantity($line_item, $quantity);
265
+				return $quantity;
266
+
267
+			case EEM_Line_Item::type_line_item:
268
+				// line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
269
+				// but if this is a percentage based line item, then ensure its quantity is 1
270
+				if ($line_item->is_percent()) {
271
+					// $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
272
+					$this->updateQuantity($line_item, 1);
273
+				}
274
+				// and we also need to loop through all of the sub items and ensure those quantities match this parent.
275
+				$children = $line_item->children();
276
+				foreach ($children as $child_line_item) {
277
+					$count += $this->updateLineItemQuantities($child_line_item, $line_item->quantity());
278
+				}
279
+				return $count;
280
+
281
+			case EEM_Line_Item::type_sub_line_item:
282
+				// percentage based items need their quantity set to 1,
283
+				// all others use the incoming value from the parent line item
284
+				$quantity = $line_item->is_percent() ? 1 : $quantity;
285
+				// $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
286
+				$this->updateQuantity($line_item, $quantity);
287
+				return $quantity;
288
+
289
+			case EEM_Line_Item::type_tax:
290
+			case EEM_Line_Item::type_sub_tax:
291
+				// $this->debug($line_item, null, __FUNCTION__, __FILE__, __LINE__, 4);
292
+				// taxes should have a quantity of 1
293
+				$this->updateQuantity($line_item, 1);
294
+				return $quantity;
295
+
296
+			case EEM_Line_Item::type_cancellation:
297
+				// cancellations will be ignored for all calculations
298
+				// so assume that they are already set correctly, not that it matters
299
+				break;
300
+		}
301
+		return 0;
302
+	}
303
+
304
+
305
+	/**
306
+	 * @param float $total
307
+	 * @param float $percent
308
+	 * @param bool  $round
309
+	 * @return float
310
+	 */
311
+	private function calculatePercentage(float $total, float $percent, bool $round = false): float
312
+	{
313
+		$amount = $total * $percent / 100;
314
+		return $this->roundNumericValue($amount, $round);
315
+	}
316
+
317
+
318
+	/**
319
+	 * @param EE_Line_Item $line_item
320
+	 * @return float
321
+	 * @throws EE_Error
322
+	 * @throws ReflectionException
323
+	 */
324
+	private function calculateTotal(EE_Line_Item $line_item): float
325
+	{
326
+		$total = $line_item->unit_price() * $line_item->quantity();
327
+		return $this->roundNumericValue($total);
328
+	}
329
+
330
+
331
+	/**
332
+	 * @param EE_Line_Item $line_item
333
+	 * @param float        $percent
334
+	 * @throws EE_Error
335
+	 * @throws ReflectionException
336
+	 */
337
+	private function updatePercent(EE_Line_Item $line_item, float $percent)
338
+	{
339
+		// update and save new percent only if incoming value does not match existing value
340
+		if ($line_item->percent() !== $percent) {
341
+			$this->debug($line_item, $percent, 'SET LINE ITEM %: ', __FILE__, __LINE__, 3);
342
+			$line_item->set_percent($percent);
343
+			$line_item->maybe_save();
344
+		}
345
+	}
346
+
347
+
348
+	/**
349
+	 * @param EE_Line_Item $line_item
350
+	 * @param int          $quantity
351
+	 * @throws EE_Error
352
+	 * @throws ReflectionException
353
+	 */
354
+	private function updateQuantity(EE_Line_Item $line_item, int $quantity)
355
+	{
356
+		// update and save new quantity only if incoming value does not match existing value
357
+		if ($line_item->quantity() !== $quantity) {
358
+			$this->debug($line_item, $quantity, 'SET LINE ITEM QTY: ', __FILE__, __LINE__, 3);
359
+			$line_item->set_quantity($quantity);
360
+			$line_item->maybe_save();
361
+		}
362
+	}
363
+
364
+
365
+	/**
366
+	 * @param EE_Line_Item $line_item
367
+	 * @param float        $pretax_total
368
+	 * @param bool         $round
369
+	 * @return float
370
+	 * @throws EE_Error
371
+	 * @throws ReflectionException
372
+	 */
373
+	private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
374
+	{
375
+		$pretax_total = $this->roundNumericValue($pretax_total, $round);
376
+		// update and save new total only if incoming value does not match existing value
377
+		if ($line_item->preTaxTotal() !== $pretax_total) {
378
+			$this->debug($line_item, $pretax_total, 'SET LINE ITEM PRETAX TOTAL: ', __FILE__, __LINE__, 3);
379
+			$line_item->setPreTaxTotal($pretax_total);
380
+			$line_item->maybe_save();
381
+		}
382
+		return $pretax_total;
383
+	}
384
+
385
+
386
+	/**
387
+	 * @param EE_Line_Item $line_item
388
+	 * @param float        $total
389
+	 * @param bool         $round
390
+	 * @return float
391
+	 * @throws EE_Error
392
+	 * @throws ReflectionException
393
+	 */
394
+	private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
395
+	{
396
+		$total = $this->roundNumericValue($total, $round);
397
+		// update and save new total only if incoming value does not match existing value
398
+		if ($line_item->total() !== $total) {
399
+			$this->debug($line_item, $total, 'SET LINE ITEM TOTAL: ', __FILE__, __LINE__, 3);
400
+			$line_item->set_total($total);
401
+			$line_item->maybe_save();
402
+		}
403
+		return $total;
404
+	}
405
+
406
+
407
+	/**
408
+	 * @param EE_Line_Item $line_item
409
+	 * @param float        $total
410
+	 * @param bool         $update_status
411
+	 * @return void
412
+	 * @throws EE_Error
413
+	 * @throws ReflectionException
414
+	 */
415
+	private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
416
+	{
417
+		// only update the related transaction's total
418
+		// if we intend to save this line item and its a grand total
419
+		if ($line_item->allow_persist()) {
420
+			$transaction = $line_item->transaction();
421
+			if ($transaction instanceof EE_Transaction) {
422
+				$transaction->set_total($total);
423
+				if ($update_status) {
424
+					// don't save the TXN because that will be done below
425
+					// and the following method only saves if the status changes
426
+					$transaction->update_status_based_on_total_paid(false);
427
+				}
428
+				if ($transaction->ID()) {
429
+					$transaction->save();
430
+				}
431
+			}
432
+		}
433
+	}
434
+
435
+
436
+	/**
437
+	 * @param EE_Line_Item $line_item
438
+	 * @param float        $total
439
+	 * @return void
440
+	 * @throws EE_Error
441
+	 * @throws ReflectionException
442
+	 */
443
+	private function updateUnitPrice(EE_Line_Item $line_item, float $total)
444
+	{
445
+		$quantity = $line_item->quantity();
446
+		// don't divide by zero else you'll create a singularity and implode the interweb
447
+		$new_unit_price = $quantity !== 0 ? $total / $quantity : 0;
448
+		$new_unit_price = $this->roundNumericValue($new_unit_price);
449
+		// update and save new total only if incoming value does not match existing value
450
+		if ($line_item->unit_price() !== $new_unit_price) {
451
+			$this->debug($line_item, $new_unit_price, 'SET LINE ITEM UNIT PRICE: ', __FILE__, __LINE__, 3);
452
+			$line_item->set_unit_price($new_unit_price);
453
+			$line_item->maybe_save();
454
+		}
455
+	}
456
+
457
+
458
+	/**
459
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
460
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
461
+	 * and tax sub-total if already in the DB
462
+	 *
463
+	 * @param EE_Line_Item $total_line_item
464
+	 * @return float
465
+	 * @throws EE_Error
466
+	 * @throws ReflectionException
467
+	 */
468
+	public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
469
+	{
470
+		$this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
471
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
472
+		// calculate the total taxable amount for globally applied taxes
473
+		$taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
474
+		$this->debug($total_line_item, "$taxable_total", '$taxable_total', __FILE__, __LINE__);
475
+		$tax_total     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
476
+		$this->debug($total_line_item, "$tax_total", '$tax_total', __FILE__, __LINE__);
477
+		$non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
478
+		$tax_total        = $this->applyNonGlobalTaxes($total_line_item, $tax_total, $non_global_taxes);
479
+		$this->debug($total_line_item, "$tax_total", 'FINAL $tax_total', __FILE__, __LINE__);
480
+		$this->recalculateTaxSubTotal($total_line_item);
481
+		return $tax_total;
482
+	}
483
+
484
+
485
+	/**
486
+	 * @param EE_Line_Item $total_line_item
487
+	 * @param float        $taxable_total
488
+	 * @return float
489
+	 * @throws EE_Error
490
+	 * @throws ReflectionException
491
+	 */
492
+	private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
493
+	{
494
+		$this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
495
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
496
+		$total_tax = 0;
497
+		$this->debug($total_line_item, $taxable_total, '$taxable_total', __FILE__, __LINE__);
498
+		// loop through all global taxes all taxes
499
+		$taxes = $total_line_item->tax_descendants();
500
+		foreach ($taxes as $tax) {
501
+			$this->debug($tax, $tax->percent(), 'GLOBAL', __FILE__, __LINE__);
502
+			$tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
503
+
504
+			$this->debug(null, $tax_total, '$tax_total', __FILE__, __LINE__);
505
+			$tax_total = $this->updateTotal($tax, $tax_total, true);
506
+			$total_tax += $tax_total;
507
+		}
508
+		return $this->roundNumericValue($total_tax, true);
509
+	}
510
+
511
+
512
+	/**
513
+	 * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
514
+	 *
515
+	 * @param EE_Line_Item $line_item
516
+	 * @return void
517
+	 * @throws EE_Error
518
+	 * @throws ReflectionException
519
+	 */
520
+	private function recalculateTaxSubTotal(EE_Line_Item $line_item)
521
+	{
522
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
523
+		foreach ($line_item->children() as $maybe_tax_subtotal) {
524
+			if (
525
+				$this->validateLineItemAndType($maybe_tax_subtotal)
526
+				&& $maybe_tax_subtotal->is_tax_sub_total()
527
+			) {
528
+				$total         = 0;
529
+				$total_percent = 0;
530
+				// simply loop through all its children (which should be taxes) and sum their total
531
+				foreach ($maybe_tax_subtotal->children() as $child_tax) {
532
+					$this->debug($child_tax, $child_tax->total(), '$child_tax->total()', __FILE__, __LINE__);
533
+					$this->debug($child_tax, $child_tax->percent(), '$child_tax->percent()', __FILE__, __LINE__);
534
+					if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
535
+						$total         += $child_tax->total();
536
+						$total_percent += $child_tax->percent();
537
+					}
538
+				}
539
+				$this->updateTotal($maybe_tax_subtotal, $total, true);
540
+				$this->updatePercent($maybe_tax_subtotal, $total_percent);
541
+				$this->debug(
542
+					$line_item,
543
+					__FUNCTION__,
544
+					EEH_Debug_Tools::shortClassName(__CLASS__),
545
+					__FILE__,
546
+					__LINE__,
547
+					1
548
+				);
549
+				$this->debug($maybe_tax_subtotal, $total, 'TOTAL TAX', __FILE__, __LINE__);
550
+				$this->debug($maybe_tax_subtotal, $total_percent, 'TOTAL TAX %', __FILE__, __LINE__);
551
+				$this->debug($maybe_tax_subtotal, $total, $maybe_tax_subtotal->total() . ' === ', __FILE__, __LINE__, 1);
552
+			}
553
+		}
554
+	}
555
+
556
+
557
+	/**
558
+	 * returns an array of tax details like:
559
+	 *  [
560
+	 *      'GST_7' => [
561
+	 *          'name'  => 'GST',
562
+	 *          'rate'  => float(7),
563
+	 *          'total' => float(4.9),
564
+	 *      ]
565
+	 *  ]
566
+	 *
567
+	 * @param EE_Line_Item $total_line_item
568
+	 * @param array        $non_global_taxes
569
+	 * @param float        $line_item_total
570
+	 * @return array
571
+	 * @throws EE_Error
572
+	 * @throws ReflectionException
573
+	 */
574
+	private function calculateNonGlobalTaxes(
575
+		EE_Line_Item $total_line_item,
576
+		array $non_global_taxes = [],
577
+		float $line_item_total = 0
578
+	): array {
579
+		foreach ($total_line_item->children() as $line_item) {
580
+			if ($this->validateLineItemAndType($line_item)) {
581
+				if ($line_item->is_sub_total()) {
582
+					$non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
583
+				} elseif ($line_item->is_line_item()) {
584
+					$this->debug($line_item, $line_item->type(), ' ~ ~ TYPE', __FILE__, __LINE__);
585
+					$non_global_taxes = $this->calculateNonGlobalTaxes(
586
+						$line_item,
587
+						$non_global_taxes,
588
+						$line_item->pretaxTotal()
589
+					);
590
+				} elseif ($line_item->isSubTax()) {
591
+					$this->debug($line_item, $line_item->type(), ' ~ ~ TYPE', __FILE__, __LINE__);
592
+					$tax_ID = $line_item->name() . '_' . $line_item->percent();
593
+					if (! isset($non_global_taxes[ $tax_ID ])) {
594
+						$this->debug($line_item, $tax_ID, '$tax_ID', __FILE__, __LINE__);
595
+						$non_global_taxes[ $tax_ID ] = [
596
+							'name'  => $line_item->name(),
597
+							'rate'  => $line_item->percent(),
598
+							'total' => 0,
599
+						];
600
+					}
601
+					$tax = $this->calculatePercentage($line_item_total, $line_item->percent());
602
+					$this->debug($line_item, "$tax", '$tax', __FILE__, __LINE__);
603
+					$non_global_taxes[ $tax_ID ]['total'] += $tax;
604
+					$this->debug($line_item, $non_global_taxes, '$non_global_taxes', __FILE__, __LINE__);
605
+				}
606
+			}
607
+		}
608
+		return $non_global_taxes;
609
+	}
610
+
611
+
612
+	/**
613
+	 * @param EE_Line_Item $total_line_item
614
+	 * @param float        $tax_total
615
+	 * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
616
+	 * @return float
617
+	 * @throws EE_Error
618
+	 * @throws ReflectionException
619
+	 */
620
+	private function applyNonGlobalTaxes(
621
+		EE_Line_Item $total_line_item,
622
+		float $tax_total,
623
+		array $non_global_taxes
624
+	): float {
625
+		$this->debug($total_line_item, __FUNCTION__, EEH_Debug_Tools::shortClassName(__CLASS__), __FILE__, __LINE__, 1);
626
+		$global_taxes   = $total_line_item->tax_descendants();
627
+		$taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
628
+		foreach ($non_global_taxes as $non_global_tax) {
629
+			$this->debug($total_line_item, $non_global_tax, '$non_global_tax', __FILE__, __LINE__);
630
+			$found = false;
631
+			foreach ($global_taxes as $global_tax) {
632
+				$this->debug($global_tax, "{$global_tax->OBJ_ID()}", 'global_tax ID', __FILE__, __LINE__);
633
+				$this->debug($global_tax, $global_tax->name(), 'global_tax name', __FILE__, __LINE__);
634
+				$this->debug($global_tax, $global_tax->percent(), 'global_tax %', __FILE__, __LINE__);
635
+				if (
636
+					$this->validateLineItemAndType($global_tax)
637
+					&& $non_global_tax['name'] === $global_tax->name()
638
+					&& $non_global_tax['rate'] === $global_tax->percent()
639
+				) {
640
+					$found = true;
641
+					$this->debug($global_tax, $global_tax->total(), '$global_tax->total()', __FILE__, __LINE__);
642
+					$this->debug($global_tax, $non_global_tax['total'], '$non_global_tax[total]', __FILE__, __LINE__);
643
+
644
+					$new_total = $global_tax->total() + $non_global_tax['total'];
645
+					$this->debug($global_tax, $new_total, '$new_total', __FILE__, __LINE__);
646
+					// add non global tax to matching global tax AND the tax total
647
+					$global_tax->set_total($new_total);
648
+					$global_tax->maybe_save();
649
+					$tax_total += $non_global_tax['total'];
650
+					$this->debug($global_tax, "$tax_total", ' +++ $tax_total', __FILE__, __LINE__, 2);
651
+				}
652
+			}
653
+			if (! $found) {
654
+				// add a new line item for this non global tax
655
+				$this->debug($total_line_item, $non_global_tax, '$non_global_tax', __FILE__, __LINE__);
656
+				$taxes_subtotal->add_child_line_item(
657
+					EE_Line_Item::new_instance(
658
+						[
659
+							'LIN_name'       => $non_global_tax['name'],
660
+							'LIN_percent'    => $non_global_tax['rate'],
661
+							'LIN_is_taxable' => false,
662
+							'LIN_total'      => $non_global_tax['total'],
663
+							'LIN_type'       => EEM_Line_Item::type_tax,
664
+						]
665
+					)
666
+				);
667
+				$tax_total += $non_global_tax['total'];
668
+				$this->debug($total_line_item, "$tax_total", ' +++ $tax_total', __FILE__, __LINE__, 2);
669
+			}
670
+		}
671
+		return $this->roundNumericValue($tax_total, true);
672
+	}
673
+
674
+
675
+	/**
676
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
677
+	 * recalculate_taxes_and_total
678
+	 *
679
+	 * @param EE_Line_Item $line_item
680
+	 * @return float
681
+	 * @throws EE_Error
682
+	 * @throws ReflectionException
683
+	 */
684
+	public function getTotalTax(EE_Line_Item $line_item): float
685
+	{
686
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
687
+		$this->recalculateTaxSubTotal($line_item);
688
+		$total = 0;
689
+		foreach ($line_item->tax_descendants() as $tax_line_item) {
690
+			if ($this->validateLineItemAndType($tax_line_item)) {
691
+				$total += $tax_line_item->total();
692
+			}
693
+		}
694
+		return $this->roundNumericValue($total, true);
695
+	}
696
+
697
+
698
+	/**
699
+	 * Returns the amount taxable among this line item's children (or if it has no children,
700
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
701
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
702
+	 * but there is a "Taxable" discount), returns 0.
703
+	 *
704
+	 * @param EE_Line_Item|null $line_item
705
+	 * @return float
706
+	 * @throws EE_Error
707
+	 * @throws ReflectionException
708
+	 */
709
+	public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
710
+	{
711
+		$total      = 0;
712
+		$child_line_items = $line_item->children();
713
+		foreach ($child_line_items as $child_line_item) {
714
+			$this->validateLineItemAndType($child_line_item);
715
+			if ($child_line_item->is_sub_total()) {
716
+				$this->debug($child_line_item, $child_line_item->name(), 'ST', __FILE__, __LINE__);
717
+				$total += $this->taxableAmountForGlobalTaxes($child_line_item);
718
+			} elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
719
+				$this->debug($child_line_item, $child_line_item->name(), 'LI', __FILE__, __LINE__);
720
+				// if it's a percent item, only take into account
721
+				// the percentage that's taxable (the taxable total so far)
722
+				$total += $child_line_item->is_percent()
723
+					? $this->calculatePercentage($total, $child_line_item->percent(), true)
724
+					: $child_line_item->pretaxTotal();
725
+			}
726
+		}
727
+		return max($total, 0);
728
+	}
729
+
730
+
731
+	/**
732
+	 * strips formatting, rounds the provided number, and returns a float
733
+	 * if $round is set to true, then the decimal precision for the site locale will be used,
734
+	 * otherwise the default decimal precision of 6 will be used
735
+	 *
736
+	 * @param float|int|string $number unformatted number value, ex: 1234.5678956789
737
+	 * @param bool             $round  whether to round the price off according to the locale settings
738
+	 * @return float                      rounded value, ex: 1,234.567896
739
+	 */
740
+	private function roundNumericValue($number, bool $round = false): float
741
+	{
742
+		$precision = $round ? $this->locale_precision : $this->decimal_precision;
743
+		return round(
744
+			$this->filterNumericValue($number),
745
+			$precision ?? $this->decimal_precision,
746
+			PHP_ROUND_HALF_UP
747
+		);
748
+	}
749
+
750
+
751
+	/**
752
+	 * Removes all characters except digits, +- and .
753
+	 *
754
+	 * @param float|int|string $number
755
+	 * @return float
756
+	 */
757
+	private function filterNumericValue($number): float
758
+	{
759
+		return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
760
+	}
761
+
762
+
763
+	/**
764
+	 * @param EE_Line_Item|null $line_item
765
+	 * @param string|null       $type
766
+	 * @return bool
767
+	 * @throws EE_Error
768
+	 * @throws ReflectionException
769
+	 */
770
+	private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
771
+	{
772
+		if (! $line_item instanceof EE_Line_Item) {
773
+			throw new DomainException(
774
+				esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
775
+			);
776
+		}
777
+		if ($type && $line_item->type() !== $type) {
778
+			throw new DomainException(
779
+				sprintf(
780
+					esc_html__(
781
+						'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
782
+						'event_espresso'
783
+					),
784
+					$line_item->type(),
785
+					$type
786
+				)
787
+			);
788
+		}
789
+		return true;
790
+	}
791
+
792
+
793
+	/**
794
+	 * @param EE_Line_Item|null $line_item
795
+	 * @param                   $value
796
+	 * @param string            $msg
797
+	 * @param string            $file
798
+	 * @param string            $line
799
+	 * @param int               $heading
800
+	 * @throws EE_Error
801
+	 * @throws ReflectionException
802
+	 */
803
+	private function debug(?EE_Line_Item $line_item, $value, string $msg, string $file, string $line, int $heading = 5)
804
+	{
805
+		if ($this->debug) {
806
+			$type = $line_item ? $line_item->type() : '';
807
+			$name = $line_item ? $line_item->name() : '';
808
+			$val  = $value ?? $name;
809
+			$msg  = $msg ? "{$msg} : " : '';
810
+			$msg  = $value ? "{$msg}{$type} {$name}" : "{$msg}{$type}";
811
+			EEH_Debug_Tools::printr($val, $msg, $file, $line, $heading);
812
+		}
813
+	}
814 814
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -25,7 +25,7 @@  discard block
 block discarded – undo
25 25
     /**
26 26
      * @var bool
27 27
      */
28
-    private $debug = false;  // true  false
28
+    private $debug = false; // true  false
29 29
 
30 30
 
31 31
     /**
@@ -548,7 +548,7 @@  discard block
 block discarded – undo
548 548
                 );
549 549
                 $this->debug($maybe_tax_subtotal, $total, 'TOTAL TAX', __FILE__, __LINE__);
550 550
                 $this->debug($maybe_tax_subtotal, $total_percent, 'TOTAL TAX %', __FILE__, __LINE__);
551
-                $this->debug($maybe_tax_subtotal, $total, $maybe_tax_subtotal->total() . ' === ', __FILE__, __LINE__, 1);
551
+                $this->debug($maybe_tax_subtotal, $total, $maybe_tax_subtotal->total().' === ', __FILE__, __LINE__, 1);
552 552
             }
553 553
         }
554 554
     }
@@ -589,10 +589,10 @@  discard block
 block discarded – undo
589 589
                     );
590 590
                 } elseif ($line_item->isSubTax()) {
591 591
                     $this->debug($line_item, $line_item->type(), ' ~ ~ TYPE', __FILE__, __LINE__);
592
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
593
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
592
+                    $tax_ID = $line_item->name().'_'.$line_item->percent();
593
+                    if ( ! isset($non_global_taxes[$tax_ID])) {
594 594
                         $this->debug($line_item, $tax_ID, '$tax_ID', __FILE__, __LINE__);
595
-                        $non_global_taxes[ $tax_ID ] = [
595
+                        $non_global_taxes[$tax_ID] = [
596 596
                             'name'  => $line_item->name(),
597 597
                             'rate'  => $line_item->percent(),
598 598
                             'total' => 0,
@@ -600,7 +600,7 @@  discard block
 block discarded – undo
600 600
                     }
601 601
                     $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
602 602
                     $this->debug($line_item, "$tax", '$tax', __FILE__, __LINE__);
603
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
603
+                    $non_global_taxes[$tax_ID]['total'] += $tax;
604 604
                     $this->debug($line_item, $non_global_taxes, '$non_global_taxes', __FILE__, __LINE__);
605 605
                 }
606 606
             }
@@ -650,7 +650,7 @@  discard block
 block discarded – undo
650 650
                     $this->debug($global_tax, "$tax_total", ' +++ $tax_total', __FILE__, __LINE__, 2);
651 651
                 }
652 652
             }
653
-            if (! $found) {
653
+            if ( ! $found) {
654 654
                 // add a new line item for this non global tax
655 655
                 $this->debug($total_line_item, $non_global_tax, '$non_global_tax', __FILE__, __LINE__);
656 656
                 $taxes_subtotal->add_child_line_item(
@@ -708,7 +708,7 @@  discard block
 block discarded – undo
708 708
      */
709 709
     public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
710 710
     {
711
-        $total      = 0;
711
+        $total = 0;
712 712
         $child_line_items = $line_item->children();
713 713
         foreach ($child_line_items as $child_line_item) {
714 714
             $this->validateLineItemAndType($child_line_item);
@@ -769,7 +769,7 @@  discard block
 block discarded – undo
769 769
      */
770 770
     private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
771 771
     {
772
-        if (! $line_item instanceof EE_Line_Item) {
772
+        if ( ! $line_item instanceof EE_Line_Item) {
773 773
             throw new DomainException(
774 774
                 esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
775 775
             );
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 2 patches
Indentation   +2046 added lines, -2046 removed lines patch added patch discarded remove patch
@@ -16,2054 +16,2054 @@
 block discarded – undo
16 16
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
17 17
 {
18 18
 
19
-    /**
20
-     * TicKet Archived:
21
-     * constant used by ticket_status() to indicate that a ticket is archived
22
-     * and no longer available for purchase
23
-     */
24
-    const archived = 'TKA';
25
-
26
-    /**
27
-     * TicKet Expired:
28
-     * constant used by ticket_status() to indicate that a ticket is expired
29
-     * and no longer available for purchase
30
-     */
31
-    const expired = 'TKE';
32
-
33
-    /**
34
-     * TicKet On sale:
35
-     * constant used by ticket_status() to indicate that a ticket is On Sale
36
-     * and IS available for purchase
37
-     */
38
-    const onsale = 'TKO';
39
-
40
-    /**
41
-     * TicKet Pending:
42
-     * constant used by ticket_status() to indicate that a ticket is pending
43
-     * and is NOT YET available for purchase
44
-     */
45
-    const pending = 'TKP';
46
-
47
-    /**
48
-     * TicKet Sold out:
49
-     * constant used by ticket_status() to indicate that a ticket is sold out
50
-     * and no longer available for purchases
51
-     */
52
-    const sold_out = 'TKS';
53
-
54
-    /**
55
-     * extra meta key for tracking ticket reservations
56
-     *
57
-     * @type string
58
-     */
59
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
60
-
61
-    /**
62
-     * override of parent property
63
-     *
64
-     * @var EEM_Ticket
65
-     */
66
-    protected $_model;
67
-
68
-    /**
69
-     * cached result from method of the same name
70
-     *
71
-     * @var float $_ticket_total_with_taxes
72
-     */
73
-    private $_ticket_total_with_taxes;
74
-
75
-    /**
76
-     * @var TicketPriceModifiers
77
-     */
78
-    protected $ticket_price_modifiers;
79
-
80
-
81
-    /**
82
-     * @param array  $props_n_values          incoming values
83
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
84
-     *                                        used.)
85
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
86
-     *                                        date_format and the second value is the time format
87
-     * @return EE_Ticket
88
-     * @throws EE_Error
89
-     * @throws ReflectionException
90
-     */
91
-    public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
92
-    {
93
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
94
-        return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
95
-    }
96
-
97
-
98
-    /**
99
-     * @param array  $props_n_values  incoming values from the database
100
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
101
-     *                                the website will be used.
102
-     * @return EE_Ticket
103
-     * @throws EE_Error
104
-     * @throws ReflectionException
105
-     */
106
-    public static function new_instance_from_db($props_n_values = [], $timezone = null)
107
-    {
108
-        return new self($props_n_values, true, $timezone);
109
-    }
110
-
111
-
112
-    /**
113
-     * @param array  $fieldValues
114
-     * @param false  $bydb
115
-     * @param string $timezone
116
-     * @param array  $date_formats
117
-     * @throws EE_Error
118
-     * @throws ReflectionException
119
-     */
120
-    public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
121
-    {
122
-        parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
123
-        $this->ticket_price_modifiers = LoaderFactory::getNew(TicketPriceModifiers::class, [$this]);
124
-    }
125
-
126
-
127
-    /**
128
-     * @return bool
129
-     * @throws EE_Error
130
-     * @throws ReflectionException
131
-     */
132
-    public function parent()
133
-    {
134
-        return $this->get('TKT_parent');
135
-    }
136
-
137
-
138
-    /**
139
-     * return if a ticket has quantities available for purchase
140
-     *
141
-     * @param int $DTT_ID the primary key for a particular datetime
142
-     * @return boolean
143
-     * @throws EE_Error
144
-     * @throws ReflectionException
145
-     */
146
-    public function available($DTT_ID = 0)
147
-    {
148
-        // are we checking availability for a particular datetime ?
149
-        if ($DTT_ID) {
150
-            // get that datetime object
151
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
152
-            // if  ticket sales for this datetime have exceeded the reg limit...
153
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
154
-                return false;
155
-            }
156
-        }
157
-        // datetime is still open for registration, but is this ticket sold out ?
158
-        return $this->qty() < 1 || $this->qty() > $this->sold();
159
-    }
160
-
161
-
162
-    /**
163
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
164
-     *
165
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
166
-     *                               relevant status const
167
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
168
-     *                               further processing
169
-     * @return mixed status int if the display string isn't requested
170
-     * @throws EE_Error
171
-     * @throws ReflectionException
172
-     */
173
-    public function ticket_status($display = false, $remaining = null)
174
-    {
175
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
176
-        if (! $remaining) {
177
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
178
-        }
179
-        if ($this->get('TKT_deleted')) {
180
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
181
-        }
182
-        if ($this->is_expired()) {
183
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
184
-        }
185
-        if ($this->is_pending()) {
186
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
187
-        }
188
-        if ($this->is_on_sale()) {
189
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
190
-        }
191
-        return '';
192
-    }
193
-
194
-
195
-    /**
196
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
197
-     * considering ALL the factors used for figuring that out.
198
-     *
199
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
200
-     * @return boolean         true = tickets remaining, false not.
201
-     * @throws EE_Error
202
-     * @throws ReflectionException
203
-     */
204
-    public function is_remaining($DTT_ID = 0)
205
-    {
206
-        $num_remaining = $this->remaining($DTT_ID);
207
-        if ($num_remaining === 0) {
208
-            return false;
209
-        }
210
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
211
-            return false;
212
-        }
213
-        return true;
214
-    }
215
-
216
-
217
-    /**
218
-     * return the total number of tickets available for purchase
219
-     *
220
-     * @param int $DTT_ID  the primary key for a particular datetime.
221
-     *                     set to 0 for all related datetimes
222
-     * @return int
223
-     * @throws EE_Error
224
-     * @throws ReflectionException
225
-     */
226
-    public function remaining($DTT_ID = 0)
227
-    {
228
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
229
-    }
230
-
231
-
232
-    /**
233
-     * Gets min
234
-     *
235
-     * @return int
236
-     * @throws EE_Error
237
-     * @throws ReflectionException
238
-     */
239
-    public function min()
240
-    {
241
-        return $this->get('TKT_min');
242
-    }
243
-
244
-
245
-    /**
246
-     * return if a ticket is no longer available cause its available dates have expired.
247
-     *
248
-     * @return boolean
249
-     * @throws EE_Error
250
-     * @throws ReflectionException
251
-     */
252
-    public function is_expired()
253
-    {
254
-        return ($this->get_raw('TKT_end_date') < time());
255
-    }
256
-
257
-
258
-    /**
259
-     * Return if a ticket is yet to go on sale or not
260
-     *
261
-     * @return boolean
262
-     * @throws EE_Error
263
-     * @throws ReflectionException
264
-     */
265
-    public function is_pending()
266
-    {
267
-        return ($this->get_raw('TKT_start_date') >= time());
268
-    }
269
-
270
-
271
-    /**
272
-     * Return if a ticket is on sale or not
273
-     *
274
-     * @return boolean
275
-     * @throws EE_Error
276
-     * @throws ReflectionException
277
-     */
278
-    public function is_on_sale()
279
-    {
280
-        return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
281
-    }
282
-
283
-
284
-    /**
285
-     * This returns the chronologically last datetime that this ticket is associated with
286
-     *
287
-     * @param string $date_format
288
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
289
-     *                            the end date ie: Jan 01 "to" Dec 31
290
-     * @return string
291
-     * @throws EE_Error
292
-     * @throws ReflectionException
293
-     */
294
-    public function date_range($date_format = '', $conjunction = ' - ')
295
-    {
296
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
297
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
298
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
299
-            : '';
300
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
301
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
302
-            : '';
303
-
304
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
305
-    }
306
-
307
-
308
-    /**
309
-     * This returns the chronologically first datetime that this ticket is associated with
310
-     *
311
-     * @return EE_Datetime
312
-     * @throws EE_Error
313
-     * @throws ReflectionException
314
-     */
315
-    public function first_datetime()
316
-    {
317
-        $datetimes = $this->datetimes(['limit' => 1]);
318
-        return reset($datetimes);
319
-    }
320
-
321
-
322
-    /**
323
-     * Gets all the datetimes this ticket can be used for attending.
324
-     * Unless otherwise specified, orders datetimes by start date.
325
-     *
326
-     * @param array $query_params
327
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
328
-     * @return EE_Datetime[]|EE_Base_Class[]
329
-     * @throws EE_Error
330
-     * @throws ReflectionException
331
-     */
332
-    public function datetimes($query_params = [])
333
-    {
334
-        if (! isset($query_params['order_by'])) {
335
-            $query_params['order_by']['DTT_order'] = 'ASC';
336
-        }
337
-        return $this->get_many_related('Datetime', $query_params);
338
-    }
339
-
340
-
341
-    /**
342
-     * This returns the chronologically last datetime that this ticket is associated with
343
-     *
344
-     * @return EE_Datetime
345
-     * @throws EE_Error
346
-     * @throws ReflectionException
347
-     */
348
-    public function last_datetime()
349
-    {
350
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
351
-        return end($datetimes);
352
-    }
353
-
354
-
355
-    /**
356
-     * This returns the total tickets sold depending on the given parameters.
357
-     *
358
-     * @param string $what    Can be one of two options: 'ticket', 'datetime'.
359
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
360
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
361
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
362
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
363
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
364
-     * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
365
-     * @return mixed (array|int)          how many tickets have sold
366
-     * @throws EE_Error
367
-     * @throws ReflectionException
368
-     */
369
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
370
-    {
371
-        $total        = 0;
372
-        $tickets_sold = $this->_all_tickets_sold();
373
-        switch ($what) {
374
-            case 'ticket':
375
-                return $tickets_sold['ticket'];
376
-
377
-            case 'datetime':
378
-                if (empty($tickets_sold['datetime'])) {
379
-                    return $total;
380
-                }
381
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
382
-                    EE_Error::add_error(
383
-                        __(
384
-                            'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
385
-                            'event_espresso'
386
-                        ),
387
-                        __FILE__,
388
-                        __FUNCTION__,
389
-                        __LINE__
390
-                    );
391
-                    return $total;
392
-                }
393
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
394
-
395
-            default:
396
-                return $total;
397
-        }
398
-    }
399
-
400
-
401
-    /**
402
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
403
-     *
404
-     * @return EE_Ticket[]
405
-     * @throws EE_Error
406
-     * @throws ReflectionException
407
-     */
408
-    protected function _all_tickets_sold()
409
-    {
410
-        $datetimes    = $this->get_many_related('Datetime');
411
-        $tickets_sold = [];
412
-        if (! empty($datetimes)) {
413
-            foreach ($datetimes as $datetime) {
414
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
415
-            }
416
-        }
417
-        // Tickets sold
418
-        $tickets_sold['ticket'] = $this->sold();
419
-        return $tickets_sold;
420
-    }
421
-
422
-
423
-    /**
424
-     * This returns the base price object for the ticket.
425
-     *
426
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
427
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
428
-     * @throws EE_Error
429
-     * @throws ReflectionException
430
-     */
431
-    public function base_price(bool $return_array = false)
432
-    {
433
-        $base_price = $this->ticket_price_modifiers->getBasePrice();
434
-        if (! empty($base_price)) {
435
-            return $return_array ? $base_price : reset($base_price);
436
-        }
437
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
438
-        return $return_array
439
-            ? $this->get_many_related('Price', [$_where])
440
-            : $this->get_first_related('Price', [$_where]);
441
-    }
442
-
443
-
444
-    /**
445
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
446
-     *
447
-     * @return EE_Price[]
448
-     * @throws EE_Error
449
-     * @throws ReflectionException
450
-     */
451
-    public function price_modifiers(): array
452
-    {
453
-        $price_modifiers = $this->usesGlobalTaxes()
454
-            ? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
455
-            : $this->ticket_price_modifiers ->getAllModifiersForTicket();
456
-        if (! empty($price_modifiers)) {
457
-            return $price_modifiers;
458
-        }
459
-        return $this->prices(
460
-            [
461
-                [
462
-                    'Price_Type.PBT_ID' => [
463
-                        'NOT IN',
464
-                        [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
465
-                    ]
466
-                ]
467
-            ]
468
-        );
469
-    }
470
-
471
-
472
-    /**
473
-     * This returns ONLY the TAX price modifiers for the ticket
474
-     *
475
-     * @return EE_Price[]
476
-     * @throws EE_Error
477
-     * @throws ReflectionException
478
-     */
479
-    public function tax_price_modifiers(): array
480
-    {
481
-        $tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
482
-        if (! empty($tax_price_modifiers)) {
483
-            return $tax_price_modifiers;
484
-        }
485
-        return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
486
-    }
487
-
488
-
489
-    /**
490
-     * Gets all the prices that combine to form the final price of this ticket
491
-     *
492
-     * @param array $query_params
493
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
494
-     * @return EE_Price[]|EE_Base_Class[]
495
-     * @throws EE_Error
496
-     * @throws ReflectionException
497
-     */
498
-    public function prices(array $query_params = []): array
499
-    {
500
-        if (! isset($query_params['order_by'])) {
501
-            $query_params['order_by']['PRC_order'] = 'ASC';
502
-        }
503
-        return $this->get_many_related('Price', $query_params);
504
-    }
505
-
506
-
507
-    /**
508
-     * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
509
-     *
510
-     * @param array $query_params
511
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
512
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
513
-     * @throws EE_Error
514
-     * @throws ReflectionException
515
-     */
516
-    public function datetime_tickets($query_params = [])
517
-    {
518
-        return $this->get_many_related('Datetime_Ticket', $query_params);
519
-    }
520
-
521
-
522
-    /**
523
-     * Gets all the datetimes from the db ordered by DTT_order
524
-     *
525
-     * @param boolean $show_expired
526
-     * @param boolean $show_deleted
527
-     * @return EE_Datetime[]
528
-     * @throws EE_Error
529
-     * @throws ReflectionException
530
-     */
531
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
532
-    {
533
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
534
-            $this->ID(),
535
-            $show_expired,
536
-            $show_deleted
537
-        );
538
-    }
539
-
540
-
541
-    /**
542
-     * Gets ID
543
-     *
544
-     * @return int
545
-     * @throws EE_Error
546
-     * @throws ReflectionException
547
-     */
548
-    public function ID()
549
-    {
550
-        return $this->get('TKT_ID');
551
-    }
552
-
553
-
554
-    /**
555
-     * get the author of the ticket.
556
-     *
557
-     * @return int
558
-     * @throws EE_Error
559
-     * @throws ReflectionException
560
-     * @since 4.5.0
561
-     */
562
-    public function wp_user()
563
-    {
564
-        return $this->get('TKT_wp_user');
565
-    }
566
-
567
-
568
-    /**
569
-     * Gets the template for the ticket
570
-     *
571
-     * @return EE_Ticket_Template|EE_Base_Class
572
-     * @throws EE_Error
573
-     * @throws ReflectionException
574
-     */
575
-    public function template()
576
-    {
577
-        return $this->get_first_related('Ticket_Template');
578
-    }
579
-
580
-
581
-    /**
582
-     * Simply returns an array of EE_Price objects that are taxes.
583
-     *
584
-     * @return EE_Price[]
585
-     * @throws EE_Error
586
-     * @throws ReflectionException
587
-     */
588
-    public function get_ticket_taxes_for_admin(): array
589
-    {
590
-        return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
591
-    }
592
-
593
-
594
-    /**
595
-     * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
596
-     * as opposed to having tax price modifiers added directly to each ticket
597
-     *
598
-     * @return bool
599
-     * @throws EE_Error
600
-     * @throws ReflectionException
601
-     * @since   $VID:$
602
-     */
603
-    public function usesGlobalTaxes(): bool
604
-    {
605
-        return $this->taxable();
606
-    }
607
-
608
-
609
-    /**
610
-     * @return float
611
-     * @throws EE_Error
612
-     * @throws ReflectionException
613
-     */
614
-    public function ticket_price()
615
-    {
616
-        return $this->get('TKT_price');
617
-    }
618
-
619
-
620
-    /**
621
-     * @return mixed
622
-     * @throws EE_Error
623
-     * @throws ReflectionException
624
-     */
625
-    public function pretty_price()
626
-    {
627
-        return $this->get_pretty('TKT_price');
628
-    }
629
-
630
-
631
-    /**
632
-     * @return bool
633
-     * @throws EE_Error
634
-     * @throws ReflectionException
635
-     */
636
-    public function is_free()
637
-    {
638
-        return $this->get_ticket_total_with_taxes() === (float) 0;
639
-    }
640
-
641
-
642
-    /**
643
-     * get_ticket_total_with_taxes
644
-     *
645
-     * @param bool $no_cache
646
-     * @return float
647
-     * @throws EE_Error
648
-     * @throws ReflectionException
649
-     */
650
-    public function get_ticket_total_with_taxes($no_cache = false)
651
-    {
652
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
653
-            $this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
654
-                ? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
655
-                : $this->ticket_price();
656
-        }
657
-        return (float) $this->_ticket_total_with_taxes;
658
-    }
659
-
660
-
661
-    /**
662
-     * @throws EE_Error
663
-     * @throws ReflectionException
664
-     */
665
-    public function ensure_TKT_Price_correct()
666
-    {
667
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
668
-        $this->save();
669
-    }
670
-
671
-
672
-    /**
673
-     * @return float
674
-     * @throws EE_Error
675
-     * @throws ReflectionException
676
-     */
677
-    public function get_ticket_subtotal()
678
-    {
679
-        return EE_Taxes::get_subtotal_for_admin($this);
680
-    }
681
-
682
-
683
-    /**
684
-     * Returns the total taxes applied to this ticket
685
-     *
686
-     * @return float
687
-     * @throws EE_Error
688
-     * @throws ReflectionException
689
-     */
690
-    public function get_ticket_taxes_total_for_admin()
691
-    {
692
-        return EE_Taxes::get_total_taxes_for_admin($this);
693
-    }
694
-
695
-
696
-    /**
697
-     * Sets name
698
-     *
699
-     * @param string $name
700
-     * @throws EE_Error
701
-     * @throws ReflectionException
702
-     */
703
-    public function set_name($name)
704
-    {
705
-        $this->set('TKT_name', $name);
706
-    }
707
-
708
-
709
-    /**
710
-     * Gets description
711
-     *
712
-     * @return string
713
-     * @throws EE_Error
714
-     * @throws ReflectionException
715
-     */
716
-    public function description()
717
-    {
718
-        return $this->get('TKT_description');
719
-    }
720
-
721
-
722
-    /**
723
-     * Sets description
724
-     *
725
-     * @param string $description
726
-     * @throws EE_Error
727
-     * @throws ReflectionException
728
-     */
729
-    public function set_description($description)
730
-    {
731
-        $this->set('TKT_description', $description);
732
-    }
733
-
734
-
735
-    /**
736
-     * Gets start_date
737
-     *
738
-     * @param string $date_format
739
-     * @param string $time_format
740
-     * @return string
741
-     * @throws EE_Error
742
-     * @throws ReflectionException
743
-     */
744
-    public function start_date($date_format = '', $time_format = '')
745
-    {
746
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
747
-    }
748
-
749
-
750
-    /**
751
-     * Sets start_date
752
-     *
753
-     * @param string $start_date
754
-     * @return void
755
-     * @throws EE_Error
756
-     * @throws ReflectionException
757
-     */
758
-    public function set_start_date($start_date)
759
-    {
760
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
761
-    }
762
-
763
-
764
-    /**
765
-     * Gets end_date
766
-     *
767
-     * @param string $date_format
768
-     * @param string $time_format
769
-     * @return string
770
-     * @throws EE_Error
771
-     * @throws ReflectionException
772
-     */
773
-    public function end_date($date_format = '', $time_format = '')
774
-    {
775
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
776
-    }
777
-
778
-
779
-    /**
780
-     * Sets end_date
781
-     *
782
-     * @param string $end_date
783
-     * @return void
784
-     * @throws EE_Error
785
-     * @throws ReflectionException
786
-     */
787
-    public function set_end_date($end_date)
788
-    {
789
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
790
-    }
791
-
792
-
793
-    /**
794
-     * Sets sell until time
795
-     *
796
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
797
-     * @throws EE_Error
798
-     * @throws ReflectionException
799
-     * @since 4.5.0
800
-     */
801
-    public function set_end_time($time)
802
-    {
803
-        $this->_set_time_for($time, 'TKT_end_date');
804
-    }
805
-
806
-
807
-    /**
808
-     * Sets min
809
-     *
810
-     * @param int $min
811
-     * @return void
812
-     * @throws EE_Error
813
-     * @throws ReflectionException
814
-     */
815
-    public function set_min($min)
816
-    {
817
-        $this->set('TKT_min', $min);
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets max
823
-     *
824
-     * @return int
825
-     * @throws EE_Error
826
-     * @throws ReflectionException
827
-     */
828
-    public function max()
829
-    {
830
-        return $this->get('TKT_max');
831
-    }
832
-
833
-
834
-    /**
835
-     * Sets max
836
-     *
837
-     * @param int $max
838
-     * @return void
839
-     * @throws EE_Error
840
-     * @throws ReflectionException
841
-     */
842
-    public function set_max($max)
843
-    {
844
-        $this->set('TKT_max', $max);
845
-    }
846
-
847
-
848
-    /**
849
-     * Sets price
850
-     *
851
-     * @param float $price
852
-     * @return void
853
-     * @throws EE_Error
854
-     * @throws ReflectionException
855
-     */
856
-    public function set_price($price)
857
-    {
858
-        $this->set('TKT_price', $price);
859
-    }
860
-
861
-
862
-    /**
863
-     * Gets sold
864
-     *
865
-     * @return int
866
-     * @throws EE_Error
867
-     * @throws ReflectionException
868
-     */
869
-    public function sold()
870
-    {
871
-        return $this->get_raw('TKT_sold');
872
-    }
873
-
874
-
875
-    /**
876
-     * Sets sold
877
-     *
878
-     * @param int $sold
879
-     * @return void
880
-     * @throws EE_Error
881
-     * @throws ReflectionException
882
-     */
883
-    public function set_sold($sold)
884
-    {
885
-        // sold can not go below zero
886
-        $sold = max(0, $sold);
887
-        $this->set('TKT_sold', $sold);
888
-    }
889
-
890
-
891
-    /**
892
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
893
-     * associated datetimes.
894
-     *
895
-     * @param int $qty
896
-     * @return boolean
897
-     * @throws EE_Error
898
-     * @throws InvalidArgumentException
899
-     * @throws InvalidDataTypeException
900
-     * @throws InvalidInterfaceException
901
-     * @throws ReflectionException
902
-     * @since 4.9.80.p
903
-     */
904
-    public function increaseSold($qty = 1)
905
-    {
906
-        $qty = absint($qty);
907
-        // increment sold and decrement reserved datetime quantities simultaneously
908
-        // don't worry about failures, because they must have already had a spot reserved
909
-        $this->increaseSoldForDatetimes($qty);
910
-        // Increment and decrement ticket quantities simultaneously
911
-        $success = $this->adjustNumericFieldsInDb(
912
-            [
913
-                'TKT_reserved' => $qty * -1,
914
-                'TKT_sold'     => $qty,
915
-            ]
916
-        );
917
-        do_action(
918
-            'AHEE__EE_Ticket__increase_sold',
919
-            $this,
920
-            $qty,
921
-            $this->sold(),
922
-            $success
923
-        );
924
-        return $success;
925
-    }
926
-
927
-
928
-    /**
929
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
930
-     *
931
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
932
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
933
-     * @param EE_Datetime[] $datetimes
934
-     * @throws EE_Error
935
-     * @throws InvalidArgumentException
936
-     * @throws InvalidDataTypeException
937
-     * @throws InvalidInterfaceException
938
-     * @throws ReflectionException
939
-     * @since 4.9.80.p
940
-     */
941
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
942
-    {
943
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
944
-        foreach ($datetimes as $datetime) {
945
-            $datetime->increaseSold($qty);
946
-        }
947
-    }
948
-
949
-
950
-    /**
951
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
952
-     * DB and then updates the model objects.
953
-     * Does not affect the reserved counts.
954
-     *
955
-     * @param int $qty
956
-     * @return boolean
957
-     * @throws EE_Error
958
-     * @throws InvalidArgumentException
959
-     * @throws InvalidDataTypeException
960
-     * @throws InvalidInterfaceException
961
-     * @throws ReflectionException
962
-     * @since 4.9.80.p
963
-     */
964
-    public function decreaseSold($qty = 1)
965
-    {
966
-        $qty = absint($qty);
967
-        $this->decreaseSoldForDatetimes($qty);
968
-        $success = $this->adjustNumericFieldsInDb(
969
-            [
970
-                'TKT_sold' => $qty * -1,
971
-            ]
972
-        );
973
-        do_action(
974
-            'AHEE__EE_Ticket__decrease_sold',
975
-            $this,
976
-            $qty,
977
-            $this->sold(),
978
-            $success
979
-        );
980
-        return $success;
981
-    }
982
-
983
-
984
-    /**
985
-     * Decreases sold on related datetimes
986
-     *
987
-     * @param int           $qty
988
-     * @param EE_Datetime[] $datetimes
989
-     * @return void
990
-     * @throws EE_Error
991
-     * @throws InvalidArgumentException
992
-     * @throws InvalidDataTypeException
993
-     * @throws InvalidInterfaceException
994
-     * @throws ReflectionException
995
-     * @since 4.9.80.p
996
-     */
997
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
998
-    {
999
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1000
-        if (is_array($datetimes)) {
1001
-            foreach ($datetimes as $datetime) {
1002
-                if ($datetime instanceof EE_Datetime) {
1003
-                    $datetime->decreaseSold($qty);
1004
-                }
1005
-            }
1006
-        }
1007
-    }
1008
-
1009
-
1010
-    /**
1011
-     * Gets qty of reserved tickets
1012
-     *
1013
-     * @return int
1014
-     * @throws EE_Error
1015
-     * @throws ReflectionException
1016
-     */
1017
-    public function reserved()
1018
-    {
1019
-        return $this->get_raw('TKT_reserved');
1020
-    }
1021
-
1022
-
1023
-    /**
1024
-     * Sets reserved
1025
-     *
1026
-     * @param int $reserved
1027
-     * @return void
1028
-     * @throws EE_Error
1029
-     * @throws ReflectionException
1030
-     */
1031
-    public function set_reserved($reserved)
1032
-    {
1033
-        // reserved can not go below zero
1034
-        $reserved = max(0, (int) $reserved);
1035
-        $this->set('TKT_reserved', $reserved);
1036
-    }
1037
-
1038
-
1039
-    /**
1040
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1041
-     *
1042
-     * @param int    $qty
1043
-     * @param string $source
1044
-     * @return bool whether we successfully reserved the ticket or not.
1045
-     * @throws EE_Error
1046
-     * @throws InvalidArgumentException
1047
-     * @throws ReflectionException
1048
-     * @throws InvalidDataTypeException
1049
-     * @throws InvalidInterfaceException
1050
-     * @since 4.9.80.p
1051
-     */
1052
-    public function increaseReserved($qty = 1, $source = 'unknown')
1053
-    {
1054
-        $qty = absint($qty);
1055
-        do_action(
1056
-            'AHEE__EE_Ticket__increase_reserved__begin',
1057
-            $this,
1058
-            $qty,
1059
-            $source
1060
-        );
1061
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1062
-        $success                         = false;
1063
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1064
-        if ($datetimes_adjusted_successfully) {
1065
-            $success = $this->incrementFieldConditionallyInDb(
1066
-                'TKT_reserved',
1067
-                'TKT_sold',
1068
-                'TKT_qty',
1069
-                $qty
1070
-            );
1071
-            if (! $success) {
1072
-                // The datetimes were successfully bumped, but not the
1073
-                // ticket. So we need to manually rollback the datetimes.
1074
-                $this->decreaseReservedForDatetimes($qty);
1075
-            }
1076
-        }
1077
-        do_action(
1078
-            'AHEE__EE_Ticket__increase_reserved',
1079
-            $this,
1080
-            $qty,
1081
-            $this->reserved(),
1082
-            $success
1083
-        );
1084
-        return $success;
1085
-    }
1086
-
1087
-
1088
-    /**
1089
-     * Increases reserved counts on related datetimes
1090
-     *
1091
-     * @param int           $qty
1092
-     * @param EE_Datetime[] $datetimes
1093
-     * @return boolean indicating success
1094
-     * @throws EE_Error
1095
-     * @throws InvalidArgumentException
1096
-     * @throws InvalidDataTypeException
1097
-     * @throws InvalidInterfaceException
1098
-     * @throws ReflectionException
1099
-     * @since 4.9.80.p
1100
-     */
1101
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1102
-    {
1103
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1104
-        $datetimes_updated = [];
1105
-        $limit_exceeded    = false;
1106
-        if (is_array($datetimes)) {
1107
-            foreach ($datetimes as $datetime) {
1108
-                if ($datetime instanceof EE_Datetime) {
1109
-                    if ($datetime->increaseReserved($qty)) {
1110
-                        $datetimes_updated[] = $datetime;
1111
-                    } else {
1112
-                        $limit_exceeded = true;
1113
-                        break;
1114
-                    }
1115
-                }
1116
-            }
1117
-            // If somewhere along the way we detected a datetime whose
1118
-            // limit was exceeded, do a manual rollback.
1119
-            if ($limit_exceeded) {
1120
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1121
-                return false;
1122
-            }
1123
-        }
1124
-        return true;
1125
-    }
1126
-
1127
-
1128
-    /**
1129
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1130
-     *
1131
-     * @param int    $qty
1132
-     * @param bool   $adjust_datetimes
1133
-     * @param string $source
1134
-     * @return boolean
1135
-     * @throws EE_Error
1136
-     * @throws InvalidArgumentException
1137
-     * @throws ReflectionException
1138
-     * @throws InvalidDataTypeException
1139
-     * @throws InvalidInterfaceException
1140
-     * @since 4.9.80.p
1141
-     */
1142
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1143
-    {
1144
-        $qty = absint($qty);
1145
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1146
-        if ($adjust_datetimes) {
1147
-            $this->decreaseReservedForDatetimes($qty);
1148
-        }
1149
-        $success = $this->adjustNumericFieldsInDb(
1150
-            [
1151
-                'TKT_reserved' => $qty * -1,
1152
-            ]
1153
-        );
1154
-        do_action(
1155
-            'AHEE__EE_Ticket__decrease_reserved',
1156
-            $this,
1157
-            $qty,
1158
-            $this->reserved(),
1159
-            $success
1160
-        );
1161
-        return $success;
1162
-    }
1163
-
1164
-
1165
-    /**
1166
-     * Decreases the reserved count on the specified datetimes.
1167
-     *
1168
-     * @param int           $qty
1169
-     * @param EE_Datetime[] $datetimes
1170
-     * @throws EE_Error
1171
-     * @throws InvalidArgumentException
1172
-     * @throws ReflectionException
1173
-     * @throws InvalidDataTypeException
1174
-     * @throws InvalidInterfaceException
1175
-     * @since 4.9.80.p
1176
-     */
1177
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1178
-    {
1179
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1180
-        foreach ($datetimes as $datetime) {
1181
-            if ($datetime instanceof EE_Datetime) {
1182
-                $datetime->decreaseReserved($qty);
1183
-            }
1184
-        }
1185
-    }
1186
-
1187
-
1188
-    /**
1189
-     * Gets ticket quantity
1190
-     *
1191
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1192
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1193
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1194
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1195
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1196
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1197
-     * @return int
1198
-     * @throws EE_Error
1199
-     * @throws ReflectionException
1200
-     */
1201
-    public function qty($context = '')
1202
-    {
1203
-        switch ($context) {
1204
-            case 'reg_limit':
1205
-                return $this->real_quantity_on_ticket();
1206
-            case 'saleable':
1207
-                return $this->real_quantity_on_ticket('saleable');
1208
-            default:
1209
-                return $this->get_raw('TKT_qty');
1210
-        }
1211
-    }
1212
-
1213
-
1214
-    /**
1215
-     * Gets ticket quantity
1216
-     *
1217
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1218
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1219
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1220
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1221
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1222
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1223
-     *                            set to 0 for all related datetimes
1224
-     * @return int
1225
-     * @throws EE_Error
1226
-     * @throws ReflectionException
1227
-     */
1228
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1229
-    {
1230
-        $raw = $this->get_raw('TKT_qty');
1231
-        // return immediately if it's zero
1232
-        if ($raw === 0) {
1233
-            return $raw;
1234
-        }
1235
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1236
-        // ensure qty doesn't exceed raw value for THIS ticket
1237
-        $qty = min(EE_INF, $raw);
1238
-        // echo "\n . qty: " . $qty . '<br />';
1239
-        // calculate this ticket's total sales and reservations
1240
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1241
-        // echo "\n . sold: " . $this->sold() . '<br />';
1242
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1243
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1244
-        // first we need to calculate the maximum number of tickets available for the datetime
1245
-        // do we want data for one datetime or all of them ?
1246
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1247
-        $datetimes    = $this->datetimes($query_params);
1248
-        if (is_array($datetimes) && ! empty($datetimes)) {
1249
-            foreach ($datetimes as $datetime) {
1250
-                if ($datetime instanceof EE_Datetime) {
1251
-                    $datetime->refresh_from_db();
1252
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1253
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1254
-                    // initialize with no restrictions for each datetime
1255
-                    // but adjust datetime qty based on datetime reg limit
1256
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1257
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1258
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1259
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1260
-                    // and reservations for this datetime, that do NOT include sales and reservations
1261
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1262
-                    if ($context === 'saleable') {
1263
-                        $datetime_qty = max(
1264
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1265
-                            0
1266
-                        );
1267
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1268
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1269
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1270
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1271
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1272
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1273
-                    }
1274
-                    $qty = min($datetime_qty, $qty);
1275
-                    // echo "\n . . qty: " . $qty . '<br />';
1276
-                }
1277
-            }
1278
-        }
1279
-        // NOW that we know the  maximum number of tickets available for the datetime
1280
-        // we can finally factor in the details for this specific ticket
1281
-        if ($qty > 0 && $context === 'saleable') {
1282
-            // and subtract the sales for THIS ticket
1283
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1284
-            // echo "\n . qty: " . $qty . '<br />';
1285
-        }
1286
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1287
-        return $qty;
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1293
-     *
1294
-     * @param int $qty
1295
-     * @return void
1296
-     * @throws EE_Error
1297
-     * @throws ReflectionException
1298
-     */
1299
-    public function set_qty($qty)
1300
-    {
1301
-        $datetimes = $this->datetimes();
1302
-        foreach ($datetimes as $datetime) {
1303
-            if ($datetime instanceof EE_Datetime) {
1304
-                $qty = min($qty, $datetime->reg_limit());
1305
-            }
1306
-        }
1307
-        $this->set('TKT_qty', $qty);
1308
-    }
1309
-
1310
-
1311
-    /**
1312
-     * Gets uses
1313
-     *
1314
-     * @return int
1315
-     * @throws EE_Error
1316
-     * @throws ReflectionException
1317
-     */
1318
-    public function uses()
1319
-    {
1320
-        return $this->get('TKT_uses');
1321
-    }
1322
-
1323
-
1324
-    /**
1325
-     * Sets uses
1326
-     *
1327
-     * @param int $uses
1328
-     * @return void
1329
-     * @throws EE_Error
1330
-     * @throws ReflectionException
1331
-     */
1332
-    public function set_uses($uses)
1333
-    {
1334
-        $this->set('TKT_uses', $uses);
1335
-    }
1336
-
1337
-
1338
-    /**
1339
-     * returns whether ticket is required or not.
1340
-     *
1341
-     * @return boolean
1342
-     * @throws EE_Error
1343
-     * @throws ReflectionException
1344
-     */
1345
-    public function required()
1346
-    {
1347
-        return $this->get('TKT_required');
1348
-    }
1349
-
1350
-
1351
-    /**
1352
-     * sets the TKT_required property
1353
-     *
1354
-     * @param boolean $required
1355
-     * @return void
1356
-     * @throws EE_Error
1357
-     * @throws ReflectionException
1358
-     */
1359
-    public function set_required($required)
1360
-    {
1361
-        $this->set('TKT_required', $required);
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * Gets taxable
1367
-     *
1368
-     * @return boolean
1369
-     * @throws EE_Error
1370
-     * @throws ReflectionException
1371
-     */
1372
-    public function taxable()
1373
-    {
1374
-        return $this->get('TKT_taxable');
1375
-    }
1376
-
1377
-
1378
-    /**
1379
-     * Sets taxable
1380
-     *
1381
-     * @param boolean $taxable
1382
-     * @return void
1383
-     * @throws EE_Error
1384
-     * @throws ReflectionException
1385
-     */
1386
-    public function set_taxable($taxable)
1387
-    {
1388
-        $this->set('TKT_taxable', $taxable);
1389
-    }
1390
-
1391
-
1392
-    /**
1393
-     * Gets is_default
1394
-     *
1395
-     * @return boolean
1396
-     * @throws EE_Error
1397
-     * @throws ReflectionException
1398
-     */
1399
-    public function is_default()
1400
-    {
1401
-        return $this->get('TKT_is_default');
1402
-    }
1403
-
1404
-
1405
-    /**
1406
-     * Sets is_default
1407
-     *
1408
-     * @param boolean $is_default
1409
-     * @return void
1410
-     * @throws EE_Error
1411
-     * @throws ReflectionException
1412
-     */
1413
-    public function set_is_default($is_default)
1414
-    {
1415
-        $this->set('TKT_is_default', $is_default);
1416
-    }
1417
-
1418
-
1419
-    /**
1420
-     * Gets order
1421
-     *
1422
-     * @return int
1423
-     * @throws EE_Error
1424
-     * @throws ReflectionException
1425
-     */
1426
-    public function order()
1427
-    {
1428
-        return $this->get('TKT_order');
1429
-    }
1430
-
1431
-
1432
-    /**
1433
-     * Sets order
1434
-     *
1435
-     * @param int $order
1436
-     * @return void
1437
-     * @throws EE_Error
1438
-     * @throws ReflectionException
1439
-     */
1440
-    public function set_order($order)
1441
-    {
1442
-        $this->set('TKT_order', $order);
1443
-    }
1444
-
1445
-
1446
-    /**
1447
-     * Gets row
1448
-     *
1449
-     * @return int
1450
-     * @throws EE_Error
1451
-     * @throws ReflectionException
1452
-     */
1453
-    public function row()
1454
-    {
1455
-        return $this->get('TKT_row');
1456
-    }
1457
-
1458
-
1459
-    /**
1460
-     * Sets row
1461
-     *
1462
-     * @param int $row
1463
-     * @return void
1464
-     * @throws EE_Error
1465
-     * @throws ReflectionException
1466
-     */
1467
-    public function set_row($row)
1468
-    {
1469
-        $this->set('TKT_row', $row);
1470
-    }
1471
-
1472
-
1473
-    /**
1474
-     * Gets deleted
1475
-     *
1476
-     * @return boolean
1477
-     * @throws EE_Error
1478
-     * @throws ReflectionException
1479
-     */
1480
-    public function deleted()
1481
-    {
1482
-        return $this->get('TKT_deleted');
1483
-    }
1484
-
1485
-
1486
-    /**
1487
-     * Sets deleted
1488
-     *
1489
-     * @param boolean $deleted
1490
-     * @return void
1491
-     * @throws EE_Error
1492
-     * @throws ReflectionException
1493
-     */
1494
-    public function set_deleted($deleted)
1495
-    {
1496
-        $this->set('TKT_deleted', $deleted);
1497
-    }
1498
-
1499
-
1500
-    /**
1501
-     * Gets parent
1502
-     *
1503
-     * @return int
1504
-     * @throws EE_Error
1505
-     * @throws ReflectionException
1506
-     */
1507
-    public function parent_ID()
1508
-    {
1509
-        return $this->get('TKT_parent');
1510
-    }
1511
-
1512
-
1513
-    /**
1514
-     * Sets parent
1515
-     *
1516
-     * @param int $parent
1517
-     * @return void
1518
-     * @throws EE_Error
1519
-     * @throws ReflectionException
1520
-     */
1521
-    public function set_parent_ID($parent)
1522
-    {
1523
-        $this->set('TKT_parent', $parent);
1524
-    }
1525
-
1526
-
1527
-    /**
1528
-     * @return boolean
1529
-     * @throws EE_Error
1530
-     * @throws InvalidArgumentException
1531
-     * @throws InvalidDataTypeException
1532
-     * @throws InvalidInterfaceException
1533
-     * @throws ReflectionException
1534
-     */
1535
-    public function reverse_calculate()
1536
-    {
1537
-        return $this->get('TKT_reverse_calculate');
1538
-    }
1539
-
1540
-
1541
-    /**
1542
-     * @param boolean $reverse_calculate
1543
-     * @throws EE_Error
1544
-     * @throws InvalidArgumentException
1545
-     * @throws InvalidDataTypeException
1546
-     * @throws InvalidInterfaceException
1547
-     * @throws ReflectionException
1548
-     */
1549
-    public function set_reverse_calculate($reverse_calculate)
1550
-    {
1551
-        $this->set('TKT_reverse_calculate', $reverse_calculate);
1552
-    }
1553
-
1554
-
1555
-    /**
1556
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1557
-     *
1558
-     * @return string
1559
-     * @throws EE_Error
1560
-     * @throws ReflectionException
1561
-     */
1562
-    public function name_and_info()
1563
-    {
1564
-        $times = [];
1565
-        foreach ($this->datetimes() as $datetime) {
1566
-            $times[] = $datetime->start_date_and_time();
1567
-        }
1568
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1569
-    }
1570
-
1571
-
1572
-    /**
1573
-     * Gets name
1574
-     *
1575
-     * @return string
1576
-     * @throws EE_Error
1577
-     * @throws ReflectionException
1578
-     */
1579
-    public function name()
1580
-    {
1581
-        return $this->get('TKT_name');
1582
-    }
1583
-
1584
-
1585
-    /**
1586
-     * Gets price
1587
-     *
1588
-     * @return float
1589
-     * @throws EE_Error
1590
-     * @throws ReflectionException
1591
-     */
1592
-    public function price()
1593
-    {
1594
-        return $this->get('TKT_price');
1595
-    }
1596
-
1597
-
1598
-    /**
1599
-     * Gets all the registrations for this ticket
1600
-     *
1601
-     * @param array $query_params
1602
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1603
-     * @return EE_Registration[]|EE_Base_Class[]
1604
-     * @throws EE_Error
1605
-     * @throws ReflectionException
1606
-     */
1607
-    public function registrations($query_params = [])
1608
-    {
1609
-        return $this->get_many_related('Registration', $query_params);
1610
-    }
1611
-
1612
-
1613
-    /**
1614
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1615
-     *
1616
-     * @return int
1617
-     * @throws EE_Error
1618
-     * @throws ReflectionException
1619
-     */
1620
-    public function update_tickets_sold()
1621
-    {
1622
-        $count_regs_for_this_ticket = $this->count_registrations(
1623
-            [
1624
-                [
1625
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1626
-                    'REG_deleted' => 0,
1627
-                ],
1628
-            ]
1629
-        );
1630
-        $this->set_sold($count_regs_for_this_ticket);
1631
-        $this->save();
1632
-        return $count_regs_for_this_ticket;
1633
-    }
1634
-
1635
-
1636
-    /**
1637
-     * Counts the registrations for this ticket
1638
-     *
1639
-     * @param array $query_params
1640
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1641
-     * @return int
1642
-     * @throws EE_Error
1643
-     * @throws ReflectionException
1644
-     */
1645
-    public function count_registrations($query_params = [])
1646
-    {
1647
-        return $this->count_related('Registration', $query_params);
1648
-    }
1649
-
1650
-
1651
-    /**
1652
-     * Implementation for EEI_Has_Icon interface method.
1653
-     *
1654
-     * @return string
1655
-     * @see EEI_Visual_Representation for comments
1656
-     */
1657
-    public function get_icon()
1658
-    {
1659
-        return '<span class="dashicons dashicons-tickets-alt"/>';
1660
-    }
1661
-
1662
-
1663
-    /**
1664
-     * Implementation of the EEI_Event_Relation interface method
1665
-     *
1666
-     * @return EE_Event
1667
-     * @throws EE_Error
1668
-     * @throws UnexpectedEntityException
1669
-     * @throws ReflectionException
1670
-     * @see EEI_Event_Relation for comments
1671
-     */
1672
-    public function get_related_event()
1673
-    {
1674
-        // get one datetime to use for getting the event
1675
-        $datetime = $this->first_datetime();
1676
-        if (! $datetime instanceof EE_Datetime) {
1677
-            throw new UnexpectedEntityException(
1678
-                $datetime,
1679
-                'EE_Datetime',
1680
-                sprintf(
1681
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1682
-                    $this->name()
1683
-                )
1684
-            );
1685
-        }
1686
-        $event = $datetime->event();
1687
-        if (! $event instanceof EE_Event) {
1688
-            throw new UnexpectedEntityException(
1689
-                $event,
1690
-                'EE_Event',
1691
-                sprintf(
1692
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1693
-                    $this->name()
1694
-                )
1695
-            );
1696
-        }
1697
-        return $event;
1698
-    }
1699
-
1700
-
1701
-    /**
1702
-     * Implementation of the EEI_Event_Relation interface method
1703
-     *
1704
-     * @return string
1705
-     * @throws UnexpectedEntityException
1706
-     * @throws EE_Error
1707
-     * @throws ReflectionException
1708
-     * @see EEI_Event_Relation for comments
1709
-     */
1710
-    public function get_event_name()
1711
-    {
1712
-        $event = $this->get_related_event();
1713
-        return $event instanceof EE_Event ? $event->name() : '';
1714
-    }
1715
-
1716
-
1717
-    /**
1718
-     * Implementation of the EEI_Event_Relation interface method
1719
-     *
1720
-     * @return int
1721
-     * @throws UnexpectedEntityException
1722
-     * @throws EE_Error
1723
-     * @throws ReflectionException
1724
-     * @see EEI_Event_Relation for comments
1725
-     */
1726
-    public function get_event_ID()
1727
-    {
1728
-        $event = $this->get_related_event();
1729
-        return $event instanceof EE_Event ? $event->ID() : 0;
1730
-    }
1731
-
1732
-
1733
-    /**
1734
-     * This simply returns whether a ticket can be permanently deleted or not.
1735
-     * The criteria for determining this is whether the ticket has any related registrations.
1736
-     * If there are none then it can be permanently deleted.
1737
-     *
1738
-     * @return bool
1739
-     * @throws EE_Error
1740
-     * @throws ReflectionException
1741
-     */
1742
-    public function is_permanently_deleteable()
1743
-    {
1744
-        return $this->count_registrations() === 0;
1745
-    }
1746
-
1747
-
1748
-    /**
1749
-     * @return int
1750
-     * @throws EE_Error
1751
-     * @throws ReflectionException
1752
-     * @since   $VID:$
1753
-     */
1754
-    public function visibility(): int
1755
-    {
1756
-        return $this->get('TKT_visibility');
1757
-    }
1758
-
1759
-
1760
-    /**
1761
-     * @return int
1762
-     * @throws EE_Error
1763
-     * @throws ReflectionException
1764
-     * @since   $VID:$
1765
-     */
1766
-    public function isHidden(): int
1767
-    {
1768
-        return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1769
-    }
1770
-
1771
-
1772
-    /**
1773
-     * @return int
1774
-     * @throws EE_Error
1775
-     * @throws ReflectionException
1776
-     * @since   $VID:$
1777
-     */
1778
-    public function isNotHidden(): int
1779
-    {
1780
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1781
-    }
1782
-
1783
-
1784
-    /**
1785
-     * @return int
1786
-     * @throws EE_Error
1787
-     * @throws ReflectionException
1788
-     * @since   $VID:$
1789
-     */
1790
-    public function isPublicOnly(): int
1791
-    {
1792
-        return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1793
-    }
1794
-
1795
-
1796
-    /**
1797
-     * @return int
1798
-     * @throws EE_Error
1799
-     * @throws ReflectionException
1800
-     * @since   $VID:$
1801
-     */
1802
-    public function isMembersOnly(): int
1803
-    {
1804
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1805
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1806
-    }
1807
-
1808
-
1809
-    /**
1810
-     * @return int
1811
-     * @throws EE_Error
1812
-     * @throws ReflectionException
1813
-     * @since   $VID:$
1814
-     */
1815
-    public function isAdminsOnly(): int
1816
-    {
1817
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1818
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1819
-    }
1820
-
1821
-
1822
-    /**
1823
-     * @return int
1824
-     * @throws EE_Error
1825
-     * @throws ReflectionException
1826
-     * @since   $VID:$
1827
-     */
1828
-    public function isAdminUiOnly(): int
1829
-    {
1830
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1831
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1832
-    }
1833
-
1834
-
1835
-    /**
1836
-     * @param int $visibility
1837
-     * @throws EE_Error
1838
-     * @throws ReflectionException
1839
-     * @since   $VID:$
1840
-     */
1841
-    public function set_visibility(int $visibility)
1842
-    {
1843
-
1844
-        $ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1845
-        $ticket_visibility         = -1;
1846
-        foreach ($ticket_visibility_options as $ticket_visibility_option) {
1847
-            if ($visibility === $ticket_visibility_option) {
1848
-                $ticket_visibility = $visibility;
1849
-            }
1850
-        }
1851
-        if ($ticket_visibility === -1) {
1852
-            throw new DomainException(
1853
-                sprintf(
1854
-                    esc_html__(
1855
-                        'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1856
-                        'event_espresso'
1857
-                    ),
1858
-                    $visibility,
1859
-                    '<br />',
1860
-                    var_export($ticket_visibility_options, true)
1861
-                )
1862
-            );
1863
-        }
1864
-        $this->set('TKT_visibility', $ticket_visibility);
1865
-    }
1866
-
1867
-
1868
-    /*******************************************************************
19
+	/**
20
+	 * TicKet Archived:
21
+	 * constant used by ticket_status() to indicate that a ticket is archived
22
+	 * and no longer available for purchase
23
+	 */
24
+	const archived = 'TKA';
25
+
26
+	/**
27
+	 * TicKet Expired:
28
+	 * constant used by ticket_status() to indicate that a ticket is expired
29
+	 * and no longer available for purchase
30
+	 */
31
+	const expired = 'TKE';
32
+
33
+	/**
34
+	 * TicKet On sale:
35
+	 * constant used by ticket_status() to indicate that a ticket is On Sale
36
+	 * and IS available for purchase
37
+	 */
38
+	const onsale = 'TKO';
39
+
40
+	/**
41
+	 * TicKet Pending:
42
+	 * constant used by ticket_status() to indicate that a ticket is pending
43
+	 * and is NOT YET available for purchase
44
+	 */
45
+	const pending = 'TKP';
46
+
47
+	/**
48
+	 * TicKet Sold out:
49
+	 * constant used by ticket_status() to indicate that a ticket is sold out
50
+	 * and no longer available for purchases
51
+	 */
52
+	const sold_out = 'TKS';
53
+
54
+	/**
55
+	 * extra meta key for tracking ticket reservations
56
+	 *
57
+	 * @type string
58
+	 */
59
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
60
+
61
+	/**
62
+	 * override of parent property
63
+	 *
64
+	 * @var EEM_Ticket
65
+	 */
66
+	protected $_model;
67
+
68
+	/**
69
+	 * cached result from method of the same name
70
+	 *
71
+	 * @var float $_ticket_total_with_taxes
72
+	 */
73
+	private $_ticket_total_with_taxes;
74
+
75
+	/**
76
+	 * @var TicketPriceModifiers
77
+	 */
78
+	protected $ticket_price_modifiers;
79
+
80
+
81
+	/**
82
+	 * @param array  $props_n_values          incoming values
83
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
84
+	 *                                        used.)
85
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
86
+	 *                                        date_format and the second value is the time format
87
+	 * @return EE_Ticket
88
+	 * @throws EE_Error
89
+	 * @throws ReflectionException
90
+	 */
91
+	public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
92
+	{
93
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
94
+		return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
95
+	}
96
+
97
+
98
+	/**
99
+	 * @param array  $props_n_values  incoming values from the database
100
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
101
+	 *                                the website will be used.
102
+	 * @return EE_Ticket
103
+	 * @throws EE_Error
104
+	 * @throws ReflectionException
105
+	 */
106
+	public static function new_instance_from_db($props_n_values = [], $timezone = null)
107
+	{
108
+		return new self($props_n_values, true, $timezone);
109
+	}
110
+
111
+
112
+	/**
113
+	 * @param array  $fieldValues
114
+	 * @param false  $bydb
115
+	 * @param string $timezone
116
+	 * @param array  $date_formats
117
+	 * @throws EE_Error
118
+	 * @throws ReflectionException
119
+	 */
120
+	public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
121
+	{
122
+		parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
123
+		$this->ticket_price_modifiers = LoaderFactory::getNew(TicketPriceModifiers::class, [$this]);
124
+	}
125
+
126
+
127
+	/**
128
+	 * @return bool
129
+	 * @throws EE_Error
130
+	 * @throws ReflectionException
131
+	 */
132
+	public function parent()
133
+	{
134
+		return $this->get('TKT_parent');
135
+	}
136
+
137
+
138
+	/**
139
+	 * return if a ticket has quantities available for purchase
140
+	 *
141
+	 * @param int $DTT_ID the primary key for a particular datetime
142
+	 * @return boolean
143
+	 * @throws EE_Error
144
+	 * @throws ReflectionException
145
+	 */
146
+	public function available($DTT_ID = 0)
147
+	{
148
+		// are we checking availability for a particular datetime ?
149
+		if ($DTT_ID) {
150
+			// get that datetime object
151
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
152
+			// if  ticket sales for this datetime have exceeded the reg limit...
153
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
154
+				return false;
155
+			}
156
+		}
157
+		// datetime is still open for registration, but is this ticket sold out ?
158
+		return $this->qty() < 1 || $this->qty() > $this->sold();
159
+	}
160
+
161
+
162
+	/**
163
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
164
+	 *
165
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
166
+	 *                               relevant status const
167
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
168
+	 *                               further processing
169
+	 * @return mixed status int if the display string isn't requested
170
+	 * @throws EE_Error
171
+	 * @throws ReflectionException
172
+	 */
173
+	public function ticket_status($display = false, $remaining = null)
174
+	{
175
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
176
+		if (! $remaining) {
177
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
178
+		}
179
+		if ($this->get('TKT_deleted')) {
180
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
181
+		}
182
+		if ($this->is_expired()) {
183
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
184
+		}
185
+		if ($this->is_pending()) {
186
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
187
+		}
188
+		if ($this->is_on_sale()) {
189
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
190
+		}
191
+		return '';
192
+	}
193
+
194
+
195
+	/**
196
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
197
+	 * considering ALL the factors used for figuring that out.
198
+	 *
199
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
200
+	 * @return boolean         true = tickets remaining, false not.
201
+	 * @throws EE_Error
202
+	 * @throws ReflectionException
203
+	 */
204
+	public function is_remaining($DTT_ID = 0)
205
+	{
206
+		$num_remaining = $this->remaining($DTT_ID);
207
+		if ($num_remaining === 0) {
208
+			return false;
209
+		}
210
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
211
+			return false;
212
+		}
213
+		return true;
214
+	}
215
+
216
+
217
+	/**
218
+	 * return the total number of tickets available for purchase
219
+	 *
220
+	 * @param int $DTT_ID  the primary key for a particular datetime.
221
+	 *                     set to 0 for all related datetimes
222
+	 * @return int
223
+	 * @throws EE_Error
224
+	 * @throws ReflectionException
225
+	 */
226
+	public function remaining($DTT_ID = 0)
227
+	{
228
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
229
+	}
230
+
231
+
232
+	/**
233
+	 * Gets min
234
+	 *
235
+	 * @return int
236
+	 * @throws EE_Error
237
+	 * @throws ReflectionException
238
+	 */
239
+	public function min()
240
+	{
241
+		return $this->get('TKT_min');
242
+	}
243
+
244
+
245
+	/**
246
+	 * return if a ticket is no longer available cause its available dates have expired.
247
+	 *
248
+	 * @return boolean
249
+	 * @throws EE_Error
250
+	 * @throws ReflectionException
251
+	 */
252
+	public function is_expired()
253
+	{
254
+		return ($this->get_raw('TKT_end_date') < time());
255
+	}
256
+
257
+
258
+	/**
259
+	 * Return if a ticket is yet to go on sale or not
260
+	 *
261
+	 * @return boolean
262
+	 * @throws EE_Error
263
+	 * @throws ReflectionException
264
+	 */
265
+	public function is_pending()
266
+	{
267
+		return ($this->get_raw('TKT_start_date') >= time());
268
+	}
269
+
270
+
271
+	/**
272
+	 * Return if a ticket is on sale or not
273
+	 *
274
+	 * @return boolean
275
+	 * @throws EE_Error
276
+	 * @throws ReflectionException
277
+	 */
278
+	public function is_on_sale()
279
+	{
280
+		return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
281
+	}
282
+
283
+
284
+	/**
285
+	 * This returns the chronologically last datetime that this ticket is associated with
286
+	 *
287
+	 * @param string $date_format
288
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
289
+	 *                            the end date ie: Jan 01 "to" Dec 31
290
+	 * @return string
291
+	 * @throws EE_Error
292
+	 * @throws ReflectionException
293
+	 */
294
+	public function date_range($date_format = '', $conjunction = ' - ')
295
+	{
296
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
297
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
298
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
299
+			: '';
300
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
301
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
302
+			: '';
303
+
304
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
305
+	}
306
+
307
+
308
+	/**
309
+	 * This returns the chronologically first datetime that this ticket is associated with
310
+	 *
311
+	 * @return EE_Datetime
312
+	 * @throws EE_Error
313
+	 * @throws ReflectionException
314
+	 */
315
+	public function first_datetime()
316
+	{
317
+		$datetimes = $this->datetimes(['limit' => 1]);
318
+		return reset($datetimes);
319
+	}
320
+
321
+
322
+	/**
323
+	 * Gets all the datetimes this ticket can be used for attending.
324
+	 * Unless otherwise specified, orders datetimes by start date.
325
+	 *
326
+	 * @param array $query_params
327
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
328
+	 * @return EE_Datetime[]|EE_Base_Class[]
329
+	 * @throws EE_Error
330
+	 * @throws ReflectionException
331
+	 */
332
+	public function datetimes($query_params = [])
333
+	{
334
+		if (! isset($query_params['order_by'])) {
335
+			$query_params['order_by']['DTT_order'] = 'ASC';
336
+		}
337
+		return $this->get_many_related('Datetime', $query_params);
338
+	}
339
+
340
+
341
+	/**
342
+	 * This returns the chronologically last datetime that this ticket is associated with
343
+	 *
344
+	 * @return EE_Datetime
345
+	 * @throws EE_Error
346
+	 * @throws ReflectionException
347
+	 */
348
+	public function last_datetime()
349
+	{
350
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
351
+		return end($datetimes);
352
+	}
353
+
354
+
355
+	/**
356
+	 * This returns the total tickets sold depending on the given parameters.
357
+	 *
358
+	 * @param string $what    Can be one of two options: 'ticket', 'datetime'.
359
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
360
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
361
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
362
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
363
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
364
+	 * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
365
+	 * @return mixed (array|int)          how many tickets have sold
366
+	 * @throws EE_Error
367
+	 * @throws ReflectionException
368
+	 */
369
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
370
+	{
371
+		$total        = 0;
372
+		$tickets_sold = $this->_all_tickets_sold();
373
+		switch ($what) {
374
+			case 'ticket':
375
+				return $tickets_sold['ticket'];
376
+
377
+			case 'datetime':
378
+				if (empty($tickets_sold['datetime'])) {
379
+					return $total;
380
+				}
381
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
382
+					EE_Error::add_error(
383
+						__(
384
+							'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
385
+							'event_espresso'
386
+						),
387
+						__FILE__,
388
+						__FUNCTION__,
389
+						__LINE__
390
+					);
391
+					return $total;
392
+				}
393
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
394
+
395
+			default:
396
+				return $total;
397
+		}
398
+	}
399
+
400
+
401
+	/**
402
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
403
+	 *
404
+	 * @return EE_Ticket[]
405
+	 * @throws EE_Error
406
+	 * @throws ReflectionException
407
+	 */
408
+	protected function _all_tickets_sold()
409
+	{
410
+		$datetimes    = $this->get_many_related('Datetime');
411
+		$tickets_sold = [];
412
+		if (! empty($datetimes)) {
413
+			foreach ($datetimes as $datetime) {
414
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
415
+			}
416
+		}
417
+		// Tickets sold
418
+		$tickets_sold['ticket'] = $this->sold();
419
+		return $tickets_sold;
420
+	}
421
+
422
+
423
+	/**
424
+	 * This returns the base price object for the ticket.
425
+	 *
426
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
427
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
428
+	 * @throws EE_Error
429
+	 * @throws ReflectionException
430
+	 */
431
+	public function base_price(bool $return_array = false)
432
+	{
433
+		$base_price = $this->ticket_price_modifiers->getBasePrice();
434
+		if (! empty($base_price)) {
435
+			return $return_array ? $base_price : reset($base_price);
436
+		}
437
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
438
+		return $return_array
439
+			? $this->get_many_related('Price', [$_where])
440
+			: $this->get_first_related('Price', [$_where]);
441
+	}
442
+
443
+
444
+	/**
445
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
446
+	 *
447
+	 * @return EE_Price[]
448
+	 * @throws EE_Error
449
+	 * @throws ReflectionException
450
+	 */
451
+	public function price_modifiers(): array
452
+	{
453
+		$price_modifiers = $this->usesGlobalTaxes()
454
+			? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
455
+			: $this->ticket_price_modifiers ->getAllModifiersForTicket();
456
+		if (! empty($price_modifiers)) {
457
+			return $price_modifiers;
458
+		}
459
+		return $this->prices(
460
+			[
461
+				[
462
+					'Price_Type.PBT_ID' => [
463
+						'NOT IN',
464
+						[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
465
+					]
466
+				]
467
+			]
468
+		);
469
+	}
470
+
471
+
472
+	/**
473
+	 * This returns ONLY the TAX price modifiers for the ticket
474
+	 *
475
+	 * @return EE_Price[]
476
+	 * @throws EE_Error
477
+	 * @throws ReflectionException
478
+	 */
479
+	public function tax_price_modifiers(): array
480
+	{
481
+		$tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
482
+		if (! empty($tax_price_modifiers)) {
483
+			return $tax_price_modifiers;
484
+		}
485
+		return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
486
+	}
487
+
488
+
489
+	/**
490
+	 * Gets all the prices that combine to form the final price of this ticket
491
+	 *
492
+	 * @param array $query_params
493
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
494
+	 * @return EE_Price[]|EE_Base_Class[]
495
+	 * @throws EE_Error
496
+	 * @throws ReflectionException
497
+	 */
498
+	public function prices(array $query_params = []): array
499
+	{
500
+		if (! isset($query_params['order_by'])) {
501
+			$query_params['order_by']['PRC_order'] = 'ASC';
502
+		}
503
+		return $this->get_many_related('Price', $query_params);
504
+	}
505
+
506
+
507
+	/**
508
+	 * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
509
+	 *
510
+	 * @param array $query_params
511
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
512
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
513
+	 * @throws EE_Error
514
+	 * @throws ReflectionException
515
+	 */
516
+	public function datetime_tickets($query_params = [])
517
+	{
518
+		return $this->get_many_related('Datetime_Ticket', $query_params);
519
+	}
520
+
521
+
522
+	/**
523
+	 * Gets all the datetimes from the db ordered by DTT_order
524
+	 *
525
+	 * @param boolean $show_expired
526
+	 * @param boolean $show_deleted
527
+	 * @return EE_Datetime[]
528
+	 * @throws EE_Error
529
+	 * @throws ReflectionException
530
+	 */
531
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
532
+	{
533
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
534
+			$this->ID(),
535
+			$show_expired,
536
+			$show_deleted
537
+		);
538
+	}
539
+
540
+
541
+	/**
542
+	 * Gets ID
543
+	 *
544
+	 * @return int
545
+	 * @throws EE_Error
546
+	 * @throws ReflectionException
547
+	 */
548
+	public function ID()
549
+	{
550
+		return $this->get('TKT_ID');
551
+	}
552
+
553
+
554
+	/**
555
+	 * get the author of the ticket.
556
+	 *
557
+	 * @return int
558
+	 * @throws EE_Error
559
+	 * @throws ReflectionException
560
+	 * @since 4.5.0
561
+	 */
562
+	public function wp_user()
563
+	{
564
+		return $this->get('TKT_wp_user');
565
+	}
566
+
567
+
568
+	/**
569
+	 * Gets the template for the ticket
570
+	 *
571
+	 * @return EE_Ticket_Template|EE_Base_Class
572
+	 * @throws EE_Error
573
+	 * @throws ReflectionException
574
+	 */
575
+	public function template()
576
+	{
577
+		return $this->get_first_related('Ticket_Template');
578
+	}
579
+
580
+
581
+	/**
582
+	 * Simply returns an array of EE_Price objects that are taxes.
583
+	 *
584
+	 * @return EE_Price[]
585
+	 * @throws EE_Error
586
+	 * @throws ReflectionException
587
+	 */
588
+	public function get_ticket_taxes_for_admin(): array
589
+	{
590
+		return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
591
+	}
592
+
593
+
594
+	/**
595
+	 * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
596
+	 * as opposed to having tax price modifiers added directly to each ticket
597
+	 *
598
+	 * @return bool
599
+	 * @throws EE_Error
600
+	 * @throws ReflectionException
601
+	 * @since   $VID:$
602
+	 */
603
+	public function usesGlobalTaxes(): bool
604
+	{
605
+		return $this->taxable();
606
+	}
607
+
608
+
609
+	/**
610
+	 * @return float
611
+	 * @throws EE_Error
612
+	 * @throws ReflectionException
613
+	 */
614
+	public function ticket_price()
615
+	{
616
+		return $this->get('TKT_price');
617
+	}
618
+
619
+
620
+	/**
621
+	 * @return mixed
622
+	 * @throws EE_Error
623
+	 * @throws ReflectionException
624
+	 */
625
+	public function pretty_price()
626
+	{
627
+		return $this->get_pretty('TKT_price');
628
+	}
629
+
630
+
631
+	/**
632
+	 * @return bool
633
+	 * @throws EE_Error
634
+	 * @throws ReflectionException
635
+	 */
636
+	public function is_free()
637
+	{
638
+		return $this->get_ticket_total_with_taxes() === (float) 0;
639
+	}
640
+
641
+
642
+	/**
643
+	 * get_ticket_total_with_taxes
644
+	 *
645
+	 * @param bool $no_cache
646
+	 * @return float
647
+	 * @throws EE_Error
648
+	 * @throws ReflectionException
649
+	 */
650
+	public function get_ticket_total_with_taxes($no_cache = false)
651
+	{
652
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
653
+			$this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
654
+				? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
655
+				: $this->ticket_price();
656
+		}
657
+		return (float) $this->_ticket_total_with_taxes;
658
+	}
659
+
660
+
661
+	/**
662
+	 * @throws EE_Error
663
+	 * @throws ReflectionException
664
+	 */
665
+	public function ensure_TKT_Price_correct()
666
+	{
667
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
668
+		$this->save();
669
+	}
670
+
671
+
672
+	/**
673
+	 * @return float
674
+	 * @throws EE_Error
675
+	 * @throws ReflectionException
676
+	 */
677
+	public function get_ticket_subtotal()
678
+	{
679
+		return EE_Taxes::get_subtotal_for_admin($this);
680
+	}
681
+
682
+
683
+	/**
684
+	 * Returns the total taxes applied to this ticket
685
+	 *
686
+	 * @return float
687
+	 * @throws EE_Error
688
+	 * @throws ReflectionException
689
+	 */
690
+	public function get_ticket_taxes_total_for_admin()
691
+	{
692
+		return EE_Taxes::get_total_taxes_for_admin($this);
693
+	}
694
+
695
+
696
+	/**
697
+	 * Sets name
698
+	 *
699
+	 * @param string $name
700
+	 * @throws EE_Error
701
+	 * @throws ReflectionException
702
+	 */
703
+	public function set_name($name)
704
+	{
705
+		$this->set('TKT_name', $name);
706
+	}
707
+
708
+
709
+	/**
710
+	 * Gets description
711
+	 *
712
+	 * @return string
713
+	 * @throws EE_Error
714
+	 * @throws ReflectionException
715
+	 */
716
+	public function description()
717
+	{
718
+		return $this->get('TKT_description');
719
+	}
720
+
721
+
722
+	/**
723
+	 * Sets description
724
+	 *
725
+	 * @param string $description
726
+	 * @throws EE_Error
727
+	 * @throws ReflectionException
728
+	 */
729
+	public function set_description($description)
730
+	{
731
+		$this->set('TKT_description', $description);
732
+	}
733
+
734
+
735
+	/**
736
+	 * Gets start_date
737
+	 *
738
+	 * @param string $date_format
739
+	 * @param string $time_format
740
+	 * @return string
741
+	 * @throws EE_Error
742
+	 * @throws ReflectionException
743
+	 */
744
+	public function start_date($date_format = '', $time_format = '')
745
+	{
746
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
747
+	}
748
+
749
+
750
+	/**
751
+	 * Sets start_date
752
+	 *
753
+	 * @param string $start_date
754
+	 * @return void
755
+	 * @throws EE_Error
756
+	 * @throws ReflectionException
757
+	 */
758
+	public function set_start_date($start_date)
759
+	{
760
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
761
+	}
762
+
763
+
764
+	/**
765
+	 * Gets end_date
766
+	 *
767
+	 * @param string $date_format
768
+	 * @param string $time_format
769
+	 * @return string
770
+	 * @throws EE_Error
771
+	 * @throws ReflectionException
772
+	 */
773
+	public function end_date($date_format = '', $time_format = '')
774
+	{
775
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
776
+	}
777
+
778
+
779
+	/**
780
+	 * Sets end_date
781
+	 *
782
+	 * @param string $end_date
783
+	 * @return void
784
+	 * @throws EE_Error
785
+	 * @throws ReflectionException
786
+	 */
787
+	public function set_end_date($end_date)
788
+	{
789
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
790
+	}
791
+
792
+
793
+	/**
794
+	 * Sets sell until time
795
+	 *
796
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
797
+	 * @throws EE_Error
798
+	 * @throws ReflectionException
799
+	 * @since 4.5.0
800
+	 */
801
+	public function set_end_time($time)
802
+	{
803
+		$this->_set_time_for($time, 'TKT_end_date');
804
+	}
805
+
806
+
807
+	/**
808
+	 * Sets min
809
+	 *
810
+	 * @param int $min
811
+	 * @return void
812
+	 * @throws EE_Error
813
+	 * @throws ReflectionException
814
+	 */
815
+	public function set_min($min)
816
+	{
817
+		$this->set('TKT_min', $min);
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets max
823
+	 *
824
+	 * @return int
825
+	 * @throws EE_Error
826
+	 * @throws ReflectionException
827
+	 */
828
+	public function max()
829
+	{
830
+		return $this->get('TKT_max');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Sets max
836
+	 *
837
+	 * @param int $max
838
+	 * @return void
839
+	 * @throws EE_Error
840
+	 * @throws ReflectionException
841
+	 */
842
+	public function set_max($max)
843
+	{
844
+		$this->set('TKT_max', $max);
845
+	}
846
+
847
+
848
+	/**
849
+	 * Sets price
850
+	 *
851
+	 * @param float $price
852
+	 * @return void
853
+	 * @throws EE_Error
854
+	 * @throws ReflectionException
855
+	 */
856
+	public function set_price($price)
857
+	{
858
+		$this->set('TKT_price', $price);
859
+	}
860
+
861
+
862
+	/**
863
+	 * Gets sold
864
+	 *
865
+	 * @return int
866
+	 * @throws EE_Error
867
+	 * @throws ReflectionException
868
+	 */
869
+	public function sold()
870
+	{
871
+		return $this->get_raw('TKT_sold');
872
+	}
873
+
874
+
875
+	/**
876
+	 * Sets sold
877
+	 *
878
+	 * @param int $sold
879
+	 * @return void
880
+	 * @throws EE_Error
881
+	 * @throws ReflectionException
882
+	 */
883
+	public function set_sold($sold)
884
+	{
885
+		// sold can not go below zero
886
+		$sold = max(0, $sold);
887
+		$this->set('TKT_sold', $sold);
888
+	}
889
+
890
+
891
+	/**
892
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
893
+	 * associated datetimes.
894
+	 *
895
+	 * @param int $qty
896
+	 * @return boolean
897
+	 * @throws EE_Error
898
+	 * @throws InvalidArgumentException
899
+	 * @throws InvalidDataTypeException
900
+	 * @throws InvalidInterfaceException
901
+	 * @throws ReflectionException
902
+	 * @since 4.9.80.p
903
+	 */
904
+	public function increaseSold($qty = 1)
905
+	{
906
+		$qty = absint($qty);
907
+		// increment sold and decrement reserved datetime quantities simultaneously
908
+		// don't worry about failures, because they must have already had a spot reserved
909
+		$this->increaseSoldForDatetimes($qty);
910
+		// Increment and decrement ticket quantities simultaneously
911
+		$success = $this->adjustNumericFieldsInDb(
912
+			[
913
+				'TKT_reserved' => $qty * -1,
914
+				'TKT_sold'     => $qty,
915
+			]
916
+		);
917
+		do_action(
918
+			'AHEE__EE_Ticket__increase_sold',
919
+			$this,
920
+			$qty,
921
+			$this->sold(),
922
+			$success
923
+		);
924
+		return $success;
925
+	}
926
+
927
+
928
+	/**
929
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
930
+	 *
931
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
932
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
933
+	 * @param EE_Datetime[] $datetimes
934
+	 * @throws EE_Error
935
+	 * @throws InvalidArgumentException
936
+	 * @throws InvalidDataTypeException
937
+	 * @throws InvalidInterfaceException
938
+	 * @throws ReflectionException
939
+	 * @since 4.9.80.p
940
+	 */
941
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
942
+	{
943
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
944
+		foreach ($datetimes as $datetime) {
945
+			$datetime->increaseSold($qty);
946
+		}
947
+	}
948
+
949
+
950
+	/**
951
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
952
+	 * DB and then updates the model objects.
953
+	 * Does not affect the reserved counts.
954
+	 *
955
+	 * @param int $qty
956
+	 * @return boolean
957
+	 * @throws EE_Error
958
+	 * @throws InvalidArgumentException
959
+	 * @throws InvalidDataTypeException
960
+	 * @throws InvalidInterfaceException
961
+	 * @throws ReflectionException
962
+	 * @since 4.9.80.p
963
+	 */
964
+	public function decreaseSold($qty = 1)
965
+	{
966
+		$qty = absint($qty);
967
+		$this->decreaseSoldForDatetimes($qty);
968
+		$success = $this->adjustNumericFieldsInDb(
969
+			[
970
+				'TKT_sold' => $qty * -1,
971
+			]
972
+		);
973
+		do_action(
974
+			'AHEE__EE_Ticket__decrease_sold',
975
+			$this,
976
+			$qty,
977
+			$this->sold(),
978
+			$success
979
+		);
980
+		return $success;
981
+	}
982
+
983
+
984
+	/**
985
+	 * Decreases sold on related datetimes
986
+	 *
987
+	 * @param int           $qty
988
+	 * @param EE_Datetime[] $datetimes
989
+	 * @return void
990
+	 * @throws EE_Error
991
+	 * @throws InvalidArgumentException
992
+	 * @throws InvalidDataTypeException
993
+	 * @throws InvalidInterfaceException
994
+	 * @throws ReflectionException
995
+	 * @since 4.9.80.p
996
+	 */
997
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
998
+	{
999
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1000
+		if (is_array($datetimes)) {
1001
+			foreach ($datetimes as $datetime) {
1002
+				if ($datetime instanceof EE_Datetime) {
1003
+					$datetime->decreaseSold($qty);
1004
+				}
1005
+			}
1006
+		}
1007
+	}
1008
+
1009
+
1010
+	/**
1011
+	 * Gets qty of reserved tickets
1012
+	 *
1013
+	 * @return int
1014
+	 * @throws EE_Error
1015
+	 * @throws ReflectionException
1016
+	 */
1017
+	public function reserved()
1018
+	{
1019
+		return $this->get_raw('TKT_reserved');
1020
+	}
1021
+
1022
+
1023
+	/**
1024
+	 * Sets reserved
1025
+	 *
1026
+	 * @param int $reserved
1027
+	 * @return void
1028
+	 * @throws EE_Error
1029
+	 * @throws ReflectionException
1030
+	 */
1031
+	public function set_reserved($reserved)
1032
+	{
1033
+		// reserved can not go below zero
1034
+		$reserved = max(0, (int) $reserved);
1035
+		$this->set('TKT_reserved', $reserved);
1036
+	}
1037
+
1038
+
1039
+	/**
1040
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1041
+	 *
1042
+	 * @param int    $qty
1043
+	 * @param string $source
1044
+	 * @return bool whether we successfully reserved the ticket or not.
1045
+	 * @throws EE_Error
1046
+	 * @throws InvalidArgumentException
1047
+	 * @throws ReflectionException
1048
+	 * @throws InvalidDataTypeException
1049
+	 * @throws InvalidInterfaceException
1050
+	 * @since 4.9.80.p
1051
+	 */
1052
+	public function increaseReserved($qty = 1, $source = 'unknown')
1053
+	{
1054
+		$qty = absint($qty);
1055
+		do_action(
1056
+			'AHEE__EE_Ticket__increase_reserved__begin',
1057
+			$this,
1058
+			$qty,
1059
+			$source
1060
+		);
1061
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1062
+		$success                         = false;
1063
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1064
+		if ($datetimes_adjusted_successfully) {
1065
+			$success = $this->incrementFieldConditionallyInDb(
1066
+				'TKT_reserved',
1067
+				'TKT_sold',
1068
+				'TKT_qty',
1069
+				$qty
1070
+			);
1071
+			if (! $success) {
1072
+				// The datetimes were successfully bumped, but not the
1073
+				// ticket. So we need to manually rollback the datetimes.
1074
+				$this->decreaseReservedForDatetimes($qty);
1075
+			}
1076
+		}
1077
+		do_action(
1078
+			'AHEE__EE_Ticket__increase_reserved',
1079
+			$this,
1080
+			$qty,
1081
+			$this->reserved(),
1082
+			$success
1083
+		);
1084
+		return $success;
1085
+	}
1086
+
1087
+
1088
+	/**
1089
+	 * Increases reserved counts on related datetimes
1090
+	 *
1091
+	 * @param int           $qty
1092
+	 * @param EE_Datetime[] $datetimes
1093
+	 * @return boolean indicating success
1094
+	 * @throws EE_Error
1095
+	 * @throws InvalidArgumentException
1096
+	 * @throws InvalidDataTypeException
1097
+	 * @throws InvalidInterfaceException
1098
+	 * @throws ReflectionException
1099
+	 * @since 4.9.80.p
1100
+	 */
1101
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1102
+	{
1103
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1104
+		$datetimes_updated = [];
1105
+		$limit_exceeded    = false;
1106
+		if (is_array($datetimes)) {
1107
+			foreach ($datetimes as $datetime) {
1108
+				if ($datetime instanceof EE_Datetime) {
1109
+					if ($datetime->increaseReserved($qty)) {
1110
+						$datetimes_updated[] = $datetime;
1111
+					} else {
1112
+						$limit_exceeded = true;
1113
+						break;
1114
+					}
1115
+				}
1116
+			}
1117
+			// If somewhere along the way we detected a datetime whose
1118
+			// limit was exceeded, do a manual rollback.
1119
+			if ($limit_exceeded) {
1120
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1121
+				return false;
1122
+			}
1123
+		}
1124
+		return true;
1125
+	}
1126
+
1127
+
1128
+	/**
1129
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1130
+	 *
1131
+	 * @param int    $qty
1132
+	 * @param bool   $adjust_datetimes
1133
+	 * @param string $source
1134
+	 * @return boolean
1135
+	 * @throws EE_Error
1136
+	 * @throws InvalidArgumentException
1137
+	 * @throws ReflectionException
1138
+	 * @throws InvalidDataTypeException
1139
+	 * @throws InvalidInterfaceException
1140
+	 * @since 4.9.80.p
1141
+	 */
1142
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1143
+	{
1144
+		$qty = absint($qty);
1145
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1146
+		if ($adjust_datetimes) {
1147
+			$this->decreaseReservedForDatetimes($qty);
1148
+		}
1149
+		$success = $this->adjustNumericFieldsInDb(
1150
+			[
1151
+				'TKT_reserved' => $qty * -1,
1152
+			]
1153
+		);
1154
+		do_action(
1155
+			'AHEE__EE_Ticket__decrease_reserved',
1156
+			$this,
1157
+			$qty,
1158
+			$this->reserved(),
1159
+			$success
1160
+		);
1161
+		return $success;
1162
+	}
1163
+
1164
+
1165
+	/**
1166
+	 * Decreases the reserved count on the specified datetimes.
1167
+	 *
1168
+	 * @param int           $qty
1169
+	 * @param EE_Datetime[] $datetimes
1170
+	 * @throws EE_Error
1171
+	 * @throws InvalidArgumentException
1172
+	 * @throws ReflectionException
1173
+	 * @throws InvalidDataTypeException
1174
+	 * @throws InvalidInterfaceException
1175
+	 * @since 4.9.80.p
1176
+	 */
1177
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1178
+	{
1179
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1180
+		foreach ($datetimes as $datetime) {
1181
+			if ($datetime instanceof EE_Datetime) {
1182
+				$datetime->decreaseReserved($qty);
1183
+			}
1184
+		}
1185
+	}
1186
+
1187
+
1188
+	/**
1189
+	 * Gets ticket quantity
1190
+	 *
1191
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1192
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1193
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1194
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1195
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1196
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1197
+	 * @return int
1198
+	 * @throws EE_Error
1199
+	 * @throws ReflectionException
1200
+	 */
1201
+	public function qty($context = '')
1202
+	{
1203
+		switch ($context) {
1204
+			case 'reg_limit':
1205
+				return $this->real_quantity_on_ticket();
1206
+			case 'saleable':
1207
+				return $this->real_quantity_on_ticket('saleable');
1208
+			default:
1209
+				return $this->get_raw('TKT_qty');
1210
+		}
1211
+	}
1212
+
1213
+
1214
+	/**
1215
+	 * Gets ticket quantity
1216
+	 *
1217
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1218
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1219
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1220
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1221
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1222
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1223
+	 *                            set to 0 for all related datetimes
1224
+	 * @return int
1225
+	 * @throws EE_Error
1226
+	 * @throws ReflectionException
1227
+	 */
1228
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1229
+	{
1230
+		$raw = $this->get_raw('TKT_qty');
1231
+		// return immediately if it's zero
1232
+		if ($raw === 0) {
1233
+			return $raw;
1234
+		}
1235
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1236
+		// ensure qty doesn't exceed raw value for THIS ticket
1237
+		$qty = min(EE_INF, $raw);
1238
+		// echo "\n . qty: " . $qty . '<br />';
1239
+		// calculate this ticket's total sales and reservations
1240
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1241
+		// echo "\n . sold: " . $this->sold() . '<br />';
1242
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1243
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1244
+		// first we need to calculate the maximum number of tickets available for the datetime
1245
+		// do we want data for one datetime or all of them ?
1246
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1247
+		$datetimes    = $this->datetimes($query_params);
1248
+		if (is_array($datetimes) && ! empty($datetimes)) {
1249
+			foreach ($datetimes as $datetime) {
1250
+				if ($datetime instanceof EE_Datetime) {
1251
+					$datetime->refresh_from_db();
1252
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1253
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1254
+					// initialize with no restrictions for each datetime
1255
+					// but adjust datetime qty based on datetime reg limit
1256
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1257
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1258
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1259
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1260
+					// and reservations for this datetime, that do NOT include sales and reservations
1261
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1262
+					if ($context === 'saleable') {
1263
+						$datetime_qty = max(
1264
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1265
+							0
1266
+						);
1267
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1268
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1269
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1270
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1271
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1272
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1273
+					}
1274
+					$qty = min($datetime_qty, $qty);
1275
+					// echo "\n . . qty: " . $qty . '<br />';
1276
+				}
1277
+			}
1278
+		}
1279
+		// NOW that we know the  maximum number of tickets available for the datetime
1280
+		// we can finally factor in the details for this specific ticket
1281
+		if ($qty > 0 && $context === 'saleable') {
1282
+			// and subtract the sales for THIS ticket
1283
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1284
+			// echo "\n . qty: " . $qty . '<br />';
1285
+		}
1286
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1287
+		return $qty;
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1293
+	 *
1294
+	 * @param int $qty
1295
+	 * @return void
1296
+	 * @throws EE_Error
1297
+	 * @throws ReflectionException
1298
+	 */
1299
+	public function set_qty($qty)
1300
+	{
1301
+		$datetimes = $this->datetimes();
1302
+		foreach ($datetimes as $datetime) {
1303
+			if ($datetime instanceof EE_Datetime) {
1304
+				$qty = min($qty, $datetime->reg_limit());
1305
+			}
1306
+		}
1307
+		$this->set('TKT_qty', $qty);
1308
+	}
1309
+
1310
+
1311
+	/**
1312
+	 * Gets uses
1313
+	 *
1314
+	 * @return int
1315
+	 * @throws EE_Error
1316
+	 * @throws ReflectionException
1317
+	 */
1318
+	public function uses()
1319
+	{
1320
+		return $this->get('TKT_uses');
1321
+	}
1322
+
1323
+
1324
+	/**
1325
+	 * Sets uses
1326
+	 *
1327
+	 * @param int $uses
1328
+	 * @return void
1329
+	 * @throws EE_Error
1330
+	 * @throws ReflectionException
1331
+	 */
1332
+	public function set_uses($uses)
1333
+	{
1334
+		$this->set('TKT_uses', $uses);
1335
+	}
1336
+
1337
+
1338
+	/**
1339
+	 * returns whether ticket is required or not.
1340
+	 *
1341
+	 * @return boolean
1342
+	 * @throws EE_Error
1343
+	 * @throws ReflectionException
1344
+	 */
1345
+	public function required()
1346
+	{
1347
+		return $this->get('TKT_required');
1348
+	}
1349
+
1350
+
1351
+	/**
1352
+	 * sets the TKT_required property
1353
+	 *
1354
+	 * @param boolean $required
1355
+	 * @return void
1356
+	 * @throws EE_Error
1357
+	 * @throws ReflectionException
1358
+	 */
1359
+	public function set_required($required)
1360
+	{
1361
+		$this->set('TKT_required', $required);
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * Gets taxable
1367
+	 *
1368
+	 * @return boolean
1369
+	 * @throws EE_Error
1370
+	 * @throws ReflectionException
1371
+	 */
1372
+	public function taxable()
1373
+	{
1374
+		return $this->get('TKT_taxable');
1375
+	}
1376
+
1377
+
1378
+	/**
1379
+	 * Sets taxable
1380
+	 *
1381
+	 * @param boolean $taxable
1382
+	 * @return void
1383
+	 * @throws EE_Error
1384
+	 * @throws ReflectionException
1385
+	 */
1386
+	public function set_taxable($taxable)
1387
+	{
1388
+		$this->set('TKT_taxable', $taxable);
1389
+	}
1390
+
1391
+
1392
+	/**
1393
+	 * Gets is_default
1394
+	 *
1395
+	 * @return boolean
1396
+	 * @throws EE_Error
1397
+	 * @throws ReflectionException
1398
+	 */
1399
+	public function is_default()
1400
+	{
1401
+		return $this->get('TKT_is_default');
1402
+	}
1403
+
1404
+
1405
+	/**
1406
+	 * Sets is_default
1407
+	 *
1408
+	 * @param boolean $is_default
1409
+	 * @return void
1410
+	 * @throws EE_Error
1411
+	 * @throws ReflectionException
1412
+	 */
1413
+	public function set_is_default($is_default)
1414
+	{
1415
+		$this->set('TKT_is_default', $is_default);
1416
+	}
1417
+
1418
+
1419
+	/**
1420
+	 * Gets order
1421
+	 *
1422
+	 * @return int
1423
+	 * @throws EE_Error
1424
+	 * @throws ReflectionException
1425
+	 */
1426
+	public function order()
1427
+	{
1428
+		return $this->get('TKT_order');
1429
+	}
1430
+
1431
+
1432
+	/**
1433
+	 * Sets order
1434
+	 *
1435
+	 * @param int $order
1436
+	 * @return void
1437
+	 * @throws EE_Error
1438
+	 * @throws ReflectionException
1439
+	 */
1440
+	public function set_order($order)
1441
+	{
1442
+		$this->set('TKT_order', $order);
1443
+	}
1444
+
1445
+
1446
+	/**
1447
+	 * Gets row
1448
+	 *
1449
+	 * @return int
1450
+	 * @throws EE_Error
1451
+	 * @throws ReflectionException
1452
+	 */
1453
+	public function row()
1454
+	{
1455
+		return $this->get('TKT_row');
1456
+	}
1457
+
1458
+
1459
+	/**
1460
+	 * Sets row
1461
+	 *
1462
+	 * @param int $row
1463
+	 * @return void
1464
+	 * @throws EE_Error
1465
+	 * @throws ReflectionException
1466
+	 */
1467
+	public function set_row($row)
1468
+	{
1469
+		$this->set('TKT_row', $row);
1470
+	}
1471
+
1472
+
1473
+	/**
1474
+	 * Gets deleted
1475
+	 *
1476
+	 * @return boolean
1477
+	 * @throws EE_Error
1478
+	 * @throws ReflectionException
1479
+	 */
1480
+	public function deleted()
1481
+	{
1482
+		return $this->get('TKT_deleted');
1483
+	}
1484
+
1485
+
1486
+	/**
1487
+	 * Sets deleted
1488
+	 *
1489
+	 * @param boolean $deleted
1490
+	 * @return void
1491
+	 * @throws EE_Error
1492
+	 * @throws ReflectionException
1493
+	 */
1494
+	public function set_deleted($deleted)
1495
+	{
1496
+		$this->set('TKT_deleted', $deleted);
1497
+	}
1498
+
1499
+
1500
+	/**
1501
+	 * Gets parent
1502
+	 *
1503
+	 * @return int
1504
+	 * @throws EE_Error
1505
+	 * @throws ReflectionException
1506
+	 */
1507
+	public function parent_ID()
1508
+	{
1509
+		return $this->get('TKT_parent');
1510
+	}
1511
+
1512
+
1513
+	/**
1514
+	 * Sets parent
1515
+	 *
1516
+	 * @param int $parent
1517
+	 * @return void
1518
+	 * @throws EE_Error
1519
+	 * @throws ReflectionException
1520
+	 */
1521
+	public function set_parent_ID($parent)
1522
+	{
1523
+		$this->set('TKT_parent', $parent);
1524
+	}
1525
+
1526
+
1527
+	/**
1528
+	 * @return boolean
1529
+	 * @throws EE_Error
1530
+	 * @throws InvalidArgumentException
1531
+	 * @throws InvalidDataTypeException
1532
+	 * @throws InvalidInterfaceException
1533
+	 * @throws ReflectionException
1534
+	 */
1535
+	public function reverse_calculate()
1536
+	{
1537
+		return $this->get('TKT_reverse_calculate');
1538
+	}
1539
+
1540
+
1541
+	/**
1542
+	 * @param boolean $reverse_calculate
1543
+	 * @throws EE_Error
1544
+	 * @throws InvalidArgumentException
1545
+	 * @throws InvalidDataTypeException
1546
+	 * @throws InvalidInterfaceException
1547
+	 * @throws ReflectionException
1548
+	 */
1549
+	public function set_reverse_calculate($reverse_calculate)
1550
+	{
1551
+		$this->set('TKT_reverse_calculate', $reverse_calculate);
1552
+	}
1553
+
1554
+
1555
+	/**
1556
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1557
+	 *
1558
+	 * @return string
1559
+	 * @throws EE_Error
1560
+	 * @throws ReflectionException
1561
+	 */
1562
+	public function name_and_info()
1563
+	{
1564
+		$times = [];
1565
+		foreach ($this->datetimes() as $datetime) {
1566
+			$times[] = $datetime->start_date_and_time();
1567
+		}
1568
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1569
+	}
1570
+
1571
+
1572
+	/**
1573
+	 * Gets name
1574
+	 *
1575
+	 * @return string
1576
+	 * @throws EE_Error
1577
+	 * @throws ReflectionException
1578
+	 */
1579
+	public function name()
1580
+	{
1581
+		return $this->get('TKT_name');
1582
+	}
1583
+
1584
+
1585
+	/**
1586
+	 * Gets price
1587
+	 *
1588
+	 * @return float
1589
+	 * @throws EE_Error
1590
+	 * @throws ReflectionException
1591
+	 */
1592
+	public function price()
1593
+	{
1594
+		return $this->get('TKT_price');
1595
+	}
1596
+
1597
+
1598
+	/**
1599
+	 * Gets all the registrations for this ticket
1600
+	 *
1601
+	 * @param array $query_params
1602
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1603
+	 * @return EE_Registration[]|EE_Base_Class[]
1604
+	 * @throws EE_Error
1605
+	 * @throws ReflectionException
1606
+	 */
1607
+	public function registrations($query_params = [])
1608
+	{
1609
+		return $this->get_many_related('Registration', $query_params);
1610
+	}
1611
+
1612
+
1613
+	/**
1614
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1615
+	 *
1616
+	 * @return int
1617
+	 * @throws EE_Error
1618
+	 * @throws ReflectionException
1619
+	 */
1620
+	public function update_tickets_sold()
1621
+	{
1622
+		$count_regs_for_this_ticket = $this->count_registrations(
1623
+			[
1624
+				[
1625
+					'STS_ID'      => EEM_Registration::status_id_approved,
1626
+					'REG_deleted' => 0,
1627
+				],
1628
+			]
1629
+		);
1630
+		$this->set_sold($count_regs_for_this_ticket);
1631
+		$this->save();
1632
+		return $count_regs_for_this_ticket;
1633
+	}
1634
+
1635
+
1636
+	/**
1637
+	 * Counts the registrations for this ticket
1638
+	 *
1639
+	 * @param array $query_params
1640
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1641
+	 * @return int
1642
+	 * @throws EE_Error
1643
+	 * @throws ReflectionException
1644
+	 */
1645
+	public function count_registrations($query_params = [])
1646
+	{
1647
+		return $this->count_related('Registration', $query_params);
1648
+	}
1649
+
1650
+
1651
+	/**
1652
+	 * Implementation for EEI_Has_Icon interface method.
1653
+	 *
1654
+	 * @return string
1655
+	 * @see EEI_Visual_Representation for comments
1656
+	 */
1657
+	public function get_icon()
1658
+	{
1659
+		return '<span class="dashicons dashicons-tickets-alt"/>';
1660
+	}
1661
+
1662
+
1663
+	/**
1664
+	 * Implementation of the EEI_Event_Relation interface method
1665
+	 *
1666
+	 * @return EE_Event
1667
+	 * @throws EE_Error
1668
+	 * @throws UnexpectedEntityException
1669
+	 * @throws ReflectionException
1670
+	 * @see EEI_Event_Relation for comments
1671
+	 */
1672
+	public function get_related_event()
1673
+	{
1674
+		// get one datetime to use for getting the event
1675
+		$datetime = $this->first_datetime();
1676
+		if (! $datetime instanceof EE_Datetime) {
1677
+			throw new UnexpectedEntityException(
1678
+				$datetime,
1679
+				'EE_Datetime',
1680
+				sprintf(
1681
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1682
+					$this->name()
1683
+				)
1684
+			);
1685
+		}
1686
+		$event = $datetime->event();
1687
+		if (! $event instanceof EE_Event) {
1688
+			throw new UnexpectedEntityException(
1689
+				$event,
1690
+				'EE_Event',
1691
+				sprintf(
1692
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1693
+					$this->name()
1694
+				)
1695
+			);
1696
+		}
1697
+		return $event;
1698
+	}
1699
+
1700
+
1701
+	/**
1702
+	 * Implementation of the EEI_Event_Relation interface method
1703
+	 *
1704
+	 * @return string
1705
+	 * @throws UnexpectedEntityException
1706
+	 * @throws EE_Error
1707
+	 * @throws ReflectionException
1708
+	 * @see EEI_Event_Relation for comments
1709
+	 */
1710
+	public function get_event_name()
1711
+	{
1712
+		$event = $this->get_related_event();
1713
+		return $event instanceof EE_Event ? $event->name() : '';
1714
+	}
1715
+
1716
+
1717
+	/**
1718
+	 * Implementation of the EEI_Event_Relation interface method
1719
+	 *
1720
+	 * @return int
1721
+	 * @throws UnexpectedEntityException
1722
+	 * @throws EE_Error
1723
+	 * @throws ReflectionException
1724
+	 * @see EEI_Event_Relation for comments
1725
+	 */
1726
+	public function get_event_ID()
1727
+	{
1728
+		$event = $this->get_related_event();
1729
+		return $event instanceof EE_Event ? $event->ID() : 0;
1730
+	}
1731
+
1732
+
1733
+	/**
1734
+	 * This simply returns whether a ticket can be permanently deleted or not.
1735
+	 * The criteria for determining this is whether the ticket has any related registrations.
1736
+	 * If there are none then it can be permanently deleted.
1737
+	 *
1738
+	 * @return bool
1739
+	 * @throws EE_Error
1740
+	 * @throws ReflectionException
1741
+	 */
1742
+	public function is_permanently_deleteable()
1743
+	{
1744
+		return $this->count_registrations() === 0;
1745
+	}
1746
+
1747
+
1748
+	/**
1749
+	 * @return int
1750
+	 * @throws EE_Error
1751
+	 * @throws ReflectionException
1752
+	 * @since   $VID:$
1753
+	 */
1754
+	public function visibility(): int
1755
+	{
1756
+		return $this->get('TKT_visibility');
1757
+	}
1758
+
1759
+
1760
+	/**
1761
+	 * @return int
1762
+	 * @throws EE_Error
1763
+	 * @throws ReflectionException
1764
+	 * @since   $VID:$
1765
+	 */
1766
+	public function isHidden(): int
1767
+	{
1768
+		return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1769
+	}
1770
+
1771
+
1772
+	/**
1773
+	 * @return int
1774
+	 * @throws EE_Error
1775
+	 * @throws ReflectionException
1776
+	 * @since   $VID:$
1777
+	 */
1778
+	public function isNotHidden(): int
1779
+	{
1780
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1781
+	}
1782
+
1783
+
1784
+	/**
1785
+	 * @return int
1786
+	 * @throws EE_Error
1787
+	 * @throws ReflectionException
1788
+	 * @since   $VID:$
1789
+	 */
1790
+	public function isPublicOnly(): int
1791
+	{
1792
+		return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1793
+	}
1794
+
1795
+
1796
+	/**
1797
+	 * @return int
1798
+	 * @throws EE_Error
1799
+	 * @throws ReflectionException
1800
+	 * @since   $VID:$
1801
+	 */
1802
+	public function isMembersOnly(): int
1803
+	{
1804
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1805
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1806
+	}
1807
+
1808
+
1809
+	/**
1810
+	 * @return int
1811
+	 * @throws EE_Error
1812
+	 * @throws ReflectionException
1813
+	 * @since   $VID:$
1814
+	 */
1815
+	public function isAdminsOnly(): int
1816
+	{
1817
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1818
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1819
+	}
1820
+
1821
+
1822
+	/**
1823
+	 * @return int
1824
+	 * @throws EE_Error
1825
+	 * @throws ReflectionException
1826
+	 * @since   $VID:$
1827
+	 */
1828
+	public function isAdminUiOnly(): int
1829
+	{
1830
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1831
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1832
+	}
1833
+
1834
+
1835
+	/**
1836
+	 * @param int $visibility
1837
+	 * @throws EE_Error
1838
+	 * @throws ReflectionException
1839
+	 * @since   $VID:$
1840
+	 */
1841
+	public function set_visibility(int $visibility)
1842
+	{
1843
+
1844
+		$ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1845
+		$ticket_visibility         = -1;
1846
+		foreach ($ticket_visibility_options as $ticket_visibility_option) {
1847
+			if ($visibility === $ticket_visibility_option) {
1848
+				$ticket_visibility = $visibility;
1849
+			}
1850
+		}
1851
+		if ($ticket_visibility === -1) {
1852
+			throw new DomainException(
1853
+				sprintf(
1854
+					esc_html__(
1855
+						'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1856
+						'event_espresso'
1857
+					),
1858
+					$visibility,
1859
+					'<br />',
1860
+					var_export($ticket_visibility_options, true)
1861
+				)
1862
+			);
1863
+		}
1864
+		$this->set('TKT_visibility', $ticket_visibility);
1865
+	}
1866
+
1867
+
1868
+	/*******************************************************************
1869 1869
      ***********************  DEPRECATED METHODS  **********************
1870 1870
      *******************************************************************/
1871 1871
 
1872 1872
 
1873
-    /**
1874
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1875
-     * associated datetimes.
1876
-     *
1877
-     * @param int $qty
1878
-     * @return void
1879
-     * @throws EE_Error
1880
-     * @throws InvalidArgumentException
1881
-     * @throws InvalidDataTypeException
1882
-     * @throws InvalidInterfaceException
1883
-     * @throws ReflectionException
1884
-     * @deprecated 4.9.80.p
1885
-     */
1886
-    public function increase_sold($qty = 1)
1887
-    {
1888
-        EE_Error::doing_it_wrong(
1889
-            __FUNCTION__,
1890
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1891
-            '4.9.80.p',
1892
-            '5.0.0.p'
1893
-        );
1894
-        $this->increaseSold($qty);
1895
-    }
1896
-
1897
-
1898
-    /**
1899
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1900
-     *
1901
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1902
-     *                 Negative means to decreases old counts (and increase reserved counts).
1903
-     * @throws EE_Error
1904
-     * @throws InvalidArgumentException
1905
-     * @throws InvalidDataTypeException
1906
-     * @throws InvalidInterfaceException
1907
-     * @throws ReflectionException
1908
-     * @deprecated 4.9.80.p
1909
-     */
1910
-    protected function _increase_sold_for_datetimes($qty)
1911
-    {
1912
-        EE_Error::doing_it_wrong(
1913
-            __FUNCTION__,
1914
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1915
-            '4.9.80.p',
1916
-            '5.0.0.p'
1917
-        );
1918
-        $this->increaseSoldForDatetimes($qty);
1919
-    }
1920
-
1921
-
1922
-    /**
1923
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1924
-     * DB and then updates the model objects.
1925
-     * Does not affect the reserved counts.
1926
-     *
1927
-     * @param int $qty
1928
-     * @return void
1929
-     * @throws EE_Error
1930
-     * @throws InvalidArgumentException
1931
-     * @throws InvalidDataTypeException
1932
-     * @throws InvalidInterfaceException
1933
-     * @throws ReflectionException
1934
-     * @deprecated 4.9.80.p
1935
-     */
1936
-    public function decrease_sold($qty = 1)
1937
-    {
1938
-        EE_Error::doing_it_wrong(
1939
-            __FUNCTION__,
1940
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1941
-            '4.9.80.p',
1942
-            '5.0.0.p'
1943
-        );
1944
-        $this->decreaseSold($qty);
1945
-    }
1946
-
1947
-
1948
-    /**
1949
-     * Decreases sold on related datetimes
1950
-     *
1951
-     * @param int $qty
1952
-     * @return void
1953
-     * @throws EE_Error
1954
-     * @throws InvalidArgumentException
1955
-     * @throws InvalidDataTypeException
1956
-     * @throws InvalidInterfaceException
1957
-     * @throws ReflectionException
1958
-     * @deprecated 4.9.80.p
1959
-     */
1960
-    protected function _decrease_sold_for_datetimes($qty = 1)
1961
-    {
1962
-        EE_Error::doing_it_wrong(
1963
-            __FUNCTION__,
1964
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1965
-            '4.9.80.p',
1966
-            '5.0.0.p'
1967
-        );
1968
-        $this->decreaseSoldForDatetimes($qty);
1969
-    }
1970
-
1971
-
1972
-    /**
1973
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1974
-     *
1975
-     * @param int    $qty
1976
-     * @param string $source
1977
-     * @return bool whether we successfully reserved the ticket or not.
1978
-     * @throws EE_Error
1979
-     * @throws InvalidArgumentException
1980
-     * @throws ReflectionException
1981
-     * @throws InvalidDataTypeException
1982
-     * @throws InvalidInterfaceException
1983
-     * @deprecated 4.9.80.p
1984
-     */
1985
-    public function increase_reserved($qty = 1, $source = 'unknown')
1986
-    {
1987
-        EE_Error::doing_it_wrong(
1988
-            __FUNCTION__,
1989
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1990
-            '4.9.80.p',
1991
-            '5.0.0.p'
1992
-        );
1993
-        return $this->increaseReserved($qty);
1994
-    }
1995
-
1996
-
1997
-    /**
1998
-     * Increases sold on related datetimes
1999
-     *
2000
-     * @param int $qty
2001
-     * @return boolean indicating success
2002
-     * @throws EE_Error
2003
-     * @throws InvalidArgumentException
2004
-     * @throws InvalidDataTypeException
2005
-     * @throws InvalidInterfaceException
2006
-     * @throws ReflectionException
2007
-     * @deprecated 4.9.80.p
2008
-     */
2009
-    protected function _increase_reserved_for_datetimes($qty = 1)
2010
-    {
2011
-        EE_Error::doing_it_wrong(
2012
-            __FUNCTION__,
2013
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2014
-            '4.9.80.p',
2015
-            '5.0.0.p'
2016
-        );
2017
-        return $this->increaseReservedForDatetimes($qty);
2018
-    }
2019
-
2020
-
2021
-    /**
2022
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2023
-     *
2024
-     * @param int    $qty
2025
-     * @param bool   $adjust_datetimes
2026
-     * @param string $source
2027
-     * @return void
2028
-     * @throws EE_Error
2029
-     * @throws InvalidArgumentException
2030
-     * @throws ReflectionException
2031
-     * @throws InvalidDataTypeException
2032
-     * @throws InvalidInterfaceException
2033
-     * @deprecated 4.9.80.p
2034
-     */
2035
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2036
-    {
2037
-        EE_Error::doing_it_wrong(
2038
-            __FUNCTION__,
2039
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2040
-            '4.9.80.p',
2041
-            '5.0.0.p'
2042
-        );
2043
-        $this->decreaseReserved($qty);
2044
-    }
2045
-
2046
-
2047
-    /**
2048
-     * Decreases reserved on related datetimes
2049
-     *
2050
-     * @param int $qty
2051
-     * @return void
2052
-     * @throws EE_Error
2053
-     * @throws InvalidArgumentException
2054
-     * @throws ReflectionException
2055
-     * @throws InvalidDataTypeException
2056
-     * @throws InvalidInterfaceException
2057
-     * @deprecated 4.9.80.p
2058
-     */
2059
-    protected function _decrease_reserved_for_datetimes($qty = 1)
2060
-    {
2061
-        EE_Error::doing_it_wrong(
2062
-            __FUNCTION__,
2063
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2064
-            '4.9.80.p',
2065
-            '5.0.0.p'
2066
-        );
2067
-        $this->decreaseReservedForDatetimes($qty);
2068
-    }
1873
+	/**
1874
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1875
+	 * associated datetimes.
1876
+	 *
1877
+	 * @param int $qty
1878
+	 * @return void
1879
+	 * @throws EE_Error
1880
+	 * @throws InvalidArgumentException
1881
+	 * @throws InvalidDataTypeException
1882
+	 * @throws InvalidInterfaceException
1883
+	 * @throws ReflectionException
1884
+	 * @deprecated 4.9.80.p
1885
+	 */
1886
+	public function increase_sold($qty = 1)
1887
+	{
1888
+		EE_Error::doing_it_wrong(
1889
+			__FUNCTION__,
1890
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1891
+			'4.9.80.p',
1892
+			'5.0.0.p'
1893
+		);
1894
+		$this->increaseSold($qty);
1895
+	}
1896
+
1897
+
1898
+	/**
1899
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1900
+	 *
1901
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1902
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1903
+	 * @throws EE_Error
1904
+	 * @throws InvalidArgumentException
1905
+	 * @throws InvalidDataTypeException
1906
+	 * @throws InvalidInterfaceException
1907
+	 * @throws ReflectionException
1908
+	 * @deprecated 4.9.80.p
1909
+	 */
1910
+	protected function _increase_sold_for_datetimes($qty)
1911
+	{
1912
+		EE_Error::doing_it_wrong(
1913
+			__FUNCTION__,
1914
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1915
+			'4.9.80.p',
1916
+			'5.0.0.p'
1917
+		);
1918
+		$this->increaseSoldForDatetimes($qty);
1919
+	}
1920
+
1921
+
1922
+	/**
1923
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1924
+	 * DB and then updates the model objects.
1925
+	 * Does not affect the reserved counts.
1926
+	 *
1927
+	 * @param int $qty
1928
+	 * @return void
1929
+	 * @throws EE_Error
1930
+	 * @throws InvalidArgumentException
1931
+	 * @throws InvalidDataTypeException
1932
+	 * @throws InvalidInterfaceException
1933
+	 * @throws ReflectionException
1934
+	 * @deprecated 4.9.80.p
1935
+	 */
1936
+	public function decrease_sold($qty = 1)
1937
+	{
1938
+		EE_Error::doing_it_wrong(
1939
+			__FUNCTION__,
1940
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1941
+			'4.9.80.p',
1942
+			'5.0.0.p'
1943
+		);
1944
+		$this->decreaseSold($qty);
1945
+	}
1946
+
1947
+
1948
+	/**
1949
+	 * Decreases sold on related datetimes
1950
+	 *
1951
+	 * @param int $qty
1952
+	 * @return void
1953
+	 * @throws EE_Error
1954
+	 * @throws InvalidArgumentException
1955
+	 * @throws InvalidDataTypeException
1956
+	 * @throws InvalidInterfaceException
1957
+	 * @throws ReflectionException
1958
+	 * @deprecated 4.9.80.p
1959
+	 */
1960
+	protected function _decrease_sold_for_datetimes($qty = 1)
1961
+	{
1962
+		EE_Error::doing_it_wrong(
1963
+			__FUNCTION__,
1964
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1965
+			'4.9.80.p',
1966
+			'5.0.0.p'
1967
+		);
1968
+		$this->decreaseSoldForDatetimes($qty);
1969
+	}
1970
+
1971
+
1972
+	/**
1973
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1974
+	 *
1975
+	 * @param int    $qty
1976
+	 * @param string $source
1977
+	 * @return bool whether we successfully reserved the ticket or not.
1978
+	 * @throws EE_Error
1979
+	 * @throws InvalidArgumentException
1980
+	 * @throws ReflectionException
1981
+	 * @throws InvalidDataTypeException
1982
+	 * @throws InvalidInterfaceException
1983
+	 * @deprecated 4.9.80.p
1984
+	 */
1985
+	public function increase_reserved($qty = 1, $source = 'unknown')
1986
+	{
1987
+		EE_Error::doing_it_wrong(
1988
+			__FUNCTION__,
1989
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1990
+			'4.9.80.p',
1991
+			'5.0.0.p'
1992
+		);
1993
+		return $this->increaseReserved($qty);
1994
+	}
1995
+
1996
+
1997
+	/**
1998
+	 * Increases sold on related datetimes
1999
+	 *
2000
+	 * @param int $qty
2001
+	 * @return boolean indicating success
2002
+	 * @throws EE_Error
2003
+	 * @throws InvalidArgumentException
2004
+	 * @throws InvalidDataTypeException
2005
+	 * @throws InvalidInterfaceException
2006
+	 * @throws ReflectionException
2007
+	 * @deprecated 4.9.80.p
2008
+	 */
2009
+	protected function _increase_reserved_for_datetimes($qty = 1)
2010
+	{
2011
+		EE_Error::doing_it_wrong(
2012
+			__FUNCTION__,
2013
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2014
+			'4.9.80.p',
2015
+			'5.0.0.p'
2016
+		);
2017
+		return $this->increaseReservedForDatetimes($qty);
2018
+	}
2019
+
2020
+
2021
+	/**
2022
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2023
+	 *
2024
+	 * @param int    $qty
2025
+	 * @param bool   $adjust_datetimes
2026
+	 * @param string $source
2027
+	 * @return void
2028
+	 * @throws EE_Error
2029
+	 * @throws InvalidArgumentException
2030
+	 * @throws ReflectionException
2031
+	 * @throws InvalidDataTypeException
2032
+	 * @throws InvalidInterfaceException
2033
+	 * @deprecated 4.9.80.p
2034
+	 */
2035
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2036
+	{
2037
+		EE_Error::doing_it_wrong(
2038
+			__FUNCTION__,
2039
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2040
+			'4.9.80.p',
2041
+			'5.0.0.p'
2042
+		);
2043
+		$this->decreaseReserved($qty);
2044
+	}
2045
+
2046
+
2047
+	/**
2048
+	 * Decreases reserved on related datetimes
2049
+	 *
2050
+	 * @param int $qty
2051
+	 * @return void
2052
+	 * @throws EE_Error
2053
+	 * @throws InvalidArgumentException
2054
+	 * @throws ReflectionException
2055
+	 * @throws InvalidDataTypeException
2056
+	 * @throws InvalidInterfaceException
2057
+	 * @deprecated 4.9.80.p
2058
+	 */
2059
+	protected function _decrease_reserved_for_datetimes($qty = 1)
2060
+	{
2061
+		EE_Error::doing_it_wrong(
2062
+			__FUNCTION__,
2063
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2064
+			'4.9.80.p',
2065
+			'5.0.0.p'
2066
+		);
2067
+		$this->decreaseReservedForDatetimes($qty);
2068
+	}
2069 2069
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -173,7 +173,7 @@  discard block
 block discarded – undo
173 173
     public function ticket_status($display = false, $remaining = null)
174 174
     {
175 175
         $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
176
-        if (! $remaining) {
176
+        if ( ! $remaining) {
177 177
             return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
178 178
         }
179 179
         if ($this->get('TKT_deleted')) {
@@ -301,7 +301,7 @@  discard block
 block discarded – undo
301 301
             ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
302 302
             : '';
303 303
 
304
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
304
+        return $first_date && $last_date ? $first_date.$conjunction.$last_date : '';
305 305
     }
306 306
 
307 307
 
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
      */
332 332
     public function datetimes($query_params = [])
333 333
     {
334
-        if (! isset($query_params['order_by'])) {
334
+        if ( ! isset($query_params['order_by'])) {
335 335
             $query_params['order_by']['DTT_order'] = 'ASC';
336 336
         }
337 337
         return $this->get_many_related('Datetime', $query_params);
@@ -378,7 +378,7 @@  discard block
 block discarded – undo
378 378
                 if (empty($tickets_sold['datetime'])) {
379 379
                     return $total;
380 380
                 }
381
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
381
+                if ( ! empty($dtt_id) && ! isset($tickets_sold['datetime'][$dtt_id])) {
382 382
                     EE_Error::add_error(
383 383
                         __(
384 384
                             'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
@@ -390,7 +390,7 @@  discard block
 block discarded – undo
390 390
                     );
391 391
                     return $total;
392 392
                 }
393
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
393
+                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][$dtt_id];
394 394
 
395 395
             default:
396 396
                 return $total;
@@ -409,9 +409,9 @@  discard block
 block discarded – undo
409 409
     {
410 410
         $datetimes    = $this->get_many_related('Datetime');
411 411
         $tickets_sold = [];
412
-        if (! empty($datetimes)) {
412
+        if ( ! empty($datetimes)) {
413 413
             foreach ($datetimes as $datetime) {
414
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
414
+                $tickets_sold['datetime'][$datetime->ID()] = $datetime->get('DTT_sold');
415 415
             }
416 416
         }
417 417
         // Tickets sold
@@ -431,7 +431,7 @@  discard block
 block discarded – undo
431 431
     public function base_price(bool $return_array = false)
432 432
     {
433 433
         $base_price = $this->ticket_price_modifiers->getBasePrice();
434
-        if (! empty($base_price)) {
434
+        if ( ! empty($base_price)) {
435 435
             return $return_array ? $base_price : reset($base_price);
436 436
         }
437 437
         $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
         $price_modifiers = $this->usesGlobalTaxes()
454 454
             ? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
455 455
             : $this->ticket_price_modifiers ->getAllModifiersForTicket();
456
-        if (! empty($price_modifiers)) {
456
+        if ( ! empty($price_modifiers)) {
457 457
             return $price_modifiers;
458 458
         }
459 459
         return $this->prices(
@@ -479,7 +479,7 @@  discard block
 block discarded – undo
479 479
     public function tax_price_modifiers(): array
480 480
     {
481 481
         $tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
482
-        if (! empty($tax_price_modifiers)) {
482
+        if ( ! empty($tax_price_modifiers)) {
483 483
             return $tax_price_modifiers;
484 484
         }
485 485
         return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
@@ -497,7 +497,7 @@  discard block
 block discarded – undo
497 497
      */
498 498
     public function prices(array $query_params = []): array
499 499
     {
500
-        if (! isset($query_params['order_by'])) {
500
+        if ( ! isset($query_params['order_by'])) {
501 501
             $query_params['order_by']['PRC_order'] = 'ASC';
502 502
         }
503 503
         return $this->get_many_related('Price', $query_params);
@@ -1068,7 +1068,7 @@  discard block
 block discarded – undo
1068 1068
                 'TKT_qty',
1069 1069
                 $qty
1070 1070
             );
1071
-            if (! $success) {
1071
+            if ( ! $success) {
1072 1072
                 // The datetimes were successfully bumped, but not the
1073 1073
                 // ticket. So we need to manually rollback the datetimes.
1074 1074
                 $this->decreaseReservedForDatetimes($qty);
@@ -1565,7 +1565,7 @@  discard block
 block discarded – undo
1565 1565
         foreach ($this->datetimes() as $datetime) {
1566 1566
             $times[] = $datetime->start_date_and_time();
1567 1567
         }
1568
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1568
+        return $this->name().' @ '.implode(', ', $times).' for '.$this->pretty_price();
1569 1569
     }
1570 1570
 
1571 1571
 
@@ -1673,7 +1673,7 @@  discard block
 block discarded – undo
1673 1673
     {
1674 1674
         // get one datetime to use for getting the event
1675 1675
         $datetime = $this->first_datetime();
1676
-        if (! $datetime instanceof EE_Datetime) {
1676
+        if ( ! $datetime instanceof EE_Datetime) {
1677 1677
             throw new UnexpectedEntityException(
1678 1678
                 $datetime,
1679 1679
                 'EE_Datetime',
@@ -1684,7 +1684,7 @@  discard block
 block discarded – undo
1684 1684
             );
1685 1685
         }
1686 1686
         $event = $datetime->event();
1687
-        if (! $event instanceof EE_Event) {
1687
+        if ( ! $event instanceof EE_Event) {
1688 1688
             throw new UnexpectedEntityException(
1689 1689
                 $event,
1690 1690
                 'EE_Event',
Please login to merge, or discard this patch.
core/db_classes/EE_Taxes.class.php 2 patches
Indentation   +132 added lines, -132 removed lines patch added patch discarded remove patch
@@ -12,147 +12,147 @@
 block discarded – undo
12 12
 class EE_Taxes extends EE_Base
13 13
 {
14 14
 
15
-    /**
16
-     * This is used for when EE_Taxes is used statically by the admin
17
-     *
18
-     * @var array
19
-     */
20
-    private static $_subtotal = [];
15
+	/**
16
+	 * This is used for when EE_Taxes is used statically by the admin
17
+	 *
18
+	 * @var array
19
+	 */
20
+	private static $_subtotal = [];
21 21
 
22
-    /**
23
-     * This holds an array of EE_Price objects that are of PRT_ID == 4 (tax price types)
24
-     *
25
-     * @var EE_Price[]
26
-     */
27
-    private static $_default_taxes = [];
22
+	/**
23
+	 * This holds an array of EE_Price objects that are of PRT_ID == 4 (tax price types)
24
+	 *
25
+	 * @var EE_Price[]
26
+	 */
27
+	private static $_default_taxes = [];
28 28
 
29 29
 
30
-    /**
31
-     * This method simply calculates the total taxes for a given ticket (by pulling the prices attached to the ticket
32
-     * and applying default taxes to it). Note: this is just an intermediary helper method added to facilitate quick
33
-     * calc of taxes for tickets listed in the event editor.
34
-     *
35
-     * @param EE_Ticket $ticket incoming EE_Ticket
36
-     * @return float             total taxes to apply to ticket.
37
-     * @throws EE_Error
38
-     * @throws ReflectionException
39
-     */
40
-    public static function get_total_taxes_for_admin(EE_Ticket $ticket)
41
-    {
42
-        $tax = 0;
43
-        $total_tax = 0;
44
-        // This first checks to see if the given ticket is taxable.
45
-        if (! $ticket->taxable()) {
46
-            return $tax;
47
-        }
48
-        // get subtotal (notice we're only retrieving a subtotal if there isn't one given)
49
-        $subtotal = EE_Taxes::get_subtotal_for_admin($ticket);
50
-        // get taxes
51
-        $taxes = EE_Taxes::get_taxes_for_admin();
52
-        // apply taxes to subtotal
53
-        foreach ($taxes as $tax) {
54
-            // assuming taxes are not cumulative
55
-            $total_tax += $subtotal * $tax->amount() / 100;
56
-        }
57
-        return $total_tax;
58
-    }
30
+	/**
31
+	 * This method simply calculates the total taxes for a given ticket (by pulling the prices attached to the ticket
32
+	 * and applying default taxes to it). Note: this is just an intermediary helper method added to facilitate quick
33
+	 * calc of taxes for tickets listed in the event editor.
34
+	 *
35
+	 * @param EE_Ticket $ticket incoming EE_Ticket
36
+	 * @return float             total taxes to apply to ticket.
37
+	 * @throws EE_Error
38
+	 * @throws ReflectionException
39
+	 */
40
+	public static function get_total_taxes_for_admin(EE_Ticket $ticket)
41
+	{
42
+		$tax = 0;
43
+		$total_tax = 0;
44
+		// This first checks to see if the given ticket is taxable.
45
+		if (! $ticket->taxable()) {
46
+			return $tax;
47
+		}
48
+		// get subtotal (notice we're only retrieving a subtotal if there isn't one given)
49
+		$subtotal = EE_Taxes::get_subtotal_for_admin($ticket);
50
+		// get taxes
51
+		$taxes = EE_Taxes::get_taxes_for_admin();
52
+		// apply taxes to subtotal
53
+		foreach ($taxes as $tax) {
54
+			// assuming taxes are not cumulative
55
+			$total_tax += $subtotal * $tax->amount() / 100;
56
+		}
57
+		return $total_tax;
58
+	}
59 59
 
60 60
 
61
-    /**
62
-     * Gets the total percentage of tax that should be applied to taxable line items
63
-     *
64
-     * @return float the percentage of tax that should be added to taxable items
65
-     * @throws EE_Error
66
-     * @throws ReflectionException
67
-     * eg 20 for %20 tax (NOT 0.20, which
68
-     */
69
-    public static function get_total_taxes_percentage()
70
-    {
71
-        $total_tax_percent = 0;
72
-        foreach (EE_Taxes::get_taxes_for_admin() as $tax_price) {
73
-            $total_tax_percent += $tax_price->get('PRC_amount');
74
-        }
75
-        return $total_tax_percent;
76
-    }
61
+	/**
62
+	 * Gets the total percentage of tax that should be applied to taxable line items
63
+	 *
64
+	 * @return float the percentage of tax that should be added to taxable items
65
+	 * @throws EE_Error
66
+	 * @throws ReflectionException
67
+	 * eg 20 for %20 tax (NOT 0.20, which
68
+	 */
69
+	public static function get_total_taxes_percentage()
70
+	{
71
+		$total_tax_percent = 0;
72
+		foreach (EE_Taxes::get_taxes_for_admin() as $tax_price) {
73
+			$total_tax_percent += $tax_price->get('PRC_amount');
74
+		}
75
+		return $total_tax_percent;
76
+	}
77 77
 
78 78
 
79
-    /**
80
-     * @param EE_Ticket $ticket
81
-     * @return float
82
-     * @throws EE_Error
83
-     * @throws ReflectionException
84
-     */
85
-    public static function get_subtotal_for_admin(EE_Ticket $ticket)
86
-    {
87
-        $TKT_ID = $ticket->ID();
88
-        return EE_Taxes::$_subtotal[ $TKT_ID ] ?? EE_Taxes::_get_subtotal_for_admin($ticket);
89
-    }
79
+	/**
80
+	 * @param EE_Ticket $ticket
81
+	 * @return float
82
+	 * @throws EE_Error
83
+	 * @throws ReflectionException
84
+	 */
85
+	public static function get_subtotal_for_admin(EE_Ticket $ticket)
86
+	{
87
+		$TKT_ID = $ticket->ID();
88
+		return EE_Taxes::$_subtotal[ $TKT_ID ] ?? EE_Taxes::_get_subtotal_for_admin($ticket);
89
+	}
90 90
 
91 91
 
92
-    /**
93
-     * simply take an incoming ticket and calculate the subtotal for the ticket
94
-     *
95
-     * @param EE_Ticket $ticket
96
-     * @return float     subtotal calculated from all EE_Price[] on Ticket.
97
-     * @throws EE_Error
98
-     * @throws ReflectionException
99
-     */
100
-    private static function _get_subtotal_for_admin(EE_Ticket $ticket)
101
-    {
102
-        $subtotal = 0;
103
-        // get all prices
104
-        $prices = $ticket->get_many_related(
105
-            'Price',
106
-            [
107
-                0 => [
108
-                    'Price_Type.PBT_ID' => ['!=', EEM_Price_Type::base_type_tax],
109
-                ],
110
-                'default_where_conditions' => 'none',
111
-                'order_by'                 => ['PRC_order' => 'ASC'],
112
-            ]
113
-        );
114
-        // let's loop through them (base price is always the first item)
115
-        foreach ($prices as $price) {
116
-            if ($price instanceof EE_Price) {
117
-                $price_type = $price->type_obj();
118
-                if ($price_type instanceof EE_Price_Type) {
119
-                    switch ($price->type_obj()->base_type()) {
120
-                        case 1: // base price
121
-                        case 3: // surcharges
122
-                            $subtotal += $price->is_percent()
123
-                                ? $subtotal * $price->get('PRC_amount') / 100
124
-                                : $price->get('PRC_amount');
125
-                            break;
126
-                        case 2: // discounts
127
-                            $subtotal -= $price->is_percent()
128
-                                ? $subtotal * $price->get('PRC_amount') / 100
129
-                                : $price->get('PRC_amount');
130
-                            break;
131
-                    }
132
-                }
133
-            }
134
-        }
135
-        $TKT_ID = $ticket->ID();
136
-        EE_Taxes::$_subtotal[ $TKT_ID ] = $subtotal;
137
-        return $subtotal;
138
-    }
92
+	/**
93
+	 * simply take an incoming ticket and calculate the subtotal for the ticket
94
+	 *
95
+	 * @param EE_Ticket $ticket
96
+	 * @return float     subtotal calculated from all EE_Price[] on Ticket.
97
+	 * @throws EE_Error
98
+	 * @throws ReflectionException
99
+	 */
100
+	private static function _get_subtotal_for_admin(EE_Ticket $ticket)
101
+	{
102
+		$subtotal = 0;
103
+		// get all prices
104
+		$prices = $ticket->get_many_related(
105
+			'Price',
106
+			[
107
+				0 => [
108
+					'Price_Type.PBT_ID' => ['!=', EEM_Price_Type::base_type_tax],
109
+				],
110
+				'default_where_conditions' => 'none',
111
+				'order_by'                 => ['PRC_order' => 'ASC'],
112
+			]
113
+		);
114
+		// let's loop through them (base price is always the first item)
115
+		foreach ($prices as $price) {
116
+			if ($price instanceof EE_Price) {
117
+				$price_type = $price->type_obj();
118
+				if ($price_type instanceof EE_Price_Type) {
119
+					switch ($price->type_obj()->base_type()) {
120
+						case 1: // base price
121
+						case 3: // surcharges
122
+							$subtotal += $price->is_percent()
123
+								? $subtotal * $price->get('PRC_amount') / 100
124
+								: $price->get('PRC_amount');
125
+							break;
126
+						case 2: // discounts
127
+							$subtotal -= $price->is_percent()
128
+								? $subtotal * $price->get('PRC_amount') / 100
129
+								: $price->get('PRC_amount');
130
+							break;
131
+					}
132
+				}
133
+			}
134
+		}
135
+		$TKT_ID = $ticket->ID();
136
+		EE_Taxes::$_subtotal[ $TKT_ID ] = $subtotal;
137
+		return $subtotal;
138
+	}
139 139
 
140 140
 
141
-    /**
142
-     * get all default prices that are a Tax price type (PRT_ID = 4) and return
143
-     *
144
-     * @return EE_Price[] EE_Price objects that have PRT_ID == 4
145
-     * @throws EE_Error
146
-     */
147
-    public static function get_taxes_for_admin(): array
148
-    {
149
-        if (empty(EE_Taxes::$_default_taxes)) {
150
-            /** @var EEM_Price $price_model */
151
-            $price_model = LoaderFactory::getLoader()->getShared('EEM_Price');
152
-            EE_Taxes::$_default_taxes = $price_model->get_all(
153
-                [['PRC_is_default' => 1, 'Price_Type.PBT_ID' => 4]]
154
-            );
155
-        }
156
-        return EE_Taxes::$_default_taxes;
157
-    }
141
+	/**
142
+	 * get all default prices that are a Tax price type (PRT_ID = 4) and return
143
+	 *
144
+	 * @return EE_Price[] EE_Price objects that have PRT_ID == 4
145
+	 * @throws EE_Error
146
+	 */
147
+	public static function get_taxes_for_admin(): array
148
+	{
149
+		if (empty(EE_Taxes::$_default_taxes)) {
150
+			/** @var EEM_Price $price_model */
151
+			$price_model = LoaderFactory::getLoader()->getShared('EEM_Price');
152
+			EE_Taxes::$_default_taxes = $price_model->get_all(
153
+				[['PRC_is_default' => 1, 'Price_Type.PBT_ID' => 4]]
154
+			);
155
+		}
156
+		return EE_Taxes::$_default_taxes;
157
+	}
158 158
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -42,7 +42,7 @@  discard block
 block discarded – undo
42 42
         $tax = 0;
43 43
         $total_tax = 0;
44 44
         // This first checks to see if the given ticket is taxable.
45
-        if (! $ticket->taxable()) {
45
+        if ( ! $ticket->taxable()) {
46 46
             return $tax;
47 47
         }
48 48
         // get subtotal (notice we're only retrieving a subtotal if there isn't one given)
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
     public static function get_subtotal_for_admin(EE_Ticket $ticket)
86 86
     {
87 87
         $TKT_ID = $ticket->ID();
88
-        return EE_Taxes::$_subtotal[ $TKT_ID ] ?? EE_Taxes::_get_subtotal_for_admin($ticket);
88
+        return EE_Taxes::$_subtotal[$TKT_ID] ?? EE_Taxes::_get_subtotal_for_admin($ticket);
89 89
     }
90 90
 
91 91
 
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
             }
134 134
         }
135 135
         $TKT_ID = $ticket->ID();
136
-        EE_Taxes::$_subtotal[ $TKT_ID ] = $subtotal;
136
+        EE_Taxes::$_subtotal[$TKT_ID] = $subtotal;
137 137
         return $subtotal;
138 138
     }
139 139
 
Please login to merge, or discard this patch.
core/db_classes/EE_Line_Item.class.php 2 patches
Indentation   +1589 added lines, -1589 removed lines patch added patch discarded remove patch
@@ -16,1593 +16,1593 @@
 block discarded – undo
16 16
 class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
17 17
 {
18 18
 
19
-    /**
20
-     * for children line items (currently not a normal relation)
21
-     *
22
-     * @type EE_Line_Item[]
23
-     */
24
-    protected $_children = array();
25
-
26
-    /**
27
-     * for the parent line item
28
-     *
29
-     * @var EE_Line_Item
30
-     */
31
-    protected $_parent;
32
-
33
-    /**
34
-     * @var LineItemCalculator
35
-     */
36
-    protected $calculator;
37
-
38
-
39
-    /**
40
-     * @param array  $props_n_values          incoming values
41
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
-     *                                        used.)
43
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
-     *                                        date_format and the second value is the time format
45
-     * @return EE_Line_Item
46
-     * @throws EE_Error
47
-     * @throws InvalidArgumentException
48
-     * @throws InvalidDataTypeException
49
-     * @throws InvalidInterfaceException
50
-     * @throws ReflectionException
51
-     */
52
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
53
-    {
54
-        $has_object = parent::_check_for_object(
55
-            $props_n_values,
56
-            __CLASS__,
57
-            $timezone,
58
-            $date_formats
59
-        );
60
-        return $has_object
61
-            ? $has_object
62
-            : new self($props_n_values, false, $timezone);
63
-    }
64
-
65
-
66
-    /**
67
-     * @param array  $props_n_values  incoming values from the database
68
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
69
-     *                                the website will be used.
70
-     * @return EE_Line_Item
71
-     * @throws EE_Error
72
-     * @throws InvalidArgumentException
73
-     * @throws InvalidDataTypeException
74
-     * @throws InvalidInterfaceException
75
-     * @throws ReflectionException
76
-     */
77
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
-    {
79
-        return new self($props_n_values, true, $timezone);
80
-    }
81
-
82
-
83
-    /**
84
-     * Adds some defaults if they're not specified
85
-     *
86
-     * @param array  $fieldValues
87
-     * @param bool   $bydb
88
-     * @param string $timezone
89
-     * @throws EE_Error
90
-     * @throws InvalidArgumentException
91
-     * @throws InvalidDataTypeException
92
-     * @throws InvalidInterfaceException
93
-     * @throws ReflectionException
94
-     */
95
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
96
-    {
97
-        $this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
98
-        parent::__construct($fieldValues, $bydb, $timezone);
99
-        if (! $this->get('LIN_code')) {
100
-            $this->set_code($this->generate_code());
101
-        }
102
-    }
103
-
104
-
105
-    /**
106
-     * Gets ID
107
-     *
108
-     * @return int
109
-     * @throws EE_Error
110
-     * @throws InvalidArgumentException
111
-     * @throws InvalidDataTypeException
112
-     * @throws InvalidInterfaceException
113
-     * @throws ReflectionException
114
-     */
115
-    public function ID()
116
-    {
117
-        return $this->get('LIN_ID');
118
-    }
119
-
120
-
121
-    /**
122
-     * Gets TXN_ID
123
-     *
124
-     * @return int
125
-     * @throws EE_Error
126
-     * @throws InvalidArgumentException
127
-     * @throws InvalidDataTypeException
128
-     * @throws InvalidInterfaceException
129
-     * @throws ReflectionException
130
-     */
131
-    public function TXN_ID()
132
-    {
133
-        return $this->get('TXN_ID');
134
-    }
135
-
136
-
137
-    /**
138
-     * Sets TXN_ID
139
-     *
140
-     * @param int $TXN_ID
141
-     * @throws EE_Error
142
-     * @throws InvalidArgumentException
143
-     * @throws InvalidDataTypeException
144
-     * @throws InvalidInterfaceException
145
-     * @throws ReflectionException
146
-     */
147
-    public function set_TXN_ID($TXN_ID)
148
-    {
149
-        $this->set('TXN_ID', $TXN_ID);
150
-    }
151
-
152
-
153
-    /**
154
-     * Gets name
155
-     *
156
-     * @return string
157
-     * @throws EE_Error
158
-     * @throws InvalidArgumentException
159
-     * @throws InvalidDataTypeException
160
-     * @throws InvalidInterfaceException
161
-     * @throws ReflectionException
162
-     */
163
-    public function name()
164
-    {
165
-        $name = $this->get('LIN_name');
166
-        if (! $name) {
167
-            $name = ucwords(str_replace('-', ' ', $this->type()));
168
-        }
169
-        return $name;
170
-    }
171
-
172
-
173
-    /**
174
-     * Sets name
175
-     *
176
-     * @param string $name
177
-     * @throws EE_Error
178
-     * @throws InvalidArgumentException
179
-     * @throws InvalidDataTypeException
180
-     * @throws InvalidInterfaceException
181
-     * @throws ReflectionException
182
-     */
183
-    public function set_name($name)
184
-    {
185
-        $this->set('LIN_name', $name);
186
-    }
187
-
188
-
189
-    /**
190
-     * Gets desc
191
-     *
192
-     * @return string
193
-     * @throws EE_Error
194
-     * @throws InvalidArgumentException
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws ReflectionException
198
-     */
199
-    public function desc()
200
-    {
201
-        return $this->get('LIN_desc');
202
-    }
203
-
204
-
205
-    /**
206
-     * Sets desc
207
-     *
208
-     * @param string $desc
209
-     * @throws EE_Error
210
-     * @throws InvalidArgumentException
211
-     * @throws InvalidDataTypeException
212
-     * @throws InvalidInterfaceException
213
-     * @throws ReflectionException
214
-     */
215
-    public function set_desc($desc)
216
-    {
217
-        $this->set('LIN_desc', $desc);
218
-    }
219
-
220
-
221
-    /**
222
-     * Gets quantity
223
-     *
224
-     * @return int
225
-     * @throws EE_Error
226
-     * @throws InvalidArgumentException
227
-     * @throws InvalidDataTypeException
228
-     * @throws InvalidInterfaceException
229
-     * @throws ReflectionException
230
-     */
231
-    public function quantity(): int
232
-    {
233
-        return (int) $this->get('LIN_quantity');
234
-    }
235
-
236
-
237
-    /**
238
-     * Sets quantity
239
-     *
240
-     * @param int $quantity
241
-     * @throws EE_Error
242
-     * @throws InvalidArgumentException
243
-     * @throws InvalidDataTypeException
244
-     * @throws InvalidInterfaceException
245
-     * @throws ReflectionException
246
-     */
247
-    public function set_quantity($quantity)
248
-    {
249
-        $this->set('LIN_quantity', max($quantity, 0));
250
-    }
251
-
252
-
253
-    /**
254
-     * Gets item_id
255
-     *
256
-     * @return string
257
-     * @throws EE_Error
258
-     * @throws InvalidArgumentException
259
-     * @throws InvalidDataTypeException
260
-     * @throws InvalidInterfaceException
261
-     * @throws ReflectionException
262
-     */
263
-    public function OBJ_ID()
264
-    {
265
-        return $this->get('OBJ_ID');
266
-    }
267
-
268
-
269
-    /**
270
-     * Sets item_id
271
-     *
272
-     * @param string $item_id
273
-     * @throws EE_Error
274
-     * @throws InvalidArgumentException
275
-     * @throws InvalidDataTypeException
276
-     * @throws InvalidInterfaceException
277
-     * @throws ReflectionException
278
-     */
279
-    public function set_OBJ_ID($item_id)
280
-    {
281
-        $this->set('OBJ_ID', $item_id);
282
-    }
283
-
284
-
285
-    /**
286
-     * Gets item_type
287
-     *
288
-     * @return string
289
-     * @throws EE_Error
290
-     * @throws InvalidArgumentException
291
-     * @throws InvalidDataTypeException
292
-     * @throws InvalidInterfaceException
293
-     * @throws ReflectionException
294
-     */
295
-    public function OBJ_type()
296
-    {
297
-        return $this->get('OBJ_type');
298
-    }
299
-
300
-
301
-    /**
302
-     * Gets item_type
303
-     *
304
-     * @return string
305
-     * @throws EE_Error
306
-     * @throws InvalidArgumentException
307
-     * @throws InvalidDataTypeException
308
-     * @throws InvalidInterfaceException
309
-     * @throws ReflectionException
310
-     */
311
-    public function OBJ_type_i18n()
312
-    {
313
-        $obj_type = $this->OBJ_type();
314
-        switch ($obj_type) {
315
-            case EEM_Line_Item::OBJ_TYPE_EVENT:
316
-                $obj_type = esc_html__('Event', 'event_espresso');
317
-                break;
318
-            case EEM_Line_Item::OBJ_TYPE_PRICE:
319
-                $obj_type = esc_html__('Price', 'event_espresso');
320
-                break;
321
-            case EEM_Line_Item::OBJ_TYPE_PROMOTION:
322
-                $obj_type = esc_html__('Promotion', 'event_espresso');
323
-                break;
324
-            case EEM_Line_Item::OBJ_TYPE_TICKET:
325
-                $obj_type = esc_html__('Ticket', 'event_espresso');
326
-                break;
327
-            case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
328
-                $obj_type = esc_html__('Transaction', 'event_espresso');
329
-                break;
330
-        }
331
-        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
332
-    }
333
-
334
-
335
-    /**
336
-     * Sets item_type
337
-     *
338
-     * @param string $OBJ_type
339
-     * @throws EE_Error
340
-     * @throws InvalidArgumentException
341
-     * @throws InvalidDataTypeException
342
-     * @throws InvalidInterfaceException
343
-     * @throws ReflectionException
344
-     */
345
-    public function set_OBJ_type($OBJ_type)
346
-    {
347
-        $this->set('OBJ_type', $OBJ_type);
348
-    }
349
-
350
-
351
-    /**
352
-     * Gets unit_price
353
-     *
354
-     * @return float
355
-     * @throws EE_Error
356
-     * @throws InvalidArgumentException
357
-     * @throws InvalidDataTypeException
358
-     * @throws InvalidInterfaceException
359
-     * @throws ReflectionException
360
-     */
361
-    public function unit_price()
362
-    {
363
-        return $this->get('LIN_unit_price');
364
-    }
365
-
366
-
367
-    /**
368
-     * Sets unit_price
369
-     *
370
-     * @param float $unit_price
371
-     * @throws EE_Error
372
-     * @throws InvalidArgumentException
373
-     * @throws InvalidDataTypeException
374
-     * @throws InvalidInterfaceException
375
-     * @throws ReflectionException
376
-     */
377
-    public function set_unit_price($unit_price)
378
-    {
379
-        $this->set('LIN_unit_price', $unit_price);
380
-    }
381
-
382
-
383
-    /**
384
-     * Checks if this item is a percentage modifier or not
385
-     *
386
-     * @return boolean
387
-     * @throws EE_Error
388
-     * @throws InvalidArgumentException
389
-     * @throws InvalidDataTypeException
390
-     * @throws InvalidInterfaceException
391
-     * @throws ReflectionException
392
-     */
393
-    public function is_percent()
394
-    {
395
-        if ($this->is_tax_sub_total()) {
396
-            // tax subtotals HAVE a percent on them, that percentage only applies
397
-            // to taxable items, so its' an exception. Treat it like a flat line item
398
-            return false;
399
-        }
400
-        $unit_price = abs($this->get('LIN_unit_price'));
401
-        $percent = abs($this->get('LIN_percent'));
402
-        if ($unit_price < .001 && $percent) {
403
-            return true;
404
-        }
405
-        if ($unit_price >= .001 && ! $percent) {
406
-            return false;
407
-        }
408
-        if ($unit_price >= .001 && $percent) {
409
-            throw new EE_Error(
410
-                sprintf(
411
-                    esc_html__(
412
-                        'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
413
-                        'event_espresso'
414
-                    ),
415
-                    $unit_price,
416
-                    $percent
417
-                )
418
-            );
419
-        }
420
-        // if they're both 0, assume its not a percent item
421
-        return false;
422
-    }
423
-
424
-
425
-    /**
426
-     * Gets percent (between 100-.001)
427
-     *
428
-     * @return float
429
-     * @throws EE_Error
430
-     * @throws InvalidArgumentException
431
-     * @throws InvalidDataTypeException
432
-     * @throws InvalidInterfaceException
433
-     * @throws ReflectionException
434
-     */
435
-    public function percent()
436
-    {
437
-        return $this->get('LIN_percent');
438
-    }
439
-
440
-
441
-    /**
442
-     * Sets percent (between 100-0.01)
443
-     *
444
-     * @param float $percent
445
-     * @throws EE_Error
446
-     * @throws InvalidArgumentException
447
-     * @throws InvalidDataTypeException
448
-     * @throws InvalidInterfaceException
449
-     * @throws ReflectionException
450
-     */
451
-    public function set_percent($percent)
452
-    {
453
-        $this->set('LIN_percent', $percent);
454
-    }
455
-
456
-
457
-    /**
458
-     * Gets total
459
-     *
460
-     * @return float
461
-     * @throws EE_Error
462
-     * @throws InvalidArgumentException
463
-     * @throws InvalidDataTypeException
464
-     * @throws InvalidInterfaceException
465
-     * @throws ReflectionException
466
-     */
467
-    public function pretaxTotal(): float
468
-    {
469
-        return $this->get('LIN_pretax');
470
-    }
471
-
472
-
473
-    /**
474
-     * Sets total
475
-     *
476
-     * @param float $pretax_total
477
-     * @throws EE_Error
478
-     * @throws InvalidArgumentException
479
-     * @throws InvalidDataTypeException
480
-     * @throws InvalidInterfaceException
481
-     * @throws ReflectionException
482
-     */
483
-    public function setPretaxTotal(float $pretax_total)
484
-    {
485
-        $this->set('LIN_pretax', $pretax_total);
486
-    }
487
-
488
-
489
-    /**
490
-     * Gets total
491
-     *
492
-     * @return float
493
-     * @throws EE_Error
494
-     * @throws InvalidArgumentException
495
-     * @throws InvalidDataTypeException
496
-     * @throws InvalidInterfaceException
497
-     * @throws ReflectionException
498
-     */
499
-    public function total()
500
-    {
501
-        return $this->get('LIN_total');
502
-    }
503
-
504
-
505
-    /**
506
-     * Sets total
507
-     *
508
-     * @param float $total
509
-     * @throws EE_Error
510
-     * @throws InvalidArgumentException
511
-     * @throws InvalidDataTypeException
512
-     * @throws InvalidInterfaceException
513
-     * @throws ReflectionException
514
-     */
515
-    public function set_total($total)
516
-    {
517
-        $this->set('LIN_total', $total);
518
-    }
519
-
520
-
521
-    /**
522
-     * Gets order
523
-     *
524
-     * @return int
525
-     * @throws EE_Error
526
-     * @throws InvalidArgumentException
527
-     * @throws InvalidDataTypeException
528
-     * @throws InvalidInterfaceException
529
-     * @throws ReflectionException
530
-     */
531
-    public function order()
532
-    {
533
-        return $this->get('LIN_order');
534
-    }
535
-
536
-
537
-    /**
538
-     * Sets order
539
-     *
540
-     * @param int $order
541
-     * @throws EE_Error
542
-     * @throws InvalidArgumentException
543
-     * @throws InvalidDataTypeException
544
-     * @throws InvalidInterfaceException
545
-     * @throws ReflectionException
546
-     */
547
-    public function set_order($order)
548
-    {
549
-        $this->set('LIN_order', $order);
550
-    }
551
-
552
-
553
-    /**
554
-     * Gets parent
555
-     *
556
-     * @return int
557
-     * @throws EE_Error
558
-     * @throws InvalidArgumentException
559
-     * @throws InvalidDataTypeException
560
-     * @throws InvalidInterfaceException
561
-     * @throws ReflectionException
562
-     */
563
-    public function parent_ID()
564
-    {
565
-        return $this->get('LIN_parent');
566
-    }
567
-
568
-
569
-    /**
570
-     * Sets parent
571
-     *
572
-     * @param int $parent
573
-     * @throws EE_Error
574
-     * @throws InvalidArgumentException
575
-     * @throws InvalidDataTypeException
576
-     * @throws InvalidInterfaceException
577
-     * @throws ReflectionException
578
-     */
579
-    public function set_parent_ID($parent)
580
-    {
581
-        $this->set('LIN_parent', $parent);
582
-    }
583
-
584
-
585
-    /**
586
-     * Gets type
587
-     *
588
-     * @return string
589
-     * @throws EE_Error
590
-     * @throws InvalidArgumentException
591
-     * @throws InvalidDataTypeException
592
-     * @throws InvalidInterfaceException
593
-     * @throws ReflectionException
594
-     */
595
-    public function type()
596
-    {
597
-        return $this->get('LIN_type');
598
-    }
599
-
600
-
601
-    /**
602
-     * Sets type
603
-     *
604
-     * @param string $type
605
-     * @throws EE_Error
606
-     * @throws InvalidArgumentException
607
-     * @throws InvalidDataTypeException
608
-     * @throws InvalidInterfaceException
609
-     * @throws ReflectionException
610
-     */
611
-    public function set_type($type)
612
-    {
613
-        $this->set('LIN_type', $type);
614
-    }
615
-
616
-
617
-    /**
618
-     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
619
-     * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
620
-     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
621
-     * or indirectly by `EE_Line_item::add_child_line_item()`)
622
-     *
623
-     * @return EE_Base_Class|EE_Line_Item
624
-     * @throws EE_Error
625
-     * @throws InvalidArgumentException
626
-     * @throws InvalidDataTypeException
627
-     * @throws InvalidInterfaceException
628
-     * @throws ReflectionException
629
-     */
630
-    public function parent()
631
-    {
632
-        return $this->ID()
633
-            ? $this->get_model()->get_one_by_ID($this->parent_ID())
634
-            : $this->_parent;
635
-    }
636
-
637
-
638
-    /**
639
-     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
640
-     *
641
-     * @return EE_Base_Class[]|EE_Line_Item[]
642
-     * @throws EE_Error
643
-     * @throws InvalidArgumentException
644
-     * @throws InvalidDataTypeException
645
-     * @throws InvalidInterfaceException
646
-     * @throws ReflectionException
647
-     */
648
-    public function children()
649
-    {
650
-        if ($this->ID()) {
651
-            return $this->get_model()->get_all(
652
-                array(
653
-                    array('LIN_parent' => $this->ID()),
654
-                    'order_by' => array('LIN_order' => 'ASC'),
655
-                )
656
-            );
657
-        }
658
-        if (! is_array($this->_children)) {
659
-            $this->_children = array();
660
-        }
661
-        return $this->_children;
662
-    }
663
-
664
-
665
-    /**
666
-     * Gets code
667
-     *
668
-     * @return string
669
-     * @throws EE_Error
670
-     * @throws InvalidArgumentException
671
-     * @throws InvalidDataTypeException
672
-     * @throws InvalidInterfaceException
673
-     * @throws ReflectionException
674
-     */
675
-    public function code()
676
-    {
677
-        return $this->get('LIN_code');
678
-    }
679
-
680
-
681
-    /**
682
-     * Sets code
683
-     *
684
-     * @param string $code
685
-     * @throws EE_Error
686
-     * @throws InvalidArgumentException
687
-     * @throws InvalidDataTypeException
688
-     * @throws InvalidInterfaceException
689
-     * @throws ReflectionException
690
-     */
691
-    public function set_code($code)
692
-    {
693
-        $this->set('LIN_code', $code);
694
-    }
695
-
696
-
697
-    /**
698
-     * Gets is_taxable
699
-     *
700
-     * @return boolean
701
-     * @throws EE_Error
702
-     * @throws InvalidArgumentException
703
-     * @throws InvalidDataTypeException
704
-     * @throws InvalidInterfaceException
705
-     * @throws ReflectionException
706
-     */
707
-    public function is_taxable()
708
-    {
709
-        return $this->get('LIN_is_taxable');
710
-    }
711
-
712
-
713
-    /**
714
-     * Sets is_taxable
715
-     *
716
-     * @param boolean $is_taxable
717
-     * @throws EE_Error
718
-     * @throws InvalidArgumentException
719
-     * @throws InvalidDataTypeException
720
-     * @throws InvalidInterfaceException
721
-     * @throws ReflectionException
722
-     */
723
-    public function set_is_taxable($is_taxable)
724
-    {
725
-        $this->set('LIN_is_taxable', $is_taxable);
726
-    }
727
-
728
-
729
-    /**
730
-     * Gets the object that this model-joins-to.
731
-     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
732
-     * EEM_Promotion_Object
733
-     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
734
-     *
735
-     * @return EE_Base_Class | NULL
736
-     * @throws EE_Error
737
-     * @throws InvalidArgumentException
738
-     * @throws InvalidDataTypeException
739
-     * @throws InvalidInterfaceException
740
-     * @throws ReflectionException
741
-     */
742
-    public function get_object()
743
-    {
744
-        $model_name_of_related_obj = $this->OBJ_type();
745
-        return $this->get_model()->has_relation($model_name_of_related_obj)
746
-            ? $this->get_first_related($model_name_of_related_obj)
747
-            : null;
748
-    }
749
-
750
-
751
-    /**
752
-     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
753
-     * (IE, if this line item is for a price or something else, will return NULL)
754
-     *
755
-     * @param array $query_params
756
-     * @return EE_Base_Class|EE_Ticket
757
-     * @throws EE_Error
758
-     * @throws InvalidArgumentException
759
-     * @throws InvalidDataTypeException
760
-     * @throws InvalidInterfaceException
761
-     * @throws ReflectionException
762
-     */
763
-    public function ticket($query_params = array())
764
-    {
765
-        // we're going to assume that when this method is called
766
-        // we always want to receive the attached ticket EVEN if that ticket is archived.
767
-        // This can be overridden via the incoming $query_params argument
768
-        $remove_defaults = array('default_where_conditions' => 'none');
769
-        $query_params = array_merge($remove_defaults, $query_params);
770
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
771
-    }
772
-
773
-
774
-    /**
775
-     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
776
-     *
777
-     * @return EE_Datetime | NULL
778
-     * @throws EE_Error
779
-     * @throws InvalidArgumentException
780
-     * @throws InvalidDataTypeException
781
-     * @throws InvalidInterfaceException
782
-     * @throws ReflectionException
783
-     */
784
-    public function get_ticket_datetime()
785
-    {
786
-        if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
787
-            $ticket = $this->ticket();
788
-            if ($ticket instanceof EE_Ticket) {
789
-                $datetime = $ticket->first_datetime();
790
-                if ($datetime instanceof EE_Datetime) {
791
-                    return $datetime;
792
-                }
793
-            }
794
-        }
795
-        return null;
796
-    }
797
-
798
-
799
-    /**
800
-     * Gets the event's name that's related to the ticket, if this is for
801
-     * a ticket
802
-     *
803
-     * @return string
804
-     * @throws EE_Error
805
-     * @throws InvalidArgumentException
806
-     * @throws InvalidDataTypeException
807
-     * @throws InvalidInterfaceException
808
-     * @throws ReflectionException
809
-     */
810
-    public function ticket_event_name()
811
-    {
812
-        $event_name = esc_html__('Unknown', 'event_espresso');
813
-        $event = $this->ticket_event();
814
-        if ($event instanceof EE_Event) {
815
-            $event_name = $event->name();
816
-        }
817
-        return $event_name;
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets the event that's related to the ticket, if this line item represents a ticket.
823
-     *
824
-     * @return EE_Event|null
825
-     * @throws EE_Error
826
-     * @throws InvalidArgumentException
827
-     * @throws InvalidDataTypeException
828
-     * @throws InvalidInterfaceException
829
-     * @throws ReflectionException
830
-     */
831
-    public function ticket_event()
832
-    {
833
-        $event = null;
834
-        $ticket = $this->ticket();
835
-        if ($ticket instanceof EE_Ticket) {
836
-            $datetime = $ticket->first_datetime();
837
-            if ($datetime instanceof EE_Datetime) {
838
-                $event = $datetime->event();
839
-            }
840
-        }
841
-        return $event;
842
-    }
843
-
844
-
845
-    /**
846
-     * Gets the first datetime for this lien item, assuming it's for a ticket
847
-     *
848
-     * @param string $date_format
849
-     * @param string $time_format
850
-     * @return string
851
-     * @throws EE_Error
852
-     * @throws InvalidArgumentException
853
-     * @throws InvalidDataTypeException
854
-     * @throws InvalidInterfaceException
855
-     * @throws ReflectionException
856
-     */
857
-    public function ticket_datetime_start($date_format = '', $time_format = '')
858
-    {
859
-        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
860
-        $datetime = $this->get_ticket_datetime();
861
-        if ($datetime) {
862
-            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
863
-        }
864
-        return $first_datetime_string;
865
-    }
866
-
867
-
868
-    /**
869
-     * Adds the line item as a child to this line item. If there is another child line
870
-     * item with the same LIN_code, it is overwritten by this new one
871
-     *
872
-     * @param EE_Line_Item $line_item
873
-     * @param bool          $set_order
874
-     * @return bool success
875
-     * @throws EE_Error
876
-     * @throws InvalidArgumentException
877
-     * @throws InvalidDataTypeException
878
-     * @throws InvalidInterfaceException
879
-     * @throws ReflectionException
880
-     */
881
-    public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
882
-    {
883
-        // should we calculate the LIN_order for this line item ?
884
-        if ($set_order || $line_item->order() === null) {
885
-            $line_item->set_order(count($this->children()));
886
-        }
887
-        if ($this->ID()) {
888
-            // check for any duplicate line items (with the same code), if so, this replaces it
889
-            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
890
-            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
891
-                $this->delete_child_line_item($line_item_with_same_code->code());
892
-            }
893
-            $line_item->set_parent_ID($this->ID());
894
-            if ($this->TXN_ID()) {
895
-                $line_item->set_TXN_ID($this->TXN_ID());
896
-            }
897
-            return $line_item->save();
898
-        }
899
-        $this->_children[ $line_item->code() ] = $line_item;
900
-        if ($line_item->parent() !== $this) {
901
-            $line_item->set_parent($this);
902
-        }
903
-        return true;
904
-    }
905
-
906
-
907
-    /**
908
-     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
909
-     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
910
-     * However, if this line item is NOT saved to the DB, this just caches the parent on
911
-     * the EE_Line_Item::_parent property.
912
-     *
913
-     * @param EE_Line_Item $line_item
914
-     * @throws EE_Error
915
-     * @throws InvalidArgumentException
916
-     * @throws InvalidDataTypeException
917
-     * @throws InvalidInterfaceException
918
-     * @throws ReflectionException
919
-     */
920
-    public function set_parent($line_item)
921
-    {
922
-        if ($this->ID()) {
923
-            if (! $line_item->ID()) {
924
-                $line_item->save();
925
-            }
926
-            $this->set_parent_ID($line_item->ID());
927
-            $this->save();
928
-        } else {
929
-            $this->_parent = $line_item;
930
-            $this->set_parent_ID($line_item->ID());
931
-        }
932
-    }
933
-
934
-
935
-    /**
936
-     * Gets the child line item as specified by its code. Because this returns an object (by reference)
937
-     * you can modify this child line item and the parent (this object) can know about them
938
-     * because it also has a reference to that line item
939
-     *
940
-     * @param string $code
941
-     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
942
-     * @throws EE_Error
943
-     * @throws InvalidArgumentException
944
-     * @throws InvalidDataTypeException
945
-     * @throws InvalidInterfaceException
946
-     * @throws ReflectionException
947
-     */
948
-    public function get_child_line_item($code)
949
-    {
950
-        if ($this->ID()) {
951
-            return $this->get_model()->get_one(
952
-                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
953
-            );
954
-        }
955
-        return isset($this->_children[ $code ])
956
-            ? $this->_children[ $code ]
957
-            : null;
958
-    }
959
-
960
-
961
-    /**
962
-     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
963
-     * cached on it)
964
-     *
965
-     * @return int
966
-     * @throws EE_Error
967
-     * @throws InvalidArgumentException
968
-     * @throws InvalidDataTypeException
969
-     * @throws InvalidInterfaceException
970
-     * @throws ReflectionException
971
-     */
972
-    public function delete_children_line_items()
973
-    {
974
-        if ($this->ID()) {
975
-            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
976
-        }
977
-        $count = count($this->_children);
978
-        $this->_children = array();
979
-        return $count;
980
-    }
981
-
982
-
983
-    /**
984
-     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
985
-     * HAS NOT been saved to the DB, removes the child line item with index $code.
986
-     * Also searches through the child's children for a matching line item. However, once a line item has been found
987
-     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
988
-     * deleted)
989
-     *
990
-     * @param string $code
991
-     * @param bool   $stop_search_once_found
992
-     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
993
-     *             the DB yet)
994
-     * @throws EE_Error
995
-     * @throws InvalidArgumentException
996
-     * @throws InvalidDataTypeException
997
-     * @throws InvalidInterfaceException
998
-     * @throws ReflectionException
999
-     */
1000
-    public function delete_child_line_item($code, $stop_search_once_found = true)
1001
-    {
1002
-        if ($this->ID()) {
1003
-            $items_deleted = 0;
1004
-            if ($this->code() === $code) {
1005
-                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
1006
-                $items_deleted += (int) $this->delete();
1007
-                if ($stop_search_once_found) {
1008
-                    return $items_deleted;
1009
-                }
1010
-            }
1011
-            foreach ($this->children() as $child_line_item) {
1012
-                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1013
-            }
1014
-            return $items_deleted;
1015
-        }
1016
-        if (isset($this->_children[ $code ])) {
1017
-            unset($this->_children[ $code ]);
1018
-            return 1;
1019
-        }
1020
-        return 0;
1021
-    }
1022
-
1023
-
1024
-    /**
1025
-     * If this line item is in the database, is of the type subtotal, and
1026
-     * has no children, why do we have it? It should be deleted so this function
1027
-     * does that
1028
-     *
1029
-     * @return boolean
1030
-     * @throws EE_Error
1031
-     * @throws InvalidArgumentException
1032
-     * @throws InvalidDataTypeException
1033
-     * @throws InvalidInterfaceException
1034
-     * @throws ReflectionException
1035
-     */
1036
-    public function delete_if_childless_subtotal()
1037
-    {
1038
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1039
-            return $this->delete();
1040
-        }
1041
-        return false;
1042
-    }
1043
-
1044
-
1045
-    /**
1046
-     * Creates a code and returns a string. doesn't assign the code to this model object
1047
-     *
1048
-     * @return string
1049
-     * @throws EE_Error
1050
-     * @throws InvalidArgumentException
1051
-     * @throws InvalidDataTypeException
1052
-     * @throws InvalidInterfaceException
1053
-     * @throws ReflectionException
1054
-     */
1055
-    public function generate_code()
1056
-    {
1057
-        // each line item in the cart requires a unique identifier
1058
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * @return bool
1064
-     * @throws EE_Error
1065
-     * @throws InvalidArgumentException
1066
-     * @throws InvalidDataTypeException
1067
-     * @throws InvalidInterfaceException
1068
-     * @throws ReflectionException
1069
-     */
1070
-    public function isGlobalTax(): bool
1071
-    {
1072
-        return $this->type() === EEM_Line_Item::type_tax;
1073
-    }
1074
-
1075
-
1076
-    /**
1077
-     * @return bool
1078
-     * @throws EE_Error
1079
-     * @throws InvalidArgumentException
1080
-     * @throws InvalidDataTypeException
1081
-     * @throws InvalidInterfaceException
1082
-     * @throws ReflectionException
1083
-     */
1084
-    public function isSubTax(): bool
1085
-    {
1086
-        return $this->type() === EEM_Line_Item::type_sub_tax;
1087
-    }
1088
-
1089
-
1090
-    /**
1091
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1092
-     *
1093
-     * @return array
1094
-     * @throws EE_Error
1095
-     * @throws InvalidArgumentException
1096
-     * @throws InvalidDataTypeException
1097
-     * @throws InvalidInterfaceException
1098
-     * @throws ReflectionException
1099
-     */
1100
-    public function getSubTaxes(): array
1101
-    {
1102
-        if (! $this->is_line_item()) {
1103
-            return [];
1104
-        }
1105
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1111
-     *
1112
-     * @return bool
1113
-     * @throws EE_Error
1114
-     * @throws InvalidArgumentException
1115
-     * @throws InvalidDataTypeException
1116
-     * @throws InvalidInterfaceException
1117
-     * @throws ReflectionException
1118
-     */
1119
-    public function hasSubTaxes(): bool
1120
-    {
1121
-        if (! $this->is_line_item()) {
1122
-            return false;
1123
-        }
1124
-        $sub_taxes = $this->getSubTaxes();
1125
-        return ! empty($sub_taxes);
1126
-    }
1127
-
1128
-
1129
-    /**
1130
-     * @return bool
1131
-     * @throws EE_Error
1132
-     * @throws ReflectionException
1133
-     * @deprecated   $VID:$
1134
-     */
1135
-    public function is_tax(): bool
1136
-    {
1137
-        return $this->isGlobalTax();
1138
-    }
1139
-
1140
-
1141
-    /**
1142
-     * @return bool
1143
-     * @throws EE_Error
1144
-     * @throws InvalidArgumentException
1145
-     * @throws InvalidDataTypeException
1146
-     * @throws InvalidInterfaceException
1147
-     * @throws ReflectionException
1148
-     */
1149
-    public function is_tax_sub_total()
1150
-    {
1151
-        return $this->type() === EEM_Line_Item::type_tax_sub_total;
1152
-    }
1153
-
1154
-
1155
-    /**
1156
-     * @return bool
1157
-     * @throws EE_Error
1158
-     * @throws InvalidArgumentException
1159
-     * @throws InvalidDataTypeException
1160
-     * @throws InvalidInterfaceException
1161
-     * @throws ReflectionException
1162
-     */
1163
-    public function is_line_item()
1164
-    {
1165
-        return $this->type() === EEM_Line_Item::type_line_item;
1166
-    }
1167
-
1168
-
1169
-    /**
1170
-     * @return bool
1171
-     * @throws EE_Error
1172
-     * @throws InvalidArgumentException
1173
-     * @throws InvalidDataTypeException
1174
-     * @throws InvalidInterfaceException
1175
-     * @throws ReflectionException
1176
-     */
1177
-    public function is_sub_line_item()
1178
-    {
1179
-        return $this->type() === EEM_Line_Item::type_sub_line_item;
1180
-    }
1181
-
1182
-
1183
-    /**
1184
-     * @return bool
1185
-     * @throws EE_Error
1186
-     * @throws InvalidArgumentException
1187
-     * @throws InvalidDataTypeException
1188
-     * @throws InvalidInterfaceException
1189
-     * @throws ReflectionException
1190
-     */
1191
-    public function is_sub_total()
1192
-    {
1193
-        return $this->type() === EEM_Line_Item::type_sub_total;
1194
-    }
1195
-
1196
-
1197
-    /**
1198
-     * Whether or not this line item is a cancellation line item
1199
-     *
1200
-     * @return boolean
1201
-     * @throws EE_Error
1202
-     * @throws InvalidArgumentException
1203
-     * @throws InvalidDataTypeException
1204
-     * @throws InvalidInterfaceException
1205
-     * @throws ReflectionException
1206
-     */
1207
-    public function is_cancellation()
1208
-    {
1209
-        return EEM_Line_Item::type_cancellation === $this->type();
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * @return bool
1215
-     * @throws EE_Error
1216
-     * @throws InvalidArgumentException
1217
-     * @throws InvalidDataTypeException
1218
-     * @throws InvalidInterfaceException
1219
-     * @throws ReflectionException
1220
-     */
1221
-    public function is_total()
1222
-    {
1223
-        return $this->type() === EEM_Line_Item::type_total;
1224
-    }
1225
-
1226
-
1227
-    /**
1228
-     * @return bool
1229
-     * @throws EE_Error
1230
-     * @throws InvalidArgumentException
1231
-     * @throws InvalidDataTypeException
1232
-     * @throws InvalidInterfaceException
1233
-     * @throws ReflectionException
1234
-     */
1235
-    public function is_cancelled()
1236
-    {
1237
-        return $this->type() === EEM_Line_Item::type_cancellation;
1238
-    }
1239
-
1240
-
1241
-    /**
1242
-     * @return string like '2, 004.00', formatted according to the localized currency
1243
-     * @throws EE_Error
1244
-     * @throws InvalidArgumentException
1245
-     * @throws InvalidDataTypeException
1246
-     * @throws InvalidInterfaceException
1247
-     * @throws ReflectionException
1248
-     */
1249
-    public function unit_price_no_code()
1250
-    {
1251
-        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1252
-    }
1253
-
1254
-
1255
-    /**
1256
-     * @return string like '2, 004.00', formatted according to the localized currency
1257
-     * @throws EE_Error
1258
-     * @throws InvalidArgumentException
1259
-     * @throws InvalidDataTypeException
1260
-     * @throws InvalidInterfaceException
1261
-     * @throws ReflectionException
1262
-     */
1263
-    public function total_no_code()
1264
-    {
1265
-        return $this->get_pretty('LIN_total', 'no_currency_code');
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * Gets the final total on this item, taking taxes into account.
1271
-     * Has the side-effect of setting the sub-total as it was just calculated.
1272
-     * If this is used on a grand-total line item, also updates the transaction's
1273
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
1274
-     * want to change a persistable transaction with info from a non-persistent line item)
1275
-     *
1276
-     * @param bool $update_txn_status
1277
-     * @return float
1278
-     * @throws EE_Error
1279
-     * @throws InvalidArgumentException
1280
-     * @throws InvalidDataTypeException
1281
-     * @throws InvalidInterfaceException
1282
-     * @throws ReflectionException
1283
-     * @throws RuntimeException
1284
-     */
1285
-    public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1286
-    {
1287
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1288
-        return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1289
-    }
1290
-
1291
-
1292
-    /**
1293
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1294
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1295
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1296
-     * when this is called on the grand total
1297
-     *
1298
-     * @return float
1299
-     * @throws EE_Error
1300
-     * @throws InvalidArgumentException
1301
-     * @throws InvalidDataTypeException
1302
-     * @throws InvalidInterfaceException
1303
-     * @throws ReflectionException
1304
-     */
1305
-    public function recalculate_pre_tax_total(): float
1306
-    {
1307
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1308
-        [$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1309
-        return $total;
1310
-    }
1311
-
1312
-
1313
-    /**
1314
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1315
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1316
-     * and tax sub-total if already in the DB
1317
-     *
1318
-     * @return float
1319
-     * @throws EE_Error
1320
-     * @throws InvalidArgumentException
1321
-     * @throws InvalidDataTypeException
1322
-     * @throws InvalidInterfaceException
1323
-     * @throws ReflectionException
1324
-     */
1325
-    public function recalculate_taxes_and_tax_total(): float
1326
-    {
1327
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1328
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1329
-    }
1330
-
1331
-
1332
-    /**
1333
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1334
-     * recalculate_taxes_and_total
1335
-     *
1336
-     * @return float
1337
-     * @throws EE_Error
1338
-     * @throws InvalidArgumentException
1339
-     * @throws InvalidDataTypeException
1340
-     * @throws InvalidInterfaceException
1341
-     * @throws ReflectionException
1342
-     */
1343
-    public function get_total_tax()
1344
-    {
1345
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1346
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1347
-    }
1348
-
1349
-
1350
-    /**
1351
-     * Gets the total for all the items purchased only
1352
-     *
1353
-     * @return float
1354
-     * @throws EE_Error
1355
-     * @throws InvalidArgumentException
1356
-     * @throws InvalidDataTypeException
1357
-     * @throws InvalidInterfaceException
1358
-     * @throws ReflectionException
1359
-     */
1360
-    public function get_items_total()
1361
-    {
1362
-        // by default, let's make sure we're consistent with the existing line item
1363
-        if ($this->is_total()) {
1364
-            return $this->pretaxTotal();
1365
-        }
1366
-        $total = 0;
1367
-        foreach ($this->get_items() as $item) {
1368
-            if ($item instanceof EE_Line_Item) {
1369
-                $total += $item->pretaxTotal();
1370
-            }
1371
-        }
1372
-        return $total;
1373
-    }
1374
-
1375
-
1376
-    /**
1377
-     * Gets all the descendants (ie, children or children of children etc) that
1378
-     * are of the type 'tax'
1379
-     *
1380
-     * @return EE_Line_Item[]
1381
-     * @throws EE_Error
1382
-     */
1383
-    public function tax_descendants()
1384
-    {
1385
-        return EEH_Line_Item::get_tax_descendants($this);
1386
-    }
1387
-
1388
-
1389
-    /**
1390
-     * Gets all the real items purchased which are children of this item
1391
-     *
1392
-     * @return EE_Line_Item[]
1393
-     * @throws EE_Error
1394
-     */
1395
-    public function get_items()
1396
-    {
1397
-        return EEH_Line_Item::get_line_item_descendants($this);
1398
-    }
1399
-
1400
-
1401
-    /**
1402
-     * Returns the amount taxable among this line item's children (or if it has no children,
1403
-     * how much of it is taxable). Does not recalculate totals or subtotals.
1404
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1405
-     * but there is a "Taxable" discount), returns 0.
1406
-     *
1407
-     * @return float
1408
-     * @throws EE_Error
1409
-     * @throws InvalidArgumentException
1410
-     * @throws InvalidDataTypeException
1411
-     * @throws InvalidInterfaceException
1412
-     * @throws ReflectionException
1413
-     */
1414
-    public function taxable_total(): float
1415
-    {
1416
-        return $this->calculator->taxableAmountForGlobalTaxes($this);
1417
-    }
1418
-
1419
-
1420
-    /**
1421
-     * Gets the transaction for this line item
1422
-     *
1423
-     * @return EE_Base_Class|EE_Transaction
1424
-     * @throws EE_Error
1425
-     * @throws InvalidArgumentException
1426
-     * @throws InvalidDataTypeException
1427
-     * @throws InvalidInterfaceException
1428
-     * @throws ReflectionException
1429
-     */
1430
-    public function transaction()
1431
-    {
1432
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1433
-    }
1434
-
1435
-
1436
-    /**
1437
-     * Saves this line item to the DB, and recursively saves its descendants.
1438
-     * Because there currently is no proper parent-child relation on the model,
1439
-     * save_this_and_cached() will NOT save the descendants.
1440
-     * Also sets the transaction on this line item and all its descendants before saving
1441
-     *
1442
-     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1443
-     * @return int count of items saved
1444
-     * @throws EE_Error
1445
-     * @throws InvalidArgumentException
1446
-     * @throws InvalidDataTypeException
1447
-     * @throws InvalidInterfaceException
1448
-     * @throws ReflectionException
1449
-     */
1450
-    public function save_this_and_descendants_to_txn($txn_id = null)
1451
-    {
1452
-        $count = 0;
1453
-        if (! $txn_id) {
1454
-            $txn_id = $this->TXN_ID();
1455
-        }
1456
-        $this->set_TXN_ID($txn_id);
1457
-        $children = $this->children();
1458
-        $count += $this->save()
1459
-            ? 1
1460
-            : 0;
1461
-        foreach ($children as $child_line_item) {
1462
-            if ($child_line_item instanceof EE_Line_Item) {
1463
-                $child_line_item->set_parent_ID($this->ID());
1464
-                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1465
-            }
1466
-        }
1467
-        return $count;
1468
-    }
1469
-
1470
-
1471
-    /**
1472
-     * Saves this line item to the DB, and recursively saves its descendants.
1473
-     *
1474
-     * @return int count of items saved
1475
-     * @throws EE_Error
1476
-     * @throws InvalidArgumentException
1477
-     * @throws InvalidDataTypeException
1478
-     * @throws InvalidInterfaceException
1479
-     * @throws ReflectionException
1480
-     */
1481
-    public function save_this_and_descendants()
1482
-    {
1483
-        $count = 0;
1484
-        $children = $this->children();
1485
-        $count += $this->save()
1486
-            ? 1
1487
-            : 0;
1488
-        foreach ($children as $child_line_item) {
1489
-            if ($child_line_item instanceof EE_Line_Item) {
1490
-                $child_line_item->set_parent_ID($this->ID());
1491
-                $count += $child_line_item->save_this_and_descendants();
1492
-            }
1493
-        }
1494
-        return $count;
1495
-    }
1496
-
1497
-
1498
-    /**
1499
-     * returns the cancellation line item if this item was cancelled
1500
-     *
1501
-     * @return EE_Line_Item[]
1502
-     * @throws InvalidArgumentException
1503
-     * @throws InvalidInterfaceException
1504
-     * @throws InvalidDataTypeException
1505
-     * @throws ReflectionException
1506
-     * @throws EE_Error
1507
-     */
1508
-    public function get_cancellations()
1509
-    {
1510
-        EE_Registry::instance()->load_helper('Line_Item');
1511
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1512
-    }
1513
-
1514
-
1515
-    /**
1516
-     * If this item has an ID, then this saves it again to update the db
1517
-     *
1518
-     * @return int count of items saved
1519
-     * @throws EE_Error
1520
-     * @throws InvalidArgumentException
1521
-     * @throws InvalidDataTypeException
1522
-     * @throws InvalidInterfaceException
1523
-     * @throws ReflectionException
1524
-     */
1525
-    public function maybe_save()
1526
-    {
1527
-        if ($this->ID()) {
1528
-            return $this->save();
1529
-        }
1530
-        return false;
1531
-    }
1532
-
1533
-
1534
-    /**
1535
-     * clears the cached children and parent from the line item
1536
-     *
1537
-     * @return void
1538
-     */
1539
-    public function clear_related_line_item_cache()
1540
-    {
1541
-        $this->_children = array();
1542
-        $this->_parent = null;
1543
-    }
1544
-
1545
-
1546
-    /**
1547
-     * @param bool $raw
1548
-     * @return int
1549
-     * @throws EE_Error
1550
-     * @throws InvalidArgumentException
1551
-     * @throws InvalidDataTypeException
1552
-     * @throws InvalidInterfaceException
1553
-     * @throws ReflectionException
1554
-     */
1555
-    public function timestamp($raw = false)
1556
-    {
1557
-        return $raw
1558
-            ? $this->get_raw('LIN_timestamp')
1559
-            : $this->get('LIN_timestamp');
1560
-    }
1561
-
1562
-
1563
-
1564
-
1565
-    /************************* DEPRECATED *************************/
1566
-    /**
1567
-     * @deprecated 4.6.0
1568
-     * @param string $type one of the constants on EEM_Line_Item
1569
-     * @return EE_Line_Item[]
1570
-     * @throws EE_Error
1571
-     */
1572
-    protected function _get_descendants_of_type($type)
1573
-    {
1574
-        EE_Error::doing_it_wrong(
1575
-            'EE_Line_Item::_get_descendants_of_type()',
1576
-            sprintf(
1577
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1578
-                'EEH_Line_Item::get_descendants_of_type()'
1579
-            ),
1580
-            '4.6.0'
1581
-        );
1582
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1583
-    }
1584
-
1585
-
1586
-    /**
1587
-     * @deprecated 4.6.0
1588
-     * @param string $type like one of the EEM_Line_Item::type_*
1589
-     * @return EE_Line_Item
1590
-     * @throws EE_Error
1591
-     * @throws InvalidArgumentException
1592
-     * @throws InvalidDataTypeException
1593
-     * @throws InvalidInterfaceException
1594
-     * @throws ReflectionException
1595
-     */
1596
-    public function get_nearest_descendant_of_type($type)
1597
-    {
1598
-        EE_Error::doing_it_wrong(
1599
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1600
-            sprintf(
1601
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1602
-                'EEH_Line_Item::get_nearest_descendant_of_type()'
1603
-            ),
1604
-            '4.6.0'
1605
-        );
1606
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1607
-    }
19
+	/**
20
+	 * for children line items (currently not a normal relation)
21
+	 *
22
+	 * @type EE_Line_Item[]
23
+	 */
24
+	protected $_children = array();
25
+
26
+	/**
27
+	 * for the parent line item
28
+	 *
29
+	 * @var EE_Line_Item
30
+	 */
31
+	protected $_parent;
32
+
33
+	/**
34
+	 * @var LineItemCalculator
35
+	 */
36
+	protected $calculator;
37
+
38
+
39
+	/**
40
+	 * @param array  $props_n_values          incoming values
41
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
+	 *                                        used.)
43
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
+	 *                                        date_format and the second value is the time format
45
+	 * @return EE_Line_Item
46
+	 * @throws EE_Error
47
+	 * @throws InvalidArgumentException
48
+	 * @throws InvalidDataTypeException
49
+	 * @throws InvalidInterfaceException
50
+	 * @throws ReflectionException
51
+	 */
52
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
53
+	{
54
+		$has_object = parent::_check_for_object(
55
+			$props_n_values,
56
+			__CLASS__,
57
+			$timezone,
58
+			$date_formats
59
+		);
60
+		return $has_object
61
+			? $has_object
62
+			: new self($props_n_values, false, $timezone);
63
+	}
64
+
65
+
66
+	/**
67
+	 * @param array  $props_n_values  incoming values from the database
68
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
69
+	 *                                the website will be used.
70
+	 * @return EE_Line_Item
71
+	 * @throws EE_Error
72
+	 * @throws InvalidArgumentException
73
+	 * @throws InvalidDataTypeException
74
+	 * @throws InvalidInterfaceException
75
+	 * @throws ReflectionException
76
+	 */
77
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
+	{
79
+		return new self($props_n_values, true, $timezone);
80
+	}
81
+
82
+
83
+	/**
84
+	 * Adds some defaults if they're not specified
85
+	 *
86
+	 * @param array  $fieldValues
87
+	 * @param bool   $bydb
88
+	 * @param string $timezone
89
+	 * @throws EE_Error
90
+	 * @throws InvalidArgumentException
91
+	 * @throws InvalidDataTypeException
92
+	 * @throws InvalidInterfaceException
93
+	 * @throws ReflectionException
94
+	 */
95
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
96
+	{
97
+		$this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
98
+		parent::__construct($fieldValues, $bydb, $timezone);
99
+		if (! $this->get('LIN_code')) {
100
+			$this->set_code($this->generate_code());
101
+		}
102
+	}
103
+
104
+
105
+	/**
106
+	 * Gets ID
107
+	 *
108
+	 * @return int
109
+	 * @throws EE_Error
110
+	 * @throws InvalidArgumentException
111
+	 * @throws InvalidDataTypeException
112
+	 * @throws InvalidInterfaceException
113
+	 * @throws ReflectionException
114
+	 */
115
+	public function ID()
116
+	{
117
+		return $this->get('LIN_ID');
118
+	}
119
+
120
+
121
+	/**
122
+	 * Gets TXN_ID
123
+	 *
124
+	 * @return int
125
+	 * @throws EE_Error
126
+	 * @throws InvalidArgumentException
127
+	 * @throws InvalidDataTypeException
128
+	 * @throws InvalidInterfaceException
129
+	 * @throws ReflectionException
130
+	 */
131
+	public function TXN_ID()
132
+	{
133
+		return $this->get('TXN_ID');
134
+	}
135
+
136
+
137
+	/**
138
+	 * Sets TXN_ID
139
+	 *
140
+	 * @param int $TXN_ID
141
+	 * @throws EE_Error
142
+	 * @throws InvalidArgumentException
143
+	 * @throws InvalidDataTypeException
144
+	 * @throws InvalidInterfaceException
145
+	 * @throws ReflectionException
146
+	 */
147
+	public function set_TXN_ID($TXN_ID)
148
+	{
149
+		$this->set('TXN_ID', $TXN_ID);
150
+	}
151
+
152
+
153
+	/**
154
+	 * Gets name
155
+	 *
156
+	 * @return string
157
+	 * @throws EE_Error
158
+	 * @throws InvalidArgumentException
159
+	 * @throws InvalidDataTypeException
160
+	 * @throws InvalidInterfaceException
161
+	 * @throws ReflectionException
162
+	 */
163
+	public function name()
164
+	{
165
+		$name = $this->get('LIN_name');
166
+		if (! $name) {
167
+			$name = ucwords(str_replace('-', ' ', $this->type()));
168
+		}
169
+		return $name;
170
+	}
171
+
172
+
173
+	/**
174
+	 * Sets name
175
+	 *
176
+	 * @param string $name
177
+	 * @throws EE_Error
178
+	 * @throws InvalidArgumentException
179
+	 * @throws InvalidDataTypeException
180
+	 * @throws InvalidInterfaceException
181
+	 * @throws ReflectionException
182
+	 */
183
+	public function set_name($name)
184
+	{
185
+		$this->set('LIN_name', $name);
186
+	}
187
+
188
+
189
+	/**
190
+	 * Gets desc
191
+	 *
192
+	 * @return string
193
+	 * @throws EE_Error
194
+	 * @throws InvalidArgumentException
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws ReflectionException
198
+	 */
199
+	public function desc()
200
+	{
201
+		return $this->get('LIN_desc');
202
+	}
203
+
204
+
205
+	/**
206
+	 * Sets desc
207
+	 *
208
+	 * @param string $desc
209
+	 * @throws EE_Error
210
+	 * @throws InvalidArgumentException
211
+	 * @throws InvalidDataTypeException
212
+	 * @throws InvalidInterfaceException
213
+	 * @throws ReflectionException
214
+	 */
215
+	public function set_desc($desc)
216
+	{
217
+		$this->set('LIN_desc', $desc);
218
+	}
219
+
220
+
221
+	/**
222
+	 * Gets quantity
223
+	 *
224
+	 * @return int
225
+	 * @throws EE_Error
226
+	 * @throws InvalidArgumentException
227
+	 * @throws InvalidDataTypeException
228
+	 * @throws InvalidInterfaceException
229
+	 * @throws ReflectionException
230
+	 */
231
+	public function quantity(): int
232
+	{
233
+		return (int) $this->get('LIN_quantity');
234
+	}
235
+
236
+
237
+	/**
238
+	 * Sets quantity
239
+	 *
240
+	 * @param int $quantity
241
+	 * @throws EE_Error
242
+	 * @throws InvalidArgumentException
243
+	 * @throws InvalidDataTypeException
244
+	 * @throws InvalidInterfaceException
245
+	 * @throws ReflectionException
246
+	 */
247
+	public function set_quantity($quantity)
248
+	{
249
+		$this->set('LIN_quantity', max($quantity, 0));
250
+	}
251
+
252
+
253
+	/**
254
+	 * Gets item_id
255
+	 *
256
+	 * @return string
257
+	 * @throws EE_Error
258
+	 * @throws InvalidArgumentException
259
+	 * @throws InvalidDataTypeException
260
+	 * @throws InvalidInterfaceException
261
+	 * @throws ReflectionException
262
+	 */
263
+	public function OBJ_ID()
264
+	{
265
+		return $this->get('OBJ_ID');
266
+	}
267
+
268
+
269
+	/**
270
+	 * Sets item_id
271
+	 *
272
+	 * @param string $item_id
273
+	 * @throws EE_Error
274
+	 * @throws InvalidArgumentException
275
+	 * @throws InvalidDataTypeException
276
+	 * @throws InvalidInterfaceException
277
+	 * @throws ReflectionException
278
+	 */
279
+	public function set_OBJ_ID($item_id)
280
+	{
281
+		$this->set('OBJ_ID', $item_id);
282
+	}
283
+
284
+
285
+	/**
286
+	 * Gets item_type
287
+	 *
288
+	 * @return string
289
+	 * @throws EE_Error
290
+	 * @throws InvalidArgumentException
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws InvalidInterfaceException
293
+	 * @throws ReflectionException
294
+	 */
295
+	public function OBJ_type()
296
+	{
297
+		return $this->get('OBJ_type');
298
+	}
299
+
300
+
301
+	/**
302
+	 * Gets item_type
303
+	 *
304
+	 * @return string
305
+	 * @throws EE_Error
306
+	 * @throws InvalidArgumentException
307
+	 * @throws InvalidDataTypeException
308
+	 * @throws InvalidInterfaceException
309
+	 * @throws ReflectionException
310
+	 */
311
+	public function OBJ_type_i18n()
312
+	{
313
+		$obj_type = $this->OBJ_type();
314
+		switch ($obj_type) {
315
+			case EEM_Line_Item::OBJ_TYPE_EVENT:
316
+				$obj_type = esc_html__('Event', 'event_espresso');
317
+				break;
318
+			case EEM_Line_Item::OBJ_TYPE_PRICE:
319
+				$obj_type = esc_html__('Price', 'event_espresso');
320
+				break;
321
+			case EEM_Line_Item::OBJ_TYPE_PROMOTION:
322
+				$obj_type = esc_html__('Promotion', 'event_espresso');
323
+				break;
324
+			case EEM_Line_Item::OBJ_TYPE_TICKET:
325
+				$obj_type = esc_html__('Ticket', 'event_espresso');
326
+				break;
327
+			case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
328
+				$obj_type = esc_html__('Transaction', 'event_espresso');
329
+				break;
330
+		}
331
+		return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
332
+	}
333
+
334
+
335
+	/**
336
+	 * Sets item_type
337
+	 *
338
+	 * @param string $OBJ_type
339
+	 * @throws EE_Error
340
+	 * @throws InvalidArgumentException
341
+	 * @throws InvalidDataTypeException
342
+	 * @throws InvalidInterfaceException
343
+	 * @throws ReflectionException
344
+	 */
345
+	public function set_OBJ_type($OBJ_type)
346
+	{
347
+		$this->set('OBJ_type', $OBJ_type);
348
+	}
349
+
350
+
351
+	/**
352
+	 * Gets unit_price
353
+	 *
354
+	 * @return float
355
+	 * @throws EE_Error
356
+	 * @throws InvalidArgumentException
357
+	 * @throws InvalidDataTypeException
358
+	 * @throws InvalidInterfaceException
359
+	 * @throws ReflectionException
360
+	 */
361
+	public function unit_price()
362
+	{
363
+		return $this->get('LIN_unit_price');
364
+	}
365
+
366
+
367
+	/**
368
+	 * Sets unit_price
369
+	 *
370
+	 * @param float $unit_price
371
+	 * @throws EE_Error
372
+	 * @throws InvalidArgumentException
373
+	 * @throws InvalidDataTypeException
374
+	 * @throws InvalidInterfaceException
375
+	 * @throws ReflectionException
376
+	 */
377
+	public function set_unit_price($unit_price)
378
+	{
379
+		$this->set('LIN_unit_price', $unit_price);
380
+	}
381
+
382
+
383
+	/**
384
+	 * Checks if this item is a percentage modifier or not
385
+	 *
386
+	 * @return boolean
387
+	 * @throws EE_Error
388
+	 * @throws InvalidArgumentException
389
+	 * @throws InvalidDataTypeException
390
+	 * @throws InvalidInterfaceException
391
+	 * @throws ReflectionException
392
+	 */
393
+	public function is_percent()
394
+	{
395
+		if ($this->is_tax_sub_total()) {
396
+			// tax subtotals HAVE a percent on them, that percentage only applies
397
+			// to taxable items, so its' an exception. Treat it like a flat line item
398
+			return false;
399
+		}
400
+		$unit_price = abs($this->get('LIN_unit_price'));
401
+		$percent = abs($this->get('LIN_percent'));
402
+		if ($unit_price < .001 && $percent) {
403
+			return true;
404
+		}
405
+		if ($unit_price >= .001 && ! $percent) {
406
+			return false;
407
+		}
408
+		if ($unit_price >= .001 && $percent) {
409
+			throw new EE_Error(
410
+				sprintf(
411
+					esc_html__(
412
+						'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
413
+						'event_espresso'
414
+					),
415
+					$unit_price,
416
+					$percent
417
+				)
418
+			);
419
+		}
420
+		// if they're both 0, assume its not a percent item
421
+		return false;
422
+	}
423
+
424
+
425
+	/**
426
+	 * Gets percent (between 100-.001)
427
+	 *
428
+	 * @return float
429
+	 * @throws EE_Error
430
+	 * @throws InvalidArgumentException
431
+	 * @throws InvalidDataTypeException
432
+	 * @throws InvalidInterfaceException
433
+	 * @throws ReflectionException
434
+	 */
435
+	public function percent()
436
+	{
437
+		return $this->get('LIN_percent');
438
+	}
439
+
440
+
441
+	/**
442
+	 * Sets percent (between 100-0.01)
443
+	 *
444
+	 * @param float $percent
445
+	 * @throws EE_Error
446
+	 * @throws InvalidArgumentException
447
+	 * @throws InvalidDataTypeException
448
+	 * @throws InvalidInterfaceException
449
+	 * @throws ReflectionException
450
+	 */
451
+	public function set_percent($percent)
452
+	{
453
+		$this->set('LIN_percent', $percent);
454
+	}
455
+
456
+
457
+	/**
458
+	 * Gets total
459
+	 *
460
+	 * @return float
461
+	 * @throws EE_Error
462
+	 * @throws InvalidArgumentException
463
+	 * @throws InvalidDataTypeException
464
+	 * @throws InvalidInterfaceException
465
+	 * @throws ReflectionException
466
+	 */
467
+	public function pretaxTotal(): float
468
+	{
469
+		return $this->get('LIN_pretax');
470
+	}
471
+
472
+
473
+	/**
474
+	 * Sets total
475
+	 *
476
+	 * @param float $pretax_total
477
+	 * @throws EE_Error
478
+	 * @throws InvalidArgumentException
479
+	 * @throws InvalidDataTypeException
480
+	 * @throws InvalidInterfaceException
481
+	 * @throws ReflectionException
482
+	 */
483
+	public function setPretaxTotal(float $pretax_total)
484
+	{
485
+		$this->set('LIN_pretax', $pretax_total);
486
+	}
487
+
488
+
489
+	/**
490
+	 * Gets total
491
+	 *
492
+	 * @return float
493
+	 * @throws EE_Error
494
+	 * @throws InvalidArgumentException
495
+	 * @throws InvalidDataTypeException
496
+	 * @throws InvalidInterfaceException
497
+	 * @throws ReflectionException
498
+	 */
499
+	public function total()
500
+	{
501
+		return $this->get('LIN_total');
502
+	}
503
+
504
+
505
+	/**
506
+	 * Sets total
507
+	 *
508
+	 * @param float $total
509
+	 * @throws EE_Error
510
+	 * @throws InvalidArgumentException
511
+	 * @throws InvalidDataTypeException
512
+	 * @throws InvalidInterfaceException
513
+	 * @throws ReflectionException
514
+	 */
515
+	public function set_total($total)
516
+	{
517
+		$this->set('LIN_total', $total);
518
+	}
519
+
520
+
521
+	/**
522
+	 * Gets order
523
+	 *
524
+	 * @return int
525
+	 * @throws EE_Error
526
+	 * @throws InvalidArgumentException
527
+	 * @throws InvalidDataTypeException
528
+	 * @throws InvalidInterfaceException
529
+	 * @throws ReflectionException
530
+	 */
531
+	public function order()
532
+	{
533
+		return $this->get('LIN_order');
534
+	}
535
+
536
+
537
+	/**
538
+	 * Sets order
539
+	 *
540
+	 * @param int $order
541
+	 * @throws EE_Error
542
+	 * @throws InvalidArgumentException
543
+	 * @throws InvalidDataTypeException
544
+	 * @throws InvalidInterfaceException
545
+	 * @throws ReflectionException
546
+	 */
547
+	public function set_order($order)
548
+	{
549
+		$this->set('LIN_order', $order);
550
+	}
551
+
552
+
553
+	/**
554
+	 * Gets parent
555
+	 *
556
+	 * @return int
557
+	 * @throws EE_Error
558
+	 * @throws InvalidArgumentException
559
+	 * @throws InvalidDataTypeException
560
+	 * @throws InvalidInterfaceException
561
+	 * @throws ReflectionException
562
+	 */
563
+	public function parent_ID()
564
+	{
565
+		return $this->get('LIN_parent');
566
+	}
567
+
568
+
569
+	/**
570
+	 * Sets parent
571
+	 *
572
+	 * @param int $parent
573
+	 * @throws EE_Error
574
+	 * @throws InvalidArgumentException
575
+	 * @throws InvalidDataTypeException
576
+	 * @throws InvalidInterfaceException
577
+	 * @throws ReflectionException
578
+	 */
579
+	public function set_parent_ID($parent)
580
+	{
581
+		$this->set('LIN_parent', $parent);
582
+	}
583
+
584
+
585
+	/**
586
+	 * Gets type
587
+	 *
588
+	 * @return string
589
+	 * @throws EE_Error
590
+	 * @throws InvalidArgumentException
591
+	 * @throws InvalidDataTypeException
592
+	 * @throws InvalidInterfaceException
593
+	 * @throws ReflectionException
594
+	 */
595
+	public function type()
596
+	{
597
+		return $this->get('LIN_type');
598
+	}
599
+
600
+
601
+	/**
602
+	 * Sets type
603
+	 *
604
+	 * @param string $type
605
+	 * @throws EE_Error
606
+	 * @throws InvalidArgumentException
607
+	 * @throws InvalidDataTypeException
608
+	 * @throws InvalidInterfaceException
609
+	 * @throws ReflectionException
610
+	 */
611
+	public function set_type($type)
612
+	{
613
+		$this->set('LIN_type', $type);
614
+	}
615
+
616
+
617
+	/**
618
+	 * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
619
+	 * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
620
+	 * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
621
+	 * or indirectly by `EE_Line_item::add_child_line_item()`)
622
+	 *
623
+	 * @return EE_Base_Class|EE_Line_Item
624
+	 * @throws EE_Error
625
+	 * @throws InvalidArgumentException
626
+	 * @throws InvalidDataTypeException
627
+	 * @throws InvalidInterfaceException
628
+	 * @throws ReflectionException
629
+	 */
630
+	public function parent()
631
+	{
632
+		return $this->ID()
633
+			? $this->get_model()->get_one_by_ID($this->parent_ID())
634
+			: $this->_parent;
635
+	}
636
+
637
+
638
+	/**
639
+	 * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
640
+	 *
641
+	 * @return EE_Base_Class[]|EE_Line_Item[]
642
+	 * @throws EE_Error
643
+	 * @throws InvalidArgumentException
644
+	 * @throws InvalidDataTypeException
645
+	 * @throws InvalidInterfaceException
646
+	 * @throws ReflectionException
647
+	 */
648
+	public function children()
649
+	{
650
+		if ($this->ID()) {
651
+			return $this->get_model()->get_all(
652
+				array(
653
+					array('LIN_parent' => $this->ID()),
654
+					'order_by' => array('LIN_order' => 'ASC'),
655
+				)
656
+			);
657
+		}
658
+		if (! is_array($this->_children)) {
659
+			$this->_children = array();
660
+		}
661
+		return $this->_children;
662
+	}
663
+
664
+
665
+	/**
666
+	 * Gets code
667
+	 *
668
+	 * @return string
669
+	 * @throws EE_Error
670
+	 * @throws InvalidArgumentException
671
+	 * @throws InvalidDataTypeException
672
+	 * @throws InvalidInterfaceException
673
+	 * @throws ReflectionException
674
+	 */
675
+	public function code()
676
+	{
677
+		return $this->get('LIN_code');
678
+	}
679
+
680
+
681
+	/**
682
+	 * Sets code
683
+	 *
684
+	 * @param string $code
685
+	 * @throws EE_Error
686
+	 * @throws InvalidArgumentException
687
+	 * @throws InvalidDataTypeException
688
+	 * @throws InvalidInterfaceException
689
+	 * @throws ReflectionException
690
+	 */
691
+	public function set_code($code)
692
+	{
693
+		$this->set('LIN_code', $code);
694
+	}
695
+
696
+
697
+	/**
698
+	 * Gets is_taxable
699
+	 *
700
+	 * @return boolean
701
+	 * @throws EE_Error
702
+	 * @throws InvalidArgumentException
703
+	 * @throws InvalidDataTypeException
704
+	 * @throws InvalidInterfaceException
705
+	 * @throws ReflectionException
706
+	 */
707
+	public function is_taxable()
708
+	{
709
+		return $this->get('LIN_is_taxable');
710
+	}
711
+
712
+
713
+	/**
714
+	 * Sets is_taxable
715
+	 *
716
+	 * @param boolean $is_taxable
717
+	 * @throws EE_Error
718
+	 * @throws InvalidArgumentException
719
+	 * @throws InvalidDataTypeException
720
+	 * @throws InvalidInterfaceException
721
+	 * @throws ReflectionException
722
+	 */
723
+	public function set_is_taxable($is_taxable)
724
+	{
725
+		$this->set('LIN_is_taxable', $is_taxable);
726
+	}
727
+
728
+
729
+	/**
730
+	 * Gets the object that this model-joins-to.
731
+	 * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
732
+	 * EEM_Promotion_Object
733
+	 *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
734
+	 *
735
+	 * @return EE_Base_Class | NULL
736
+	 * @throws EE_Error
737
+	 * @throws InvalidArgumentException
738
+	 * @throws InvalidDataTypeException
739
+	 * @throws InvalidInterfaceException
740
+	 * @throws ReflectionException
741
+	 */
742
+	public function get_object()
743
+	{
744
+		$model_name_of_related_obj = $this->OBJ_type();
745
+		return $this->get_model()->has_relation($model_name_of_related_obj)
746
+			? $this->get_first_related($model_name_of_related_obj)
747
+			: null;
748
+	}
749
+
750
+
751
+	/**
752
+	 * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
753
+	 * (IE, if this line item is for a price or something else, will return NULL)
754
+	 *
755
+	 * @param array $query_params
756
+	 * @return EE_Base_Class|EE_Ticket
757
+	 * @throws EE_Error
758
+	 * @throws InvalidArgumentException
759
+	 * @throws InvalidDataTypeException
760
+	 * @throws InvalidInterfaceException
761
+	 * @throws ReflectionException
762
+	 */
763
+	public function ticket($query_params = array())
764
+	{
765
+		// we're going to assume that when this method is called
766
+		// we always want to receive the attached ticket EVEN if that ticket is archived.
767
+		// This can be overridden via the incoming $query_params argument
768
+		$remove_defaults = array('default_where_conditions' => 'none');
769
+		$query_params = array_merge($remove_defaults, $query_params);
770
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
771
+	}
772
+
773
+
774
+	/**
775
+	 * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
776
+	 *
777
+	 * @return EE_Datetime | NULL
778
+	 * @throws EE_Error
779
+	 * @throws InvalidArgumentException
780
+	 * @throws InvalidDataTypeException
781
+	 * @throws InvalidInterfaceException
782
+	 * @throws ReflectionException
783
+	 */
784
+	public function get_ticket_datetime()
785
+	{
786
+		if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
787
+			$ticket = $this->ticket();
788
+			if ($ticket instanceof EE_Ticket) {
789
+				$datetime = $ticket->first_datetime();
790
+				if ($datetime instanceof EE_Datetime) {
791
+					return $datetime;
792
+				}
793
+			}
794
+		}
795
+		return null;
796
+	}
797
+
798
+
799
+	/**
800
+	 * Gets the event's name that's related to the ticket, if this is for
801
+	 * a ticket
802
+	 *
803
+	 * @return string
804
+	 * @throws EE_Error
805
+	 * @throws InvalidArgumentException
806
+	 * @throws InvalidDataTypeException
807
+	 * @throws InvalidInterfaceException
808
+	 * @throws ReflectionException
809
+	 */
810
+	public function ticket_event_name()
811
+	{
812
+		$event_name = esc_html__('Unknown', 'event_espresso');
813
+		$event = $this->ticket_event();
814
+		if ($event instanceof EE_Event) {
815
+			$event_name = $event->name();
816
+		}
817
+		return $event_name;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets the event that's related to the ticket, if this line item represents a ticket.
823
+	 *
824
+	 * @return EE_Event|null
825
+	 * @throws EE_Error
826
+	 * @throws InvalidArgumentException
827
+	 * @throws InvalidDataTypeException
828
+	 * @throws InvalidInterfaceException
829
+	 * @throws ReflectionException
830
+	 */
831
+	public function ticket_event()
832
+	{
833
+		$event = null;
834
+		$ticket = $this->ticket();
835
+		if ($ticket instanceof EE_Ticket) {
836
+			$datetime = $ticket->first_datetime();
837
+			if ($datetime instanceof EE_Datetime) {
838
+				$event = $datetime->event();
839
+			}
840
+		}
841
+		return $event;
842
+	}
843
+
844
+
845
+	/**
846
+	 * Gets the first datetime for this lien item, assuming it's for a ticket
847
+	 *
848
+	 * @param string $date_format
849
+	 * @param string $time_format
850
+	 * @return string
851
+	 * @throws EE_Error
852
+	 * @throws InvalidArgumentException
853
+	 * @throws InvalidDataTypeException
854
+	 * @throws InvalidInterfaceException
855
+	 * @throws ReflectionException
856
+	 */
857
+	public function ticket_datetime_start($date_format = '', $time_format = '')
858
+	{
859
+		$first_datetime_string = esc_html__('Unknown', 'event_espresso');
860
+		$datetime = $this->get_ticket_datetime();
861
+		if ($datetime) {
862
+			$first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
863
+		}
864
+		return $first_datetime_string;
865
+	}
866
+
867
+
868
+	/**
869
+	 * Adds the line item as a child to this line item. If there is another child line
870
+	 * item with the same LIN_code, it is overwritten by this new one
871
+	 *
872
+	 * @param EE_Line_Item $line_item
873
+	 * @param bool          $set_order
874
+	 * @return bool success
875
+	 * @throws EE_Error
876
+	 * @throws InvalidArgumentException
877
+	 * @throws InvalidDataTypeException
878
+	 * @throws InvalidInterfaceException
879
+	 * @throws ReflectionException
880
+	 */
881
+	public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
882
+	{
883
+		// should we calculate the LIN_order for this line item ?
884
+		if ($set_order || $line_item->order() === null) {
885
+			$line_item->set_order(count($this->children()));
886
+		}
887
+		if ($this->ID()) {
888
+			// check for any duplicate line items (with the same code), if so, this replaces it
889
+			$line_item_with_same_code = $this->get_child_line_item($line_item->code());
890
+			if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
891
+				$this->delete_child_line_item($line_item_with_same_code->code());
892
+			}
893
+			$line_item->set_parent_ID($this->ID());
894
+			if ($this->TXN_ID()) {
895
+				$line_item->set_TXN_ID($this->TXN_ID());
896
+			}
897
+			return $line_item->save();
898
+		}
899
+		$this->_children[ $line_item->code() ] = $line_item;
900
+		if ($line_item->parent() !== $this) {
901
+			$line_item->set_parent($this);
902
+		}
903
+		return true;
904
+	}
905
+
906
+
907
+	/**
908
+	 * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
909
+	 * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
910
+	 * However, if this line item is NOT saved to the DB, this just caches the parent on
911
+	 * the EE_Line_Item::_parent property.
912
+	 *
913
+	 * @param EE_Line_Item $line_item
914
+	 * @throws EE_Error
915
+	 * @throws InvalidArgumentException
916
+	 * @throws InvalidDataTypeException
917
+	 * @throws InvalidInterfaceException
918
+	 * @throws ReflectionException
919
+	 */
920
+	public function set_parent($line_item)
921
+	{
922
+		if ($this->ID()) {
923
+			if (! $line_item->ID()) {
924
+				$line_item->save();
925
+			}
926
+			$this->set_parent_ID($line_item->ID());
927
+			$this->save();
928
+		} else {
929
+			$this->_parent = $line_item;
930
+			$this->set_parent_ID($line_item->ID());
931
+		}
932
+	}
933
+
934
+
935
+	/**
936
+	 * Gets the child line item as specified by its code. Because this returns an object (by reference)
937
+	 * you can modify this child line item and the parent (this object) can know about them
938
+	 * because it also has a reference to that line item
939
+	 *
940
+	 * @param string $code
941
+	 * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
942
+	 * @throws EE_Error
943
+	 * @throws InvalidArgumentException
944
+	 * @throws InvalidDataTypeException
945
+	 * @throws InvalidInterfaceException
946
+	 * @throws ReflectionException
947
+	 */
948
+	public function get_child_line_item($code)
949
+	{
950
+		if ($this->ID()) {
951
+			return $this->get_model()->get_one(
952
+				array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
953
+			);
954
+		}
955
+		return isset($this->_children[ $code ])
956
+			? $this->_children[ $code ]
957
+			: null;
958
+	}
959
+
960
+
961
+	/**
962
+	 * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
963
+	 * cached on it)
964
+	 *
965
+	 * @return int
966
+	 * @throws EE_Error
967
+	 * @throws InvalidArgumentException
968
+	 * @throws InvalidDataTypeException
969
+	 * @throws InvalidInterfaceException
970
+	 * @throws ReflectionException
971
+	 */
972
+	public function delete_children_line_items()
973
+	{
974
+		if ($this->ID()) {
975
+			return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
976
+		}
977
+		$count = count($this->_children);
978
+		$this->_children = array();
979
+		return $count;
980
+	}
981
+
982
+
983
+	/**
984
+	 * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
985
+	 * HAS NOT been saved to the DB, removes the child line item with index $code.
986
+	 * Also searches through the child's children for a matching line item. However, once a line item has been found
987
+	 * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
988
+	 * deleted)
989
+	 *
990
+	 * @param string $code
991
+	 * @param bool   $stop_search_once_found
992
+	 * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
993
+	 *             the DB yet)
994
+	 * @throws EE_Error
995
+	 * @throws InvalidArgumentException
996
+	 * @throws InvalidDataTypeException
997
+	 * @throws InvalidInterfaceException
998
+	 * @throws ReflectionException
999
+	 */
1000
+	public function delete_child_line_item($code, $stop_search_once_found = true)
1001
+	{
1002
+		if ($this->ID()) {
1003
+			$items_deleted = 0;
1004
+			if ($this->code() === $code) {
1005
+				$items_deleted += EEH_Line_Item::delete_all_child_items($this);
1006
+				$items_deleted += (int) $this->delete();
1007
+				if ($stop_search_once_found) {
1008
+					return $items_deleted;
1009
+				}
1010
+			}
1011
+			foreach ($this->children() as $child_line_item) {
1012
+				$items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1013
+			}
1014
+			return $items_deleted;
1015
+		}
1016
+		if (isset($this->_children[ $code ])) {
1017
+			unset($this->_children[ $code ]);
1018
+			return 1;
1019
+		}
1020
+		return 0;
1021
+	}
1022
+
1023
+
1024
+	/**
1025
+	 * If this line item is in the database, is of the type subtotal, and
1026
+	 * has no children, why do we have it? It should be deleted so this function
1027
+	 * does that
1028
+	 *
1029
+	 * @return boolean
1030
+	 * @throws EE_Error
1031
+	 * @throws InvalidArgumentException
1032
+	 * @throws InvalidDataTypeException
1033
+	 * @throws InvalidInterfaceException
1034
+	 * @throws ReflectionException
1035
+	 */
1036
+	public function delete_if_childless_subtotal()
1037
+	{
1038
+		if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1039
+			return $this->delete();
1040
+		}
1041
+		return false;
1042
+	}
1043
+
1044
+
1045
+	/**
1046
+	 * Creates a code and returns a string. doesn't assign the code to this model object
1047
+	 *
1048
+	 * @return string
1049
+	 * @throws EE_Error
1050
+	 * @throws InvalidArgumentException
1051
+	 * @throws InvalidDataTypeException
1052
+	 * @throws InvalidInterfaceException
1053
+	 * @throws ReflectionException
1054
+	 */
1055
+	public function generate_code()
1056
+	{
1057
+		// each line item in the cart requires a unique identifier
1058
+		return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * @return bool
1064
+	 * @throws EE_Error
1065
+	 * @throws InvalidArgumentException
1066
+	 * @throws InvalidDataTypeException
1067
+	 * @throws InvalidInterfaceException
1068
+	 * @throws ReflectionException
1069
+	 */
1070
+	public function isGlobalTax(): bool
1071
+	{
1072
+		return $this->type() === EEM_Line_Item::type_tax;
1073
+	}
1074
+
1075
+
1076
+	/**
1077
+	 * @return bool
1078
+	 * @throws EE_Error
1079
+	 * @throws InvalidArgumentException
1080
+	 * @throws InvalidDataTypeException
1081
+	 * @throws InvalidInterfaceException
1082
+	 * @throws ReflectionException
1083
+	 */
1084
+	public function isSubTax(): bool
1085
+	{
1086
+		return $this->type() === EEM_Line_Item::type_sub_tax;
1087
+	}
1088
+
1089
+
1090
+	/**
1091
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1092
+	 *
1093
+	 * @return array
1094
+	 * @throws EE_Error
1095
+	 * @throws InvalidArgumentException
1096
+	 * @throws InvalidDataTypeException
1097
+	 * @throws InvalidInterfaceException
1098
+	 * @throws ReflectionException
1099
+	 */
1100
+	public function getSubTaxes(): array
1101
+	{
1102
+		if (! $this->is_line_item()) {
1103
+			return [];
1104
+		}
1105
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1111
+	 *
1112
+	 * @return bool
1113
+	 * @throws EE_Error
1114
+	 * @throws InvalidArgumentException
1115
+	 * @throws InvalidDataTypeException
1116
+	 * @throws InvalidInterfaceException
1117
+	 * @throws ReflectionException
1118
+	 */
1119
+	public function hasSubTaxes(): bool
1120
+	{
1121
+		if (! $this->is_line_item()) {
1122
+			return false;
1123
+		}
1124
+		$sub_taxes = $this->getSubTaxes();
1125
+		return ! empty($sub_taxes);
1126
+	}
1127
+
1128
+
1129
+	/**
1130
+	 * @return bool
1131
+	 * @throws EE_Error
1132
+	 * @throws ReflectionException
1133
+	 * @deprecated   $VID:$
1134
+	 */
1135
+	public function is_tax(): bool
1136
+	{
1137
+		return $this->isGlobalTax();
1138
+	}
1139
+
1140
+
1141
+	/**
1142
+	 * @return bool
1143
+	 * @throws EE_Error
1144
+	 * @throws InvalidArgumentException
1145
+	 * @throws InvalidDataTypeException
1146
+	 * @throws InvalidInterfaceException
1147
+	 * @throws ReflectionException
1148
+	 */
1149
+	public function is_tax_sub_total()
1150
+	{
1151
+		return $this->type() === EEM_Line_Item::type_tax_sub_total;
1152
+	}
1153
+
1154
+
1155
+	/**
1156
+	 * @return bool
1157
+	 * @throws EE_Error
1158
+	 * @throws InvalidArgumentException
1159
+	 * @throws InvalidDataTypeException
1160
+	 * @throws InvalidInterfaceException
1161
+	 * @throws ReflectionException
1162
+	 */
1163
+	public function is_line_item()
1164
+	{
1165
+		return $this->type() === EEM_Line_Item::type_line_item;
1166
+	}
1167
+
1168
+
1169
+	/**
1170
+	 * @return bool
1171
+	 * @throws EE_Error
1172
+	 * @throws InvalidArgumentException
1173
+	 * @throws InvalidDataTypeException
1174
+	 * @throws InvalidInterfaceException
1175
+	 * @throws ReflectionException
1176
+	 */
1177
+	public function is_sub_line_item()
1178
+	{
1179
+		return $this->type() === EEM_Line_Item::type_sub_line_item;
1180
+	}
1181
+
1182
+
1183
+	/**
1184
+	 * @return bool
1185
+	 * @throws EE_Error
1186
+	 * @throws InvalidArgumentException
1187
+	 * @throws InvalidDataTypeException
1188
+	 * @throws InvalidInterfaceException
1189
+	 * @throws ReflectionException
1190
+	 */
1191
+	public function is_sub_total()
1192
+	{
1193
+		return $this->type() === EEM_Line_Item::type_sub_total;
1194
+	}
1195
+
1196
+
1197
+	/**
1198
+	 * Whether or not this line item is a cancellation line item
1199
+	 *
1200
+	 * @return boolean
1201
+	 * @throws EE_Error
1202
+	 * @throws InvalidArgumentException
1203
+	 * @throws InvalidDataTypeException
1204
+	 * @throws InvalidInterfaceException
1205
+	 * @throws ReflectionException
1206
+	 */
1207
+	public function is_cancellation()
1208
+	{
1209
+		return EEM_Line_Item::type_cancellation === $this->type();
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * @return bool
1215
+	 * @throws EE_Error
1216
+	 * @throws InvalidArgumentException
1217
+	 * @throws InvalidDataTypeException
1218
+	 * @throws InvalidInterfaceException
1219
+	 * @throws ReflectionException
1220
+	 */
1221
+	public function is_total()
1222
+	{
1223
+		return $this->type() === EEM_Line_Item::type_total;
1224
+	}
1225
+
1226
+
1227
+	/**
1228
+	 * @return bool
1229
+	 * @throws EE_Error
1230
+	 * @throws InvalidArgumentException
1231
+	 * @throws InvalidDataTypeException
1232
+	 * @throws InvalidInterfaceException
1233
+	 * @throws ReflectionException
1234
+	 */
1235
+	public function is_cancelled()
1236
+	{
1237
+		return $this->type() === EEM_Line_Item::type_cancellation;
1238
+	}
1239
+
1240
+
1241
+	/**
1242
+	 * @return string like '2, 004.00', formatted according to the localized currency
1243
+	 * @throws EE_Error
1244
+	 * @throws InvalidArgumentException
1245
+	 * @throws InvalidDataTypeException
1246
+	 * @throws InvalidInterfaceException
1247
+	 * @throws ReflectionException
1248
+	 */
1249
+	public function unit_price_no_code()
1250
+	{
1251
+		return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1252
+	}
1253
+
1254
+
1255
+	/**
1256
+	 * @return string like '2, 004.00', formatted according to the localized currency
1257
+	 * @throws EE_Error
1258
+	 * @throws InvalidArgumentException
1259
+	 * @throws InvalidDataTypeException
1260
+	 * @throws InvalidInterfaceException
1261
+	 * @throws ReflectionException
1262
+	 */
1263
+	public function total_no_code()
1264
+	{
1265
+		return $this->get_pretty('LIN_total', 'no_currency_code');
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * Gets the final total on this item, taking taxes into account.
1271
+	 * Has the side-effect of setting the sub-total as it was just calculated.
1272
+	 * If this is used on a grand-total line item, also updates the transaction's
1273
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
1274
+	 * want to change a persistable transaction with info from a non-persistent line item)
1275
+	 *
1276
+	 * @param bool $update_txn_status
1277
+	 * @return float
1278
+	 * @throws EE_Error
1279
+	 * @throws InvalidArgumentException
1280
+	 * @throws InvalidDataTypeException
1281
+	 * @throws InvalidInterfaceException
1282
+	 * @throws ReflectionException
1283
+	 * @throws RuntimeException
1284
+	 */
1285
+	public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1286
+	{
1287
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1288
+		return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1289
+	}
1290
+
1291
+
1292
+	/**
1293
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1294
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1295
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1296
+	 * when this is called on the grand total
1297
+	 *
1298
+	 * @return float
1299
+	 * @throws EE_Error
1300
+	 * @throws InvalidArgumentException
1301
+	 * @throws InvalidDataTypeException
1302
+	 * @throws InvalidInterfaceException
1303
+	 * @throws ReflectionException
1304
+	 */
1305
+	public function recalculate_pre_tax_total(): float
1306
+	{
1307
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1308
+		[$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1309
+		return $total;
1310
+	}
1311
+
1312
+
1313
+	/**
1314
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1315
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1316
+	 * and tax sub-total if already in the DB
1317
+	 *
1318
+	 * @return float
1319
+	 * @throws EE_Error
1320
+	 * @throws InvalidArgumentException
1321
+	 * @throws InvalidDataTypeException
1322
+	 * @throws InvalidInterfaceException
1323
+	 * @throws ReflectionException
1324
+	 */
1325
+	public function recalculate_taxes_and_tax_total(): float
1326
+	{
1327
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1328
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1329
+	}
1330
+
1331
+
1332
+	/**
1333
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
1334
+	 * recalculate_taxes_and_total
1335
+	 *
1336
+	 * @return float
1337
+	 * @throws EE_Error
1338
+	 * @throws InvalidArgumentException
1339
+	 * @throws InvalidDataTypeException
1340
+	 * @throws InvalidInterfaceException
1341
+	 * @throws ReflectionException
1342
+	 */
1343
+	public function get_total_tax()
1344
+	{
1345
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1346
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1347
+	}
1348
+
1349
+
1350
+	/**
1351
+	 * Gets the total for all the items purchased only
1352
+	 *
1353
+	 * @return float
1354
+	 * @throws EE_Error
1355
+	 * @throws InvalidArgumentException
1356
+	 * @throws InvalidDataTypeException
1357
+	 * @throws InvalidInterfaceException
1358
+	 * @throws ReflectionException
1359
+	 */
1360
+	public function get_items_total()
1361
+	{
1362
+		// by default, let's make sure we're consistent with the existing line item
1363
+		if ($this->is_total()) {
1364
+			return $this->pretaxTotal();
1365
+		}
1366
+		$total = 0;
1367
+		foreach ($this->get_items() as $item) {
1368
+			if ($item instanceof EE_Line_Item) {
1369
+				$total += $item->pretaxTotal();
1370
+			}
1371
+		}
1372
+		return $total;
1373
+	}
1374
+
1375
+
1376
+	/**
1377
+	 * Gets all the descendants (ie, children or children of children etc) that
1378
+	 * are of the type 'tax'
1379
+	 *
1380
+	 * @return EE_Line_Item[]
1381
+	 * @throws EE_Error
1382
+	 */
1383
+	public function tax_descendants()
1384
+	{
1385
+		return EEH_Line_Item::get_tax_descendants($this);
1386
+	}
1387
+
1388
+
1389
+	/**
1390
+	 * Gets all the real items purchased which are children of this item
1391
+	 *
1392
+	 * @return EE_Line_Item[]
1393
+	 * @throws EE_Error
1394
+	 */
1395
+	public function get_items()
1396
+	{
1397
+		return EEH_Line_Item::get_line_item_descendants($this);
1398
+	}
1399
+
1400
+
1401
+	/**
1402
+	 * Returns the amount taxable among this line item's children (or if it has no children,
1403
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
1404
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
1405
+	 * but there is a "Taxable" discount), returns 0.
1406
+	 *
1407
+	 * @return float
1408
+	 * @throws EE_Error
1409
+	 * @throws InvalidArgumentException
1410
+	 * @throws InvalidDataTypeException
1411
+	 * @throws InvalidInterfaceException
1412
+	 * @throws ReflectionException
1413
+	 */
1414
+	public function taxable_total(): float
1415
+	{
1416
+		return $this->calculator->taxableAmountForGlobalTaxes($this);
1417
+	}
1418
+
1419
+
1420
+	/**
1421
+	 * Gets the transaction for this line item
1422
+	 *
1423
+	 * @return EE_Base_Class|EE_Transaction
1424
+	 * @throws EE_Error
1425
+	 * @throws InvalidArgumentException
1426
+	 * @throws InvalidDataTypeException
1427
+	 * @throws InvalidInterfaceException
1428
+	 * @throws ReflectionException
1429
+	 */
1430
+	public function transaction()
1431
+	{
1432
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1433
+	}
1434
+
1435
+
1436
+	/**
1437
+	 * Saves this line item to the DB, and recursively saves its descendants.
1438
+	 * Because there currently is no proper parent-child relation on the model,
1439
+	 * save_this_and_cached() will NOT save the descendants.
1440
+	 * Also sets the transaction on this line item and all its descendants before saving
1441
+	 *
1442
+	 * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1443
+	 * @return int count of items saved
1444
+	 * @throws EE_Error
1445
+	 * @throws InvalidArgumentException
1446
+	 * @throws InvalidDataTypeException
1447
+	 * @throws InvalidInterfaceException
1448
+	 * @throws ReflectionException
1449
+	 */
1450
+	public function save_this_and_descendants_to_txn($txn_id = null)
1451
+	{
1452
+		$count = 0;
1453
+		if (! $txn_id) {
1454
+			$txn_id = $this->TXN_ID();
1455
+		}
1456
+		$this->set_TXN_ID($txn_id);
1457
+		$children = $this->children();
1458
+		$count += $this->save()
1459
+			? 1
1460
+			: 0;
1461
+		foreach ($children as $child_line_item) {
1462
+			if ($child_line_item instanceof EE_Line_Item) {
1463
+				$child_line_item->set_parent_ID($this->ID());
1464
+				$count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1465
+			}
1466
+		}
1467
+		return $count;
1468
+	}
1469
+
1470
+
1471
+	/**
1472
+	 * Saves this line item to the DB, and recursively saves its descendants.
1473
+	 *
1474
+	 * @return int count of items saved
1475
+	 * @throws EE_Error
1476
+	 * @throws InvalidArgumentException
1477
+	 * @throws InvalidDataTypeException
1478
+	 * @throws InvalidInterfaceException
1479
+	 * @throws ReflectionException
1480
+	 */
1481
+	public function save_this_and_descendants()
1482
+	{
1483
+		$count = 0;
1484
+		$children = $this->children();
1485
+		$count += $this->save()
1486
+			? 1
1487
+			: 0;
1488
+		foreach ($children as $child_line_item) {
1489
+			if ($child_line_item instanceof EE_Line_Item) {
1490
+				$child_line_item->set_parent_ID($this->ID());
1491
+				$count += $child_line_item->save_this_and_descendants();
1492
+			}
1493
+		}
1494
+		return $count;
1495
+	}
1496
+
1497
+
1498
+	/**
1499
+	 * returns the cancellation line item if this item was cancelled
1500
+	 *
1501
+	 * @return EE_Line_Item[]
1502
+	 * @throws InvalidArgumentException
1503
+	 * @throws InvalidInterfaceException
1504
+	 * @throws InvalidDataTypeException
1505
+	 * @throws ReflectionException
1506
+	 * @throws EE_Error
1507
+	 */
1508
+	public function get_cancellations()
1509
+	{
1510
+		EE_Registry::instance()->load_helper('Line_Item');
1511
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1512
+	}
1513
+
1514
+
1515
+	/**
1516
+	 * If this item has an ID, then this saves it again to update the db
1517
+	 *
1518
+	 * @return int count of items saved
1519
+	 * @throws EE_Error
1520
+	 * @throws InvalidArgumentException
1521
+	 * @throws InvalidDataTypeException
1522
+	 * @throws InvalidInterfaceException
1523
+	 * @throws ReflectionException
1524
+	 */
1525
+	public function maybe_save()
1526
+	{
1527
+		if ($this->ID()) {
1528
+			return $this->save();
1529
+		}
1530
+		return false;
1531
+	}
1532
+
1533
+
1534
+	/**
1535
+	 * clears the cached children and parent from the line item
1536
+	 *
1537
+	 * @return void
1538
+	 */
1539
+	public function clear_related_line_item_cache()
1540
+	{
1541
+		$this->_children = array();
1542
+		$this->_parent = null;
1543
+	}
1544
+
1545
+
1546
+	/**
1547
+	 * @param bool $raw
1548
+	 * @return int
1549
+	 * @throws EE_Error
1550
+	 * @throws InvalidArgumentException
1551
+	 * @throws InvalidDataTypeException
1552
+	 * @throws InvalidInterfaceException
1553
+	 * @throws ReflectionException
1554
+	 */
1555
+	public function timestamp($raw = false)
1556
+	{
1557
+		return $raw
1558
+			? $this->get_raw('LIN_timestamp')
1559
+			: $this->get('LIN_timestamp');
1560
+	}
1561
+
1562
+
1563
+
1564
+
1565
+	/************************* DEPRECATED *************************/
1566
+	/**
1567
+	 * @deprecated 4.6.0
1568
+	 * @param string $type one of the constants on EEM_Line_Item
1569
+	 * @return EE_Line_Item[]
1570
+	 * @throws EE_Error
1571
+	 */
1572
+	protected function _get_descendants_of_type($type)
1573
+	{
1574
+		EE_Error::doing_it_wrong(
1575
+			'EE_Line_Item::_get_descendants_of_type()',
1576
+			sprintf(
1577
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1578
+				'EEH_Line_Item::get_descendants_of_type()'
1579
+			),
1580
+			'4.6.0'
1581
+		);
1582
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1583
+	}
1584
+
1585
+
1586
+	/**
1587
+	 * @deprecated 4.6.0
1588
+	 * @param string $type like one of the EEM_Line_Item::type_*
1589
+	 * @return EE_Line_Item
1590
+	 * @throws EE_Error
1591
+	 * @throws InvalidArgumentException
1592
+	 * @throws InvalidDataTypeException
1593
+	 * @throws InvalidInterfaceException
1594
+	 * @throws ReflectionException
1595
+	 */
1596
+	public function get_nearest_descendant_of_type($type)
1597
+	{
1598
+		EE_Error::doing_it_wrong(
1599
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1600
+			sprintf(
1601
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1602
+				'EEH_Line_Item::get_nearest_descendant_of_type()'
1603
+			),
1604
+			'4.6.0'
1605
+		);
1606
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1607
+	}
1608 1608
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -96,7 +96,7 @@  discard block
 block discarded – undo
96 96
     {
97 97
         $this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
98 98
         parent::__construct($fieldValues, $bydb, $timezone);
99
-        if (! $this->get('LIN_code')) {
99
+        if ( ! $this->get('LIN_code')) {
100 100
             $this->set_code($this->generate_code());
101 101
         }
102 102
     }
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
     public function name()
164 164
     {
165 165
         $name = $this->get('LIN_name');
166
-        if (! $name) {
166
+        if ( ! $name) {
167 167
             $name = ucwords(str_replace('-', ' ', $this->type()));
168 168
         }
169 169
         return $name;
@@ -655,7 +655,7 @@  discard block
 block discarded – undo
655 655
                 )
656 656
             );
657 657
         }
658
-        if (! is_array($this->_children)) {
658
+        if ( ! is_array($this->_children)) {
659 659
             $this->_children = array();
660 660
         }
661 661
         return $this->_children;
@@ -896,7 +896,7 @@  discard block
 block discarded – undo
896 896
             }
897 897
             return $line_item->save();
898 898
         }
899
-        $this->_children[ $line_item->code() ] = $line_item;
899
+        $this->_children[$line_item->code()] = $line_item;
900 900
         if ($line_item->parent() !== $this) {
901 901
             $line_item->set_parent($this);
902 902
         }
@@ -920,7 +920,7 @@  discard block
 block discarded – undo
920 920
     public function set_parent($line_item)
921 921
     {
922 922
         if ($this->ID()) {
923
-            if (! $line_item->ID()) {
923
+            if ( ! $line_item->ID()) {
924 924
                 $line_item->save();
925 925
             }
926 926
             $this->set_parent_ID($line_item->ID());
@@ -952,8 +952,8 @@  discard block
 block discarded – undo
952 952
                 array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
953 953
             );
954 954
         }
955
-        return isset($this->_children[ $code ])
956
-            ? $this->_children[ $code ]
955
+        return isset($this->_children[$code])
956
+            ? $this->_children[$code]
957 957
             : null;
958 958
     }
959 959
 
@@ -1013,8 +1013,8 @@  discard block
 block discarded – undo
1013 1013
             }
1014 1014
             return $items_deleted;
1015 1015
         }
1016
-        if (isset($this->_children[ $code ])) {
1017
-            unset($this->_children[ $code ]);
1016
+        if (isset($this->_children[$code])) {
1017
+            unset($this->_children[$code]);
1018 1018
             return 1;
1019 1019
         }
1020 1020
         return 0;
@@ -1055,7 +1055,7 @@  discard block
 block discarded – undo
1055 1055
     public function generate_code()
1056 1056
     {
1057 1057
         // each line item in the cart requires a unique identifier
1058
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1058
+        return md5($this->get('OBJ_type').$this->get('OBJ_ID').microtime());
1059 1059
     }
1060 1060
 
1061 1061
 
@@ -1099,7 +1099,7 @@  discard block
 block discarded – undo
1099 1099
      */
1100 1100
     public function getSubTaxes(): array
1101 1101
     {
1102
-        if (! $this->is_line_item()) {
1102
+        if ( ! $this->is_line_item()) {
1103 1103
             return [];
1104 1104
         }
1105 1105
         return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
@@ -1118,7 +1118,7 @@  discard block
 block discarded – undo
1118 1118
      */
1119 1119
     public function hasSubTaxes(): bool
1120 1120
     {
1121
-        if (! $this->is_line_item()) {
1121
+        if ( ! $this->is_line_item()) {
1122 1122
             return false;
1123 1123
         }
1124 1124
         $sub_taxes = $this->getSubTaxes();
@@ -1450,7 +1450,7 @@  discard block
 block discarded – undo
1450 1450
     public function save_this_and_descendants_to_txn($txn_id = null)
1451 1451
     {
1452 1452
         $count = 0;
1453
-        if (! $txn_id) {
1453
+        if ( ! $txn_id) {
1454 1454
             $txn_id = $this->TXN_ID();
1455 1455
         }
1456 1456
         $this->set_TXN_ID($txn_id);
Please login to merge, or discard this patch.
core/helpers/EEH_Debug_Tools.helper.php 1 patch
Indentation   +703 added lines, -703 removed lines patch added patch discarded remove patch
@@ -13,697 +13,697 @@  discard block
 block discarded – undo
13 13
 class EEH_Debug_Tools
14 14
 {
15 15
 
16
-    /**
17
-     *    instance of the EEH_Autoloader object
18
-     *
19
-     * @var    $_instance
20
-     * @access    private
21
-     */
22
-    private static $_instance;
23
-
24
-    /**
25
-     * @var array
26
-     */
27
-    protected $_memory_usage_points = array();
28
-
29
-
30
-
31
-    /**
32
-     * @singleton method used to instantiate class object
33
-     * @access    public
34
-     * @return EEH_Debug_Tools
35
-     */
36
-    public static function instance()
37
-    {
38
-        // check if class object is instantiated, and instantiated properly
39
-        if (! self::$_instance instanceof EEH_Debug_Tools) {
40
-            self::$_instance = new self();
41
-        }
42
-        return self::$_instance;
43
-    }
44
-
45
-
46
-
47
-    /**
48
-     * private class constructor
49
-     */
50
-    private function __construct()
51
-    {
52
-        // load Kint PHP debugging library
53
-        if (! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')) {
54
-            // despite EE4 having a check for an existing copy of the Kint debugging class,
55
-            // if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
56
-            // then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
57
-            // so we've moved it to our test folder so that it is not included with production releases
58
-            // plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
59
-            require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
60
-        }
61
-        // if ( ! defined('DOING_AJAX') || $_REQUEST['noheader'] !== 'true' || ! isset( $_REQUEST['noheader'], $_REQUEST['TB_iframe'] ) ) {
62
-        // add_action( 'shutdown', array($this,'espresso_session_footer_dump') );
63
-        // }
64
-        $plugin = basename(EE_PLUGIN_DIR_PATH);
65
-        add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
66
-        add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
67
-        add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
68
-    }
69
-
70
-
71
-
72
-    /**
73
-     *    show_db_name
74
-     *
75
-     * @return void
76
-     */
77
-    public static function show_db_name()
78
-    {
79
-        if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
80
-            echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
81
-                 . DB_NAME
82
-                 . '</p>';
83
-        }
84
-        if (EE_DEBUG) {
85
-            Benchmark::displayResults();
86
-        }
87
-    }
88
-
89
-
90
-
91
-    /**
92
-     *    dump EE_Session object at bottom of page after everything else has happened
93
-     *
94
-     * @return void
95
-     */
96
-    public function espresso_session_footer_dump()
97
-    {
98
-        if (
99
-            (defined('WP_DEBUG') && WP_DEBUG)
100
-            && ! defined('DOING_AJAX')
101
-            && class_exists('Kint')
102
-            && function_exists('wp_get_current_user')
103
-            && current_user_can('update_core')
104
-            && class_exists('EE_Registry')
105
-        ) {
106
-            Kint::dump(EE_Registry::instance()->SSN->id());
107
-            Kint::dump(EE_Registry::instance()->SSN);
108
-            //          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
109
-            $this->espresso_list_hooked_functions();
110
-            Benchmark::displayResults();
111
-        }
112
-    }
113
-
114
-
115
-
116
-    /**
117
-     *    List All Hooked Functions
118
-     *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
119
-     *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
120
-     *
121
-     * @param string $tag
122
-     * @return void
123
-     */
124
-    public function espresso_list_hooked_functions($tag = '')
125
-    {
126
-        global $wp_filter;
127
-        echo '<br/><br/><br/><h3>Hooked Functions</h3>';
128
-        if ($tag) {
129
-            $hook[ $tag ] = $wp_filter[ $tag ];
130
-            if (! is_array($hook[ $tag ])) {
131
-                trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
132
-                return;
133
-            }
134
-            echo '<h5>For Tag: ' . $tag . '</h5>';
135
-        } else {
136
-            $hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
137
-            ksort($hook);
138
-        }
139
-        foreach ($hook as $tag_name => $priorities) {
140
-            echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>$tag_name</strong><br />";
141
-            ksort($priorities);
142
-            foreach ($priorities as $priority => $function) {
143
-                echo $priority;
144
-                foreach ($function as $name => $properties) {
145
-                    echo "\t$name<br />";
146
-                }
147
-            }
148
-        }
149
-    }
150
-
151
-
152
-
153
-    /**
154
-     *    registered_filter_callbacks
155
-     *
156
-     * @param string $hook_name
157
-     * @return array
158
-     */
159
-    public static function registered_filter_callbacks($hook_name = '')
160
-    {
161
-        $filters = array();
162
-        global $wp_filter;
163
-        if (isset($wp_filter[ $hook_name ])) {
164
-            $filters[ $hook_name ] = array();
165
-            foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
166
-                $filters[ $hook_name ][ $priority ] = array();
167
-                foreach ($callbacks as $callback) {
168
-                    $filters[ $hook_name ][ $priority ][] = $callback['function'];
169
-                }
170
-            }
171
-        }
172
-        return $filters;
173
-    }
174
-
175
-
176
-
177
-    /**
178
-     *    captures plugin activation errors for debugging
179
-     *
180
-     * @return void
181
-     * @throws EE_Error
182
-     */
183
-    public static function ee_plugin_activation_errors()
184
-    {
185
-        if (WP_DEBUG) {
186
-            $activation_errors = ob_get_contents();
187
-            if (empty($activation_errors)) {
188
-                return;
189
-            }
190
-            $activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
191
-            espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
192
-            if (class_exists('EEH_File')) {
193
-                try {
194
-                    EEH_File::ensure_file_exists_and_is_writable(
195
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
196
-                    );
197
-                    EEH_File::write_to_file(
198
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
199
-                        $activation_errors
200
-                    );
201
-                } catch (EE_Error $e) {
202
-                    EE_Error::add_error(
203
-                        sprintf(
204
-                            __(
205
-                                'The Event Espresso activation errors file could not be setup because: %s',
206
-                                'event_espresso'
207
-                            ),
208
-                            $e->getMessage()
209
-                        ),
210
-                        __FILE__,
211
-                        __FUNCTION__,
212
-                        __LINE__
213
-                    );
214
-                }
215
-            } else {
216
-                // old school attempt
217
-                file_put_contents(
218
-                    EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
219
-                    $activation_errors
220
-                );
221
-            }
222
-            $activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
223
-            update_option('ee_plugin_activation_errors', $activation_errors);
224
-        }
225
-    }
226
-
227
-
228
-
229
-    /**
230
-     * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
231
-     * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
232
-     * or we want to make sure they use something the right way.
233
-     *
234
-     * @access public
235
-     * @param string $function      The function that was called
236
-     * @param string $message       A message explaining what has been done incorrectly
237
-     * @param string $version       The version of Event Espresso where the error was added
238
-     * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
239
-     *                              for a deprecated function. This allows deprecation to occur during one version,
240
-     *                              but not have any notices appear until a later version. This allows developers
241
-     *                              extra time to update their code before notices appear.
242
-     * @param int    $error_type
243
-     * @uses   trigger_error()
244
-     */
245
-    public function doing_it_wrong(
246
-        $function,
247
-        $message,
248
-        $version,
249
-        $applies_when = '',
250
-        $error_type = null
251
-    ) {
252
-        $applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
253
-        $error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
254
-        // because we swapped the parameter order around for the last two params,
255
-        // let's verify that some third party isn't still passing an error type value for the third param
256
-        if (is_int($applies_when)) {
257
-            $error_type = $applies_when;
258
-            $applies_when = espresso_version();
259
-        }
260
-        // if not displaying notices yet, then just leave
261
-        if (version_compare(espresso_version(), $applies_when, '<')) {
262
-            return;
263
-        }
264
-        do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
265
-        $version = $version === null
266
-            ? ''
267
-            : sprintf(
268
-                __('(This message was added in version %s of Event Espresso)', 'event_espresso'),
269
-                $version
270
-            );
271
-        $error_message = sprintf(
272
-            esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
273
-            $function,
274
-            '<strong>',
275
-            '</strong>',
276
-            $message,
277
-            $version
278
-        );
279
-        // don't trigger error if doing ajax,
280
-        // instead we'll add a transient EE_Error notice that in theory should show on the next request.
281
-        if (defined('DOING_AJAX') && DOING_AJAX) {
282
-            $error_message .= ' ' . esc_html__(
283
-                'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
284
-                'event_espresso'
285
-            );
286
-            $error_message .= '<ul><li>';
287
-            $error_message .= implode('</li><li>', EE_Registry::instance()->REQ->params());
288
-            $error_message .= '</ul>';
289
-            EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
290
-            // now we set this on the transient so it shows up on the next request.
291
-            EE_Error::get_notices(false, true);
292
-        } else {
293
-            trigger_error($error_message, $error_type);
294
-        }
295
-    }
296
-
297
-
298
-
299
-
300
-    /**
301
-     * Logger helpers
302
-     */
303
-    /**
304
-     * debug
305
-     *
306
-     * @param string $class
307
-     * @param string $func
308
-     * @param string $line
309
-     * @param array  $info
310
-     * @param bool   $display_request
311
-     * @param string $debug_index
312
-     * @param string $debug_key
313
-     * @throws EE_Error
314
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
315
-     */
316
-    public static function log(
317
-        $class = '',
318
-        $func = '',
319
-        $line = '',
320
-        $info = array(),
321
-        $display_request = false,
322
-        $debug_index = '',
323
-        $debug_key = 'EE_DEBUG_SPCO'
324
-    ) {
325
-        if (WP_DEBUG) {
326
-            $debug_key = $debug_key . '_' . EE_Session::instance()->id();
327
-            $debug_data = get_option($debug_key, array());
328
-            $default_data = array(
329
-                $class => $func . '() : ' . $line,
330
-                'REQ'  => $display_request ? $_REQUEST : '',
331
-            );
332
-            // don't serialize objects
333
-            $info = self::strip_objects($info);
334
-            $index = ! empty($debug_index) ? $debug_index : 0;
335
-            if (! isset($debug_data[ $index ])) {
336
-                $debug_data[ $index ] = array();
337
-            }
338
-            $debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
339
-            update_option($debug_key, $debug_data);
340
-        }
341
-    }
342
-
343
-
344
-
345
-    /**
346
-     * strip_objects
347
-     *
348
-     * @param array $info
349
-     * @return array
350
-     */
351
-    public static function strip_objects($info = array())
352
-    {
353
-        foreach ($info as $key => $value) {
354
-            if (is_array($value)) {
355
-                $info[ $key ] = self::strip_objects($value);
356
-            } elseif (is_object($value)) {
357
-                $object_class = get_class($value);
358
-                $info[ $object_class ] = array();
359
-                $info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
360
-                if (method_exists($value, 'ID')) {
361
-                    $info[ $object_class ]['ID'] = $value->ID();
362
-                }
363
-                if (method_exists($value, 'status')) {
364
-                    $info[ $object_class ]['status'] = $value->status();
365
-                } elseif (method_exists($value, 'status_ID')) {
366
-                    $info[ $object_class ]['status'] = $value->status_ID();
367
-                }
368
-                unset($info[ $key ]);
369
-            }
370
-        }
371
-        return (array) $info;
372
-    }
373
-
374
-
375
-
376
-    /**
377
-     * @param mixed      $var
378
-     * @param string     $var_name
379
-     * @param string     $file
380
-     * @param int|string $line
381
-     * @param int|string $heading_tag
382
-     * @param bool       $die
383
-     * @param string     $margin
384
-     */
385
-    public static function printv(
386
-        $var,
387
-        $var_name = '',
388
-        $file = '',
389
-        $line = '',
390
-        $heading_tag = 5,
391
-        $die = false,
392
-        $margin = ''
393
-    ) {
394
-        $var_name = ! $var_name ? 'string' : $var_name;
395
-        $var_name = ucwords(str_replace('$', '', $var_name));
396
-        $is_method = method_exists($var_name, $var);
397
-        $var_name = ucwords(str_replace('_', ' ', $var_name));
398
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
399
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
400
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
401
-        $result .= $is_method
402
-            ? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
403
-            : EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
404
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
405
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
406
-        if ($die) {
407
-            die($result);
408
-        }
409
-        echo $result;
410
-    }
411
-
412
-
413
-    protected static function headingTag($heading_tag)
414
-    {
415
-        $heading_tag = absint($heading_tag);
416
-        return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
417
-    }
418
-
419
-
420
-    protected static function headingSpacer($heading_tag)
421
-    {
422
-        return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
423
-            ? "\n"
424
-            : '';
425
-    }
426
-
427
-
428
-    protected static function plainOutput()
429
-    {
430
-        return defined('EE_TESTS_DIR')
431
-               || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
432
-               || (
433
-                   isset($_SERVER['REQUEST_URI'])
434
-                   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
435
-               );
436
-    }
437
-
438
-
439
-    /**
440
-     * @param string $var_name
441
-     * @param string $heading_tag
442
-     * @param string $margin
443
-     * @param int    $line
444
-     * @return string
445
-     */
446
-    protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
447
-    {
448
-        if (EEH_Debug_Tools::plainOutput()) {
449
-            switch ($heading_tag) {
450
-                case 'h1':
451
-                    $line_breaks = EEH_Debug_Tools::lineBreak(3);
452
-                    break;
453
-                case 'h2':
454
-                    $line_breaks = EEH_Debug_Tools::lineBreak(2);
455
-                    break;
456
-                default:
457
-                    $line_breaks = EEH_Debug_Tools::lineBreak();
458
-                    break;
459
-            }
460
-            return "{$line_breaks}{$line}) {$var_name}";
461
-        }
462
-        $margin = "25px 0 0 {$margin}";
463
-        return '<' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . ';"><b>' . $var_name . '</b>';
464
-    }
465
-
466
-
467
-
468
-    /**
469
-     * @param string $heading_tag
470
-     * @return string
471
-     */
472
-    protected static function headingX($heading_tag = 'h5')
473
-    {
474
-        if (EEH_Debug_Tools::plainOutput()) {
475
-            return '';
476
-        }
477
-        return '</' . $heading_tag . '>';
478
-    }
479
-
480
-
481
-
482
-    /**
483
-     * @param string $content
484
-     * @return string
485
-     */
486
-    protected static function grey_span($content = '')
487
-    {
488
-        if (EEH_Debug_Tools::plainOutput()) {
489
-            return $content;
490
-        }
491
-        return '<span style="color:#999">' . $content . '</span>';
492
-    }
493
-
494
-
495
-
496
-    /**
497
-     * @param string $file
498
-     * @param int    $line
499
-     * @return string
500
-     */
501
-    protected static function file_and_line($file, $line, $heading_tag)
502
-    {
503
-        if ($file === '' || $line === '') {
504
-            return '';
505
-        }
506
-        $file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
507
-        if (EEH_Debug_Tools::plainOutput()) {
508
-            if ($heading_tag === 'h1' || $heading_tag === 'h2') {
509
-                return " ({$file})";
510
-            }
511
-            return '';
512
-        }
513
-        return '<br /><span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
514
-               . $file
515
-               . '<br />line no: '
516
-               . $line
517
-               . '</span>';
518
-    }
519
-
520
-
521
-
522
-    /**
523
-     * @param string $content
524
-     * @return string
525
-     */
526
-    protected static function orange_span($content = '')
527
-    {
528
-        if (EEH_Debug_Tools::plainOutput()) {
529
-            return $content;
530
-        }
531
-        return '<span style="color:#E76700">' . $content . '</span>';
532
-    }
533
-
534
-
535
-
536
-    /**
537
-     * @param mixed $var
538
-     * @return string
539
-     */
540
-    protected static function pre_span($var)
541
-    {
542
-        ob_start();
543
-        var_dump($var);
544
-        $var = ob_get_clean();
545
-        if (EEH_Debug_Tools::plainOutput()) {
546
-            return $var;
547
-        }
548
-        return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">' . $var . '</pre>';
549
-    }
550
-
551
-
552
-
553
-    /**
554
-     * @param mixed      $var
555
-     * @param string     $var_name
556
-     * @param string     $file
557
-     * @param int|string $line
558
-     * @param int|string $heading_tag
559
-     * @param bool       $die
560
-     */
561
-    public static function printr(
562
-        $var,
563
-        $var_name = '',
564
-        $file = '',
565
-        $line = '',
566
-        $heading_tag = 5,
567
-        $die = false
568
-    ) {
569
-        // return;
570
-        $file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
571
-        if (empty($var) && empty($var_name)) {
572
-            $var = $file;
573
-            $var_name = "line $line";
574
-            $file = '';
575
-            $line = '';
576
-        }
577
-        $margin = is_admin() ? ' 180px' : '0';
578
-        // $print_r = false;
579
-        if (is_string($var)) {
580
-            EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
581
-            return;
582
-        }
583
-        if (is_object($var)) {
584
-            $var_name = ! $var_name ? 'object' : $var_name;
585
-            // $print_r = true;
586
-        } elseif (is_array($var)) {
587
-            $var_name = ! $var_name ? 'array' : $var_name;
588
-            // $print_r = true;
589
-        } elseif (is_numeric($var)) {
590
-            $var_name = ! $var_name ? 'numeric' : $var_name;
591
-        } elseif ($var === null) {
592
-            $var_name = ! $var_name ? 'null' : $var_name;
593
-        }
594
-        $var_name = ucwords(str_replace(array('$', '_'), array('', ' '), $var_name));
595
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
596
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
597
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
598
-        $result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
599
-            EEH_Debug_Tools::pre_span($var)
600
-        );
601
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
602
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
603
-        if ($die) {
604
-            die($result);
605
-        }
606
-        echo $result;
607
-    }
608
-
609
-
610
-    private static function lineBreak($lines = 1): string
611
-    {
612
-        $linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
613
-        return str_repeat($linebreak, $lines);
614
-    }
615
-
616
-
617
-    public static function shortClassName(string $fqcn): string
618
-    {
619
-        return substr(strrchr($fqcn, '\\'), 1);
620
-    }
621
-
622
-
623
-
624
-    /******************** deprecated ********************/
625
-
626
-
627
-
628
-    /**
629
-     * @deprecated 4.9.39.rc.034
630
-     */
631
-    public function reset_times()
632
-    {
633
-        Benchmark::resetTimes();
634
-    }
635
-
636
-
637
-
638
-    /**
639
-     * @deprecated 4.9.39.rc.034
640
-     * @param null $timer_name
641
-     */
642
-    public function start_timer($timer_name = null)
643
-    {
644
-        Benchmark::startTimer($timer_name);
645
-    }
646
-
647
-
648
-
649
-    /**
650
-     * @deprecated 4.9.39.rc.034
651
-     * @param string $timer_name
652
-     */
653
-    public function stop_timer($timer_name = '')
654
-    {
655
-        Benchmark::stopTimer($timer_name);
656
-    }
657
-
658
-
659
-
660
-    /**
661
-     * @deprecated 4.9.39.rc.034
662
-     * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
663
-     * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
664
-     * @return void
665
-     */
666
-    public function measure_memory($label, $output_now = false)
667
-    {
668
-        Benchmark::measureMemory($label, $output_now);
669
-    }
670
-
671
-
672
-
673
-    /**
674
-     * @deprecated 4.9.39.rc.034
675
-     * @param int $size
676
-     * @return string
677
-     */
678
-    public function convert($size)
679
-    {
680
-        return Benchmark::convert($size);
681
-    }
682
-
683
-
684
-
685
-    /**
686
-     * @deprecated 4.9.39.rc.034
687
-     * @param bool $output_now
688
-     * @return string
689
-     */
690
-    public function show_times($output_now = true)
691
-    {
692
-        return Benchmark::displayResults($output_now);
693
-    }
16
+	/**
17
+	 *    instance of the EEH_Autoloader object
18
+	 *
19
+	 * @var    $_instance
20
+	 * @access    private
21
+	 */
22
+	private static $_instance;
23
+
24
+	/**
25
+	 * @var array
26
+	 */
27
+	protected $_memory_usage_points = array();
28
+
29
+
30
+
31
+	/**
32
+	 * @singleton method used to instantiate class object
33
+	 * @access    public
34
+	 * @return EEH_Debug_Tools
35
+	 */
36
+	public static function instance()
37
+	{
38
+		// check if class object is instantiated, and instantiated properly
39
+		if (! self::$_instance instanceof EEH_Debug_Tools) {
40
+			self::$_instance = new self();
41
+		}
42
+		return self::$_instance;
43
+	}
44
+
45
+
46
+
47
+	/**
48
+	 * private class constructor
49
+	 */
50
+	private function __construct()
51
+	{
52
+		// load Kint PHP debugging library
53
+		if (! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')) {
54
+			// despite EE4 having a check for an existing copy of the Kint debugging class,
55
+			// if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
56
+			// then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
57
+			// so we've moved it to our test folder so that it is not included with production releases
58
+			// plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
59
+			require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
60
+		}
61
+		// if ( ! defined('DOING_AJAX') || $_REQUEST['noheader'] !== 'true' || ! isset( $_REQUEST['noheader'], $_REQUEST['TB_iframe'] ) ) {
62
+		// add_action( 'shutdown', array($this,'espresso_session_footer_dump') );
63
+		// }
64
+		$plugin = basename(EE_PLUGIN_DIR_PATH);
65
+		add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
66
+		add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
67
+		add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
68
+	}
69
+
70
+
71
+
72
+	/**
73
+	 *    show_db_name
74
+	 *
75
+	 * @return void
76
+	 */
77
+	public static function show_db_name()
78
+	{
79
+		if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
80
+			echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
81
+				 . DB_NAME
82
+				 . '</p>';
83
+		}
84
+		if (EE_DEBUG) {
85
+			Benchmark::displayResults();
86
+		}
87
+	}
88
+
89
+
90
+
91
+	/**
92
+	 *    dump EE_Session object at bottom of page after everything else has happened
93
+	 *
94
+	 * @return void
95
+	 */
96
+	public function espresso_session_footer_dump()
97
+	{
98
+		if (
99
+			(defined('WP_DEBUG') && WP_DEBUG)
100
+			&& ! defined('DOING_AJAX')
101
+			&& class_exists('Kint')
102
+			&& function_exists('wp_get_current_user')
103
+			&& current_user_can('update_core')
104
+			&& class_exists('EE_Registry')
105
+		) {
106
+			Kint::dump(EE_Registry::instance()->SSN->id());
107
+			Kint::dump(EE_Registry::instance()->SSN);
108
+			//          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
109
+			$this->espresso_list_hooked_functions();
110
+			Benchmark::displayResults();
111
+		}
112
+	}
113
+
114
+
115
+
116
+	/**
117
+	 *    List All Hooked Functions
118
+	 *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
119
+	 *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
120
+	 *
121
+	 * @param string $tag
122
+	 * @return void
123
+	 */
124
+	public function espresso_list_hooked_functions($tag = '')
125
+	{
126
+		global $wp_filter;
127
+		echo '<br/><br/><br/><h3>Hooked Functions</h3>';
128
+		if ($tag) {
129
+			$hook[ $tag ] = $wp_filter[ $tag ];
130
+			if (! is_array($hook[ $tag ])) {
131
+				trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
132
+				return;
133
+			}
134
+			echo '<h5>For Tag: ' . $tag . '</h5>';
135
+		} else {
136
+			$hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
137
+			ksort($hook);
138
+		}
139
+		foreach ($hook as $tag_name => $priorities) {
140
+			echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>$tag_name</strong><br />";
141
+			ksort($priorities);
142
+			foreach ($priorities as $priority => $function) {
143
+				echo $priority;
144
+				foreach ($function as $name => $properties) {
145
+					echo "\t$name<br />";
146
+				}
147
+			}
148
+		}
149
+	}
150
+
151
+
152
+
153
+	/**
154
+	 *    registered_filter_callbacks
155
+	 *
156
+	 * @param string $hook_name
157
+	 * @return array
158
+	 */
159
+	public static function registered_filter_callbacks($hook_name = '')
160
+	{
161
+		$filters = array();
162
+		global $wp_filter;
163
+		if (isset($wp_filter[ $hook_name ])) {
164
+			$filters[ $hook_name ] = array();
165
+			foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
166
+				$filters[ $hook_name ][ $priority ] = array();
167
+				foreach ($callbacks as $callback) {
168
+					$filters[ $hook_name ][ $priority ][] = $callback['function'];
169
+				}
170
+			}
171
+		}
172
+		return $filters;
173
+	}
174
+
175
+
176
+
177
+	/**
178
+	 *    captures plugin activation errors for debugging
179
+	 *
180
+	 * @return void
181
+	 * @throws EE_Error
182
+	 */
183
+	public static function ee_plugin_activation_errors()
184
+	{
185
+		if (WP_DEBUG) {
186
+			$activation_errors = ob_get_contents();
187
+			if (empty($activation_errors)) {
188
+				return;
189
+			}
190
+			$activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
191
+			espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
192
+			if (class_exists('EEH_File')) {
193
+				try {
194
+					EEH_File::ensure_file_exists_and_is_writable(
195
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
196
+					);
197
+					EEH_File::write_to_file(
198
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
199
+						$activation_errors
200
+					);
201
+				} catch (EE_Error $e) {
202
+					EE_Error::add_error(
203
+						sprintf(
204
+							__(
205
+								'The Event Espresso activation errors file could not be setup because: %s',
206
+								'event_espresso'
207
+							),
208
+							$e->getMessage()
209
+						),
210
+						__FILE__,
211
+						__FUNCTION__,
212
+						__LINE__
213
+					);
214
+				}
215
+			} else {
216
+				// old school attempt
217
+				file_put_contents(
218
+					EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
219
+					$activation_errors
220
+				);
221
+			}
222
+			$activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
223
+			update_option('ee_plugin_activation_errors', $activation_errors);
224
+		}
225
+	}
226
+
227
+
228
+
229
+	/**
230
+	 * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
231
+	 * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
232
+	 * or we want to make sure they use something the right way.
233
+	 *
234
+	 * @access public
235
+	 * @param string $function      The function that was called
236
+	 * @param string $message       A message explaining what has been done incorrectly
237
+	 * @param string $version       The version of Event Espresso where the error was added
238
+	 * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
239
+	 *                              for a deprecated function. This allows deprecation to occur during one version,
240
+	 *                              but not have any notices appear until a later version. This allows developers
241
+	 *                              extra time to update their code before notices appear.
242
+	 * @param int    $error_type
243
+	 * @uses   trigger_error()
244
+	 */
245
+	public function doing_it_wrong(
246
+		$function,
247
+		$message,
248
+		$version,
249
+		$applies_when = '',
250
+		$error_type = null
251
+	) {
252
+		$applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
253
+		$error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
254
+		// because we swapped the parameter order around for the last two params,
255
+		// let's verify that some third party isn't still passing an error type value for the third param
256
+		if (is_int($applies_when)) {
257
+			$error_type = $applies_when;
258
+			$applies_when = espresso_version();
259
+		}
260
+		// if not displaying notices yet, then just leave
261
+		if (version_compare(espresso_version(), $applies_when, '<')) {
262
+			return;
263
+		}
264
+		do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
265
+		$version = $version === null
266
+			? ''
267
+			: sprintf(
268
+				__('(This message was added in version %s of Event Espresso)', 'event_espresso'),
269
+				$version
270
+			);
271
+		$error_message = sprintf(
272
+			esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
273
+			$function,
274
+			'<strong>',
275
+			'</strong>',
276
+			$message,
277
+			$version
278
+		);
279
+		// don't trigger error if doing ajax,
280
+		// instead we'll add a transient EE_Error notice that in theory should show on the next request.
281
+		if (defined('DOING_AJAX') && DOING_AJAX) {
282
+			$error_message .= ' ' . esc_html__(
283
+				'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
284
+				'event_espresso'
285
+			);
286
+			$error_message .= '<ul><li>';
287
+			$error_message .= implode('</li><li>', EE_Registry::instance()->REQ->params());
288
+			$error_message .= '</ul>';
289
+			EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
290
+			// now we set this on the transient so it shows up on the next request.
291
+			EE_Error::get_notices(false, true);
292
+		} else {
293
+			trigger_error($error_message, $error_type);
294
+		}
295
+	}
296
+
297
+
298
+
299
+
300
+	/**
301
+	 * Logger helpers
302
+	 */
303
+	/**
304
+	 * debug
305
+	 *
306
+	 * @param string $class
307
+	 * @param string $func
308
+	 * @param string $line
309
+	 * @param array  $info
310
+	 * @param bool   $display_request
311
+	 * @param string $debug_index
312
+	 * @param string $debug_key
313
+	 * @throws EE_Error
314
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
315
+	 */
316
+	public static function log(
317
+		$class = '',
318
+		$func = '',
319
+		$line = '',
320
+		$info = array(),
321
+		$display_request = false,
322
+		$debug_index = '',
323
+		$debug_key = 'EE_DEBUG_SPCO'
324
+	) {
325
+		if (WP_DEBUG) {
326
+			$debug_key = $debug_key . '_' . EE_Session::instance()->id();
327
+			$debug_data = get_option($debug_key, array());
328
+			$default_data = array(
329
+				$class => $func . '() : ' . $line,
330
+				'REQ'  => $display_request ? $_REQUEST : '',
331
+			);
332
+			// don't serialize objects
333
+			$info = self::strip_objects($info);
334
+			$index = ! empty($debug_index) ? $debug_index : 0;
335
+			if (! isset($debug_data[ $index ])) {
336
+				$debug_data[ $index ] = array();
337
+			}
338
+			$debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
339
+			update_option($debug_key, $debug_data);
340
+		}
341
+	}
342
+
343
+
344
+
345
+	/**
346
+	 * strip_objects
347
+	 *
348
+	 * @param array $info
349
+	 * @return array
350
+	 */
351
+	public static function strip_objects($info = array())
352
+	{
353
+		foreach ($info as $key => $value) {
354
+			if (is_array($value)) {
355
+				$info[ $key ] = self::strip_objects($value);
356
+			} elseif (is_object($value)) {
357
+				$object_class = get_class($value);
358
+				$info[ $object_class ] = array();
359
+				$info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
360
+				if (method_exists($value, 'ID')) {
361
+					$info[ $object_class ]['ID'] = $value->ID();
362
+				}
363
+				if (method_exists($value, 'status')) {
364
+					$info[ $object_class ]['status'] = $value->status();
365
+				} elseif (method_exists($value, 'status_ID')) {
366
+					$info[ $object_class ]['status'] = $value->status_ID();
367
+				}
368
+				unset($info[ $key ]);
369
+			}
370
+		}
371
+		return (array) $info;
372
+	}
373
+
374
+
375
+
376
+	/**
377
+	 * @param mixed      $var
378
+	 * @param string     $var_name
379
+	 * @param string     $file
380
+	 * @param int|string $line
381
+	 * @param int|string $heading_tag
382
+	 * @param bool       $die
383
+	 * @param string     $margin
384
+	 */
385
+	public static function printv(
386
+		$var,
387
+		$var_name = '',
388
+		$file = '',
389
+		$line = '',
390
+		$heading_tag = 5,
391
+		$die = false,
392
+		$margin = ''
393
+	) {
394
+		$var_name = ! $var_name ? 'string' : $var_name;
395
+		$var_name = ucwords(str_replace('$', '', $var_name));
396
+		$is_method = method_exists($var_name, $var);
397
+		$var_name = ucwords(str_replace('_', ' ', $var_name));
398
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
399
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
400
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
401
+		$result .= $is_method
402
+			? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
403
+			: EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
404
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
405
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
406
+		if ($die) {
407
+			die($result);
408
+		}
409
+		echo $result;
410
+	}
411
+
412
+
413
+	protected static function headingTag($heading_tag)
414
+	{
415
+		$heading_tag = absint($heading_tag);
416
+		return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
417
+	}
418
+
419
+
420
+	protected static function headingSpacer($heading_tag)
421
+	{
422
+		return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
423
+			? "\n"
424
+			: '';
425
+	}
426
+
427
+
428
+	protected static function plainOutput()
429
+	{
430
+		return defined('EE_TESTS_DIR')
431
+			   || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
432
+			   || (
433
+				   isset($_SERVER['REQUEST_URI'])
434
+				   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
435
+			   );
436
+	}
437
+
438
+
439
+	/**
440
+	 * @param string $var_name
441
+	 * @param string $heading_tag
442
+	 * @param string $margin
443
+	 * @param int    $line
444
+	 * @return string
445
+	 */
446
+	protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
447
+	{
448
+		if (EEH_Debug_Tools::plainOutput()) {
449
+			switch ($heading_tag) {
450
+				case 'h1':
451
+					$line_breaks = EEH_Debug_Tools::lineBreak(3);
452
+					break;
453
+				case 'h2':
454
+					$line_breaks = EEH_Debug_Tools::lineBreak(2);
455
+					break;
456
+				default:
457
+					$line_breaks = EEH_Debug_Tools::lineBreak();
458
+					break;
459
+			}
460
+			return "{$line_breaks}{$line}) {$var_name}";
461
+		}
462
+		$margin = "25px 0 0 {$margin}";
463
+		return '<' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . ';"><b>' . $var_name . '</b>';
464
+	}
465
+
466
+
467
+
468
+	/**
469
+	 * @param string $heading_tag
470
+	 * @return string
471
+	 */
472
+	protected static function headingX($heading_tag = 'h5')
473
+	{
474
+		if (EEH_Debug_Tools::plainOutput()) {
475
+			return '';
476
+		}
477
+		return '</' . $heading_tag . '>';
478
+	}
479
+
480
+
481
+
482
+	/**
483
+	 * @param string $content
484
+	 * @return string
485
+	 */
486
+	protected static function grey_span($content = '')
487
+	{
488
+		if (EEH_Debug_Tools::plainOutput()) {
489
+			return $content;
490
+		}
491
+		return '<span style="color:#999">' . $content . '</span>';
492
+	}
493
+
494
+
495
+
496
+	/**
497
+	 * @param string $file
498
+	 * @param int    $line
499
+	 * @return string
500
+	 */
501
+	protected static function file_and_line($file, $line, $heading_tag)
502
+	{
503
+		if ($file === '' || $line === '') {
504
+			return '';
505
+		}
506
+		$file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
507
+		if (EEH_Debug_Tools::plainOutput()) {
508
+			if ($heading_tag === 'h1' || $heading_tag === 'h2') {
509
+				return " ({$file})";
510
+			}
511
+			return '';
512
+		}
513
+		return '<br /><span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
514
+			   . $file
515
+			   . '<br />line no: '
516
+			   . $line
517
+			   . '</span>';
518
+	}
519
+
520
+
521
+
522
+	/**
523
+	 * @param string $content
524
+	 * @return string
525
+	 */
526
+	protected static function orange_span($content = '')
527
+	{
528
+		if (EEH_Debug_Tools::plainOutput()) {
529
+			return $content;
530
+		}
531
+		return '<span style="color:#E76700">' . $content . '</span>';
532
+	}
533
+
534
+
535
+
536
+	/**
537
+	 * @param mixed $var
538
+	 * @return string
539
+	 */
540
+	protected static function pre_span($var)
541
+	{
542
+		ob_start();
543
+		var_dump($var);
544
+		$var = ob_get_clean();
545
+		if (EEH_Debug_Tools::plainOutput()) {
546
+			return $var;
547
+		}
548
+		return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">' . $var . '</pre>';
549
+	}
550
+
551
+
552
+
553
+	/**
554
+	 * @param mixed      $var
555
+	 * @param string     $var_name
556
+	 * @param string     $file
557
+	 * @param int|string $line
558
+	 * @param int|string $heading_tag
559
+	 * @param bool       $die
560
+	 */
561
+	public static function printr(
562
+		$var,
563
+		$var_name = '',
564
+		$file = '',
565
+		$line = '',
566
+		$heading_tag = 5,
567
+		$die = false
568
+	) {
569
+		// return;
570
+		$file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
571
+		if (empty($var) && empty($var_name)) {
572
+			$var = $file;
573
+			$var_name = "line $line";
574
+			$file = '';
575
+			$line = '';
576
+		}
577
+		$margin = is_admin() ? ' 180px' : '0';
578
+		// $print_r = false;
579
+		if (is_string($var)) {
580
+			EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
581
+			return;
582
+		}
583
+		if (is_object($var)) {
584
+			$var_name = ! $var_name ? 'object' : $var_name;
585
+			// $print_r = true;
586
+		} elseif (is_array($var)) {
587
+			$var_name = ! $var_name ? 'array' : $var_name;
588
+			// $print_r = true;
589
+		} elseif (is_numeric($var)) {
590
+			$var_name = ! $var_name ? 'numeric' : $var_name;
591
+		} elseif ($var === null) {
592
+			$var_name = ! $var_name ? 'null' : $var_name;
593
+		}
594
+		$var_name = ucwords(str_replace(array('$', '_'), array('', ' '), $var_name));
595
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
596
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
597
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
598
+		$result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
599
+			EEH_Debug_Tools::pre_span($var)
600
+		);
601
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
602
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
603
+		if ($die) {
604
+			die($result);
605
+		}
606
+		echo $result;
607
+	}
608
+
609
+
610
+	private static function lineBreak($lines = 1): string
611
+	{
612
+		$linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
613
+		return str_repeat($linebreak, $lines);
614
+	}
615
+
616
+
617
+	public static function shortClassName(string $fqcn): string
618
+	{
619
+		return substr(strrchr($fqcn, '\\'), 1);
620
+	}
621
+
622
+
623
+
624
+	/******************** deprecated ********************/
625
+
626
+
627
+
628
+	/**
629
+	 * @deprecated 4.9.39.rc.034
630
+	 */
631
+	public function reset_times()
632
+	{
633
+		Benchmark::resetTimes();
634
+	}
635
+
636
+
637
+
638
+	/**
639
+	 * @deprecated 4.9.39.rc.034
640
+	 * @param null $timer_name
641
+	 */
642
+	public function start_timer($timer_name = null)
643
+	{
644
+		Benchmark::startTimer($timer_name);
645
+	}
646
+
647
+
648
+
649
+	/**
650
+	 * @deprecated 4.9.39.rc.034
651
+	 * @param string $timer_name
652
+	 */
653
+	public function stop_timer($timer_name = '')
654
+	{
655
+		Benchmark::stopTimer($timer_name);
656
+	}
657
+
658
+
659
+
660
+	/**
661
+	 * @deprecated 4.9.39.rc.034
662
+	 * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
663
+	 * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
664
+	 * @return void
665
+	 */
666
+	public function measure_memory($label, $output_now = false)
667
+	{
668
+		Benchmark::measureMemory($label, $output_now);
669
+	}
670
+
671
+
672
+
673
+	/**
674
+	 * @deprecated 4.9.39.rc.034
675
+	 * @param int $size
676
+	 * @return string
677
+	 */
678
+	public function convert($size)
679
+	{
680
+		return Benchmark::convert($size);
681
+	}
682
+
683
+
684
+
685
+	/**
686
+	 * @deprecated 4.9.39.rc.034
687
+	 * @param bool $output_now
688
+	 * @return string
689
+	 */
690
+	public function show_times($output_now = true)
691
+	{
692
+		return Benchmark::displayResults($output_now);
693
+	}
694 694
 
695 695
 
696 696
 
697
-    /**
698
-     * @deprecated 4.9.39.rc.034
699
-     * @param string $timer_name
700
-     * @param float  $total_time
701
-     * @return string
702
-     */
703
-    public function format_time($timer_name, $total_time)
704
-    {
705
-        return Benchmark::formatTime($timer_name, $total_time);
706
-    }
697
+	/**
698
+	 * @deprecated 4.9.39.rc.034
699
+	 * @param string $timer_name
700
+	 * @param float  $total_time
701
+	 * @return string
702
+	 */
703
+	public function format_time($timer_name, $total_time)
704
+	{
705
+		return Benchmark::formatTime($timer_name, $total_time);
706
+	}
707 707
 }
708 708
 
709 709
 
@@ -713,31 +713,31 @@  discard block
 block discarded – undo
713 713
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
714 714
  */
715 715
 if (class_exists('Kint') && ! function_exists('dump_wp_query')) {
716
-    function dump_wp_query()
717
-    {
718
-        global $wp_query;
719
-        d($wp_query);
720
-    }
716
+	function dump_wp_query()
717
+	{
718
+		global $wp_query;
719
+		d($wp_query);
720
+	}
721 721
 }
722 722
 /**
723 723
  * borrowed from Kint Debugger
724 724
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
725 725
  */
726 726
 if (class_exists('Kint') && ! function_exists('dump_wp')) {
727
-    function dump_wp()
728
-    {
729
-        global $wp;
730
-        d($wp);
731
-    }
727
+	function dump_wp()
728
+	{
729
+		global $wp;
730
+		d($wp);
731
+	}
732 732
 }
733 733
 /**
734 734
  * borrowed from Kint Debugger
735 735
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
736 736
  */
737 737
 if (class_exists('Kint') && ! function_exists('dump_post')) {
738
-    function dump_post()
739
-    {
740
-        global $post;
741
-        d($post);
742
-    }
738
+	function dump_post()
739
+	{
740
+		global $post;
741
+		d($post);
742
+	}
743 743
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +2100 added lines, -2100 removed lines patch added patch discarded remove patch
@@ -21,2104 +21,2104 @@
 block discarded – undo
21 21
  */
22 22
 class EEH_Line_Item
23 23
 {
24
-    /**
25
-     * @var EE_Line_Item[]
26
-    */
27
-    private static $global_taxes;
28
-
29
-    /**
30
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
31
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
32
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
33
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
34
-     * to keep the registration final prices in-sync with the transaction's total.
35
-     *
36
-     * @param EE_Line_Item $parent_line_item
37
-     * @param string       $name
38
-     * @param float        $unit_price
39
-     * @param string       $description
40
-     * @param int          $quantity
41
-     * @param boolean      $taxable
42
-     * @param boolean      $code if set to a value, ensures there is only one line item with that code
43
-     * @return boolean success
44
-     * @throws EE_Error
45
-     * @throws InvalidArgumentException
46
-     * @throws InvalidDataTypeException
47
-     * @throws InvalidInterfaceException
48
-     * @throws ReflectionException
49
-     */
50
-    public static function add_unrelated_item(
51
-        EE_Line_Item $parent_line_item,
52
-        $name,
53
-        $unit_price,
54
-        $description = '',
55
-        $quantity = 1,
56
-        $taxable = false,
57
-        $code = null
58
-    ) {
59
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
60
-        $line_item = EE_Line_Item::new_instance(array(
61
-            'LIN_name'       => $name,
62
-            'LIN_desc'       => $description,
63
-            'LIN_unit_price' => $unit_price,
64
-            'LIN_quantity'   => $quantity,
65
-            'LIN_percent'    => null,
66
-            'LIN_is_taxable' => $taxable,
67
-            'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
68
-            'LIN_total'      => (float) $unit_price * (int) $quantity,
69
-            'LIN_type'       => EEM_Line_Item::type_line_item,
70
-            'LIN_code'       => $code,
71
-        ));
72
-        $line_item = apply_filters(
73
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
74
-            $line_item,
75
-            $parent_line_item
76
-        );
77
-        return self::add_item($parent_line_item, $line_item);
78
-    }
79
-
80
-
81
-    /**
82
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
83
-     * in the correct spot in the line item tree. Does not automatically
84
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
85
-     * registrations' final prices (which should probably change because of this).
86
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
87
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
88
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
89
-     *
90
-     * @param EE_Line_Item $parent_line_item
91
-     * @param string       $name
92
-     * @param float        $percentage_amount
93
-     * @param string       $description
94
-     * @param boolean      $taxable
95
-     * @return boolean success
96
-     * @throws EE_Error
97
-     */
98
-    public static function add_percentage_based_item(
99
-        EE_Line_Item $parent_line_item,
100
-        $name,
101
-        $percentage_amount,
102
-        $description = '',
103
-        $taxable = false
104
-    ) {
105
-        $line_item = EE_Line_Item::new_instance(array(
106
-            'LIN_name'       => $name,
107
-            'LIN_desc'       => $description,
108
-            'LIN_unit_price' => 0,
109
-            'LIN_percent'    => $percentage_amount,
110
-            'LIN_quantity'   => 1,
111
-            'LIN_is_taxable' => $taxable,
112
-            'LIN_total'      => (float) ($percentage_amount * ($parent_line_item->total() / 100)),
113
-            'LIN_type'       => EEM_Line_Item::type_line_item,
114
-            'LIN_parent'     => $parent_line_item->ID(),
115
-        ));
116
-        $line_item = apply_filters(
117
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
118
-            $line_item
119
-        );
120
-        return $parent_line_item->add_child_line_item($line_item, false);
121
-    }
122
-
123
-
124
-    /**
125
-     * Returns the new line item created by adding a purchase of the ticket
126
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
127
-     * If this ticket has already been purchased, just increments its count.
128
-     * Automatically re-calculates the line item totals and updates the related transaction. But
129
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
130
-     * should probably change because of this).
131
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
132
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
133
-     *
134
-     * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
135
-     * @param EE_Ticket    $ticket
136
-     * @param int          $qty
137
-     * @return EE_Line_Item
138
-     * @throws EE_Error
139
-     * @throws InvalidArgumentException
140
-     * @throws InvalidDataTypeException
141
-     * @throws InvalidInterfaceException
142
-     * @throws ReflectionException
143
-     */
144
-    public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
145
-    {
146
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
147
-            throw new EE_Error(
148
-                sprintf(
149
-                    esc_html__(
150
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
151
-                        'event_espresso'
152
-                    ),
153
-                    $ticket->ID(),
154
-                    $total_line_item->ID()
155
-                )
156
-            );
157
-        }
158
-        // either increment the qty for an existing ticket
159
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
160
-        // or add a new one
161
-        if (! $line_item instanceof EE_Line_Item) {
162
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
163
-        }
164
-        $total_line_item->recalculate_total_including_taxes();
165
-        return $line_item;
166
-    }
167
-
168
-
169
-    /**
170
-     * Returns the new line item created by adding a purchase of the ticket
171
-     *
172
-     * @param EE_Line_Item $total_line_item
173
-     * @param EE_Ticket    $ticket
174
-     * @param int          $qty
175
-     * @return EE_Line_Item
176
-     * @throws EE_Error
177
-     * @throws InvalidArgumentException
178
-     * @throws InvalidDataTypeException
179
-     * @throws InvalidInterfaceException
180
-     * @throws ReflectionException
181
-     */
182
-    public static function increment_ticket_qty_if_already_in_cart(
183
-        EE_Line_Item $total_line_item,
184
-        EE_Ticket $ticket,
185
-        $qty = 1
186
-    ) {
187
-        $line_item = null;
188
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
189
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
190
-            foreach ((array) $ticket_line_items as $ticket_line_item) {
191
-                if (
192
-                    $ticket_line_item instanceof EE_Line_Item
193
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
194
-                ) {
195
-                    $line_item = $ticket_line_item;
196
-                    break;
197
-                }
198
-            }
199
-        }
200
-        if ($line_item instanceof EE_Line_Item) {
201
-            EEH_Line_Item::increment_quantity($line_item, $qty);
202
-            return $line_item;
203
-        }
204
-        return null;
205
-    }
206
-
207
-
208
-    /**
209
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
210
-     * Does NOT save or recalculate other line items totals
211
-     *
212
-     * @param EE_Line_Item $line_item
213
-     * @param int          $qty
214
-     * @return void
215
-     * @throws EE_Error
216
-     * @throws InvalidArgumentException
217
-     * @throws InvalidDataTypeException
218
-     * @throws InvalidInterfaceException
219
-     * @throws ReflectionException
220
-     */
221
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
222
-    {
223
-        if (! $line_item->is_percent()) {
224
-            $qty += $line_item->quantity();
225
-            $line_item->set_quantity($qty);
226
-            $line_item->set_total($line_item->unit_price() * $qty);
227
-            $line_item->save();
228
-        }
229
-        foreach ($line_item->children() as $child) {
230
-            if ($child->is_sub_line_item()) {
231
-                EEH_Line_Item::update_quantity($child, $qty);
232
-            }
233
-        }
234
-    }
235
-
236
-
237
-    /**
238
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
239
-     * Does NOT save or recalculate other line items totals
240
-     *
241
-     * @param EE_Line_Item $line_item
242
-     * @param int          $qty
243
-     * @return void
244
-     * @throws EE_Error
245
-     * @throws InvalidArgumentException
246
-     * @throws InvalidDataTypeException
247
-     * @throws InvalidInterfaceException
248
-     * @throws ReflectionException
249
-     */
250
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
251
-    {
252
-        if (! $line_item->is_percent()) {
253
-            $qty = $line_item->quantity() - $qty;
254
-            $qty = max($qty, 0);
255
-            $line_item->set_quantity($qty);
256
-            $line_item->set_total($line_item->unit_price() * $qty);
257
-            $line_item->save();
258
-        }
259
-        foreach ($line_item->children() as $child) {
260
-            if ($child->is_sub_line_item()) {
261
-                EEH_Line_Item::update_quantity($child, $qty);
262
-            }
263
-        }
264
-    }
265
-
266
-
267
-    /**
268
-     * Updates the line item and its children's quantities to the specified number.
269
-     * Does NOT save them or recalculate totals.
270
-     *
271
-     * @param EE_Line_Item $line_item
272
-     * @param int          $new_quantity
273
-     * @throws EE_Error
274
-     * @throws InvalidArgumentException
275
-     * @throws InvalidDataTypeException
276
-     * @throws InvalidInterfaceException
277
-     * @throws ReflectionException
278
-     */
279
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
280
-    {
281
-        if (! $line_item->is_percent()) {
282
-            $line_item->set_quantity($new_quantity);
283
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
284
-            $line_item->save();
285
-        }
286
-        foreach ($line_item->children() as $child) {
287
-            if ($child->is_sub_line_item()) {
288
-                EEH_Line_Item::update_quantity($child, $new_quantity);
289
-            }
290
-        }
291
-    }
292
-
293
-
294
-    /**
295
-     * Returns the new line item created by adding a purchase of the ticket
296
-     *
297
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
298
-     * @param EE_Ticket    $ticket
299
-     * @param int          $qty
300
-     * @return EE_Line_Item
301
-     * @throws EE_Error
302
-     * @throws InvalidArgumentException
303
-     * @throws InvalidDataTypeException
304
-     * @throws InvalidInterfaceException
305
-     * @throws ReflectionException
306
-     */
307
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
308
-    {
309
-        $datetimes = $ticket->datetimes();
310
-        $first_datetime = reset($datetimes);
311
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
312
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
313
-            $first_datetime_name = $first_datetime->event()->name();
314
-        }
315
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
316
-        // get event subtotal line
317
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
318
-        $taxes = $ticket->tax_price_modifiers();
319
-        // add $ticket to cart
320
-        $line_item = EE_Line_Item::new_instance(array(
321
-            'LIN_name'       => $ticket->name(),
322
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
323
-            'LIN_unit_price' => $ticket->price(),
324
-            'LIN_quantity'   => $qty,
325
-            'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
326
-            'LIN_order'      => count($events_sub_total->children()),
327
-            'LIN_total'      => $ticket->price() * $qty,
328
-            'LIN_type'       => EEM_Line_Item::type_line_item,
329
-            'OBJ_ID'         => $ticket->ID(),
330
-            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
331
-        ));
332
-        $line_item = apply_filters(
333
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
334
-            $line_item
335
-        );
336
-        if (!$line_item instanceof EE_Line_Item) {
337
-            throw new DomainException(
338
-                esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
339
-            );
340
-        }
341
-        $events_sub_total->add_child_line_item($line_item);
342
-        // now add the sub-line items
343
-        $running_total = 0;
344
-        $running_pre_tax_total = 0;
345
-        foreach ($ticket->prices() as $price) {
346
-            $sign = $price->is_discount() ? -1 : 1;
347
-            $price_total = $price->is_percent()
348
-                ? $running_pre_tax_total * $price->amount() / 100
349
-                : $price->amount() * $qty;
350
-            if ($price->is_percent()) {
351
-                $percent = $sign * $price->amount();
352
-                $unit_price = 0;
353
-            } else {
354
-                $percent    = 0;
355
-                $unit_price = $sign * $price->amount();
356
-            }
357
-            $sub_line_item = EE_Line_Item::new_instance(array(
358
-                'LIN_name'       => $price->name(),
359
-                'LIN_desc'       => $price->desc(),
360
-                'LIN_quantity'   => $price->is_percent() ? null : $qty,
361
-                'LIN_is_taxable' => false,
362
-                'LIN_order'      => $price->order(),
363
-                'LIN_total'      => $price_total,
364
-                'LIN_pretax'     => 0,
365
-                'LIN_unit_price' => $unit_price,
366
-                'LIN_percent'    => $percent,
367
-                'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
368
-                'OBJ_ID'         => $price->ID(),
369
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
370
-            ));
371
-            $sub_line_item = apply_filters(
372
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
373
-                $sub_line_item
374
-            );
375
-            $running_total += $sign * $price_total;
376
-            $running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
377
-            $line_item->add_child_line_item($sub_line_item);
378
-        }
379
-        $line_item->setPretaxTotal($running_pre_tax_total);
380
-        return $line_item;
381
-    }
382
-
383
-
384
-    /**
385
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
386
-     * re-calculates the line item totals and updates the related transaction. But
387
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
388
-     * should probably change because of this).
389
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
390
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
391
-     *
392
-     * @param EE_Line_Item $total_line_item
393
-     * @param EE_Line_Item $item to be added
394
-     * @return boolean
395
-     * @throws EE_Error
396
-     * @throws InvalidArgumentException
397
-     * @throws InvalidDataTypeException
398
-     * @throws InvalidInterfaceException
399
-     * @throws ReflectionException
400
-     */
401
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
402
-    {
403
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
404
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
405
-            $success = $pre_tax_subtotal->add_child_line_item($item);
406
-        } else {
407
-            return false;
408
-        }
409
-        $total_line_item->recalculate_total_including_taxes();
410
-        return $success;
411
-    }
412
-
413
-
414
-    /**
415
-     * cancels an existing ticket line item,
416
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
417
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
418
-     *
419
-     * @param EE_Line_Item $ticket_line_item
420
-     * @param int          $qty
421
-     * @return bool success
422
-     * @throws EE_Error
423
-     * @throws InvalidArgumentException
424
-     * @throws InvalidDataTypeException
425
-     * @throws InvalidInterfaceException
426
-     * @throws ReflectionException
427
-     */
428
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
429
-    {
430
-        // validate incoming line_item
431
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
432
-            throw new EE_Error(
433
-                sprintf(
434
-                    esc_html__(
435
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
436
-                        'event_espresso'
437
-                    ),
438
-                    $ticket_line_item->type()
439
-                )
440
-            );
441
-        }
442
-        if ($ticket_line_item->quantity() < $qty) {
443
-            throw new EE_Error(
444
-                sprintf(
445
-                    esc_html__(
446
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
447
-                        'event_espresso'
448
-                    ),
449
-                    $qty,
450
-                    $ticket_line_item->quantity()
451
-                )
452
-            );
453
-        }
454
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
455
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
456
-        foreach ($ticket_line_item->children() as $child_line_item) {
457
-            if (
458
-                $child_line_item->is_sub_line_item()
459
-                && ! $child_line_item->is_percent()
460
-                && ! $child_line_item->is_cancellation()
461
-            ) {
462
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
463
-            }
464
-        }
465
-        // get cancellation sub line item
466
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
467
-            $ticket_line_item,
468
-            EEM_Line_Item::type_cancellation
469
-        );
470
-        $cancellation_line_item = reset($cancellation_line_item);
471
-        // verify that this ticket was indeed previously cancelled
472
-        if ($cancellation_line_item instanceof EE_Line_Item) {
473
-            // increment cancelled quantity
474
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
475
-        } else {
476
-            // create cancellation sub line item
477
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
478
-                'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
479
-                'LIN_desc'       => sprintf(
480
-                    esc_html_x(
481
-                        'Cancelled %1$s : %2$s',
482
-                        'Cancelled Ticket Name : 2015-01-01 11:11',
483
-                        'event_espresso'
484
-                    ),
485
-                    $ticket_line_item->name(),
486
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
487
-                ),
488
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
489
-                'LIN_quantity'   => $qty,
490
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
491
-                'LIN_order'      => count($ticket_line_item->children()),
492
-                'LIN_total'      => 0, // $ticket_line_item->unit_price()
493
-                'LIN_type'       => EEM_Line_Item::type_cancellation,
494
-            ));
495
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
496
-        }
497
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
498
-            // decrement parent line item quantity
499
-            $event_line_item = $ticket_line_item->parent();
500
-            if (
501
-                $event_line_item instanceof EE_Line_Item
502
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
503
-            ) {
504
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
505
-                $event_line_item->save();
506
-            }
507
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
508
-            return true;
509
-        }
510
-        return false;
511
-    }
512
-
513
-
514
-    /**
515
-     * reinstates (un-cancels?) a previously canceled ticket line item,
516
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
517
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
518
-     *
519
-     * @param EE_Line_Item $ticket_line_item
520
-     * @param int          $qty
521
-     * @return bool success
522
-     * @throws EE_Error
523
-     * @throws InvalidArgumentException
524
-     * @throws InvalidDataTypeException
525
-     * @throws InvalidInterfaceException
526
-     * @throws ReflectionException
527
-     */
528
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
529
-    {
530
-        // validate incoming line_item
531
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
532
-            throw new EE_Error(
533
-                sprintf(
534
-                    esc_html__(
535
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
536
-                        'event_espresso'
537
-                    ),
538
-                    $ticket_line_item->type()
539
-                )
540
-            );
541
-        }
542
-        // get cancellation sub line item
543
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
544
-            $ticket_line_item,
545
-            EEM_Line_Item::type_cancellation
546
-        );
547
-        $cancellation_line_item = reset($cancellation_line_item);
548
-        // verify that this ticket was indeed previously cancelled
549
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
550
-            return false;
551
-        }
552
-        if ($cancellation_line_item->quantity() > $qty) {
553
-            // decrement cancelled quantity
554
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
555
-        } elseif ($cancellation_line_item->quantity() === $qty) {
556
-            // decrement cancelled quantity in case anyone still has the object kicking around
557
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
558
-            // delete because quantity will end up as 0
559
-            $cancellation_line_item->delete();
560
-            // and attempt to destroy the object,
561
-            // even though PHP won't actually destroy it until it needs the memory
562
-            unset($cancellation_line_item);
563
-        } else {
564
-            // what ?!?! negative quantity ?!?!
565
-            throw new EE_Error(
566
-                sprintf(
567
-                    esc_html__(
568
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
569
-                        'event_espresso'
570
-                    ),
571
-                    $qty,
572
-                    $cancellation_line_item->quantity()
573
-                )
574
-            );
575
-        }
576
-        // increment ticket quantity
577
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
578
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
579
-            // increment parent line item quantity
580
-            $event_line_item = $ticket_line_item->parent();
581
-            if (
582
-                $event_line_item instanceof EE_Line_Item
583
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
584
-            ) {
585
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
586
-            }
587
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
588
-            return true;
589
-        }
590
-        return false;
591
-    }
592
-
593
-
594
-    /**
595
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
596
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
597
-     *
598
-     * @param EE_Line_Item $line_item
599
-     * @return float
600
-     * @throws EE_Error
601
-     * @throws InvalidArgumentException
602
-     * @throws InvalidDataTypeException
603
-     * @throws InvalidInterfaceException
604
-     * @throws ReflectionException
605
-     */
606
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
607
-    {
608
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
609
-        return $grand_total_line_item->recalculate_total_including_taxes();
610
-    }
611
-
612
-
613
-    /**
614
-     * Gets the line item which contains the subtotal of all the items
615
-     *
616
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
617
-     * @return EE_Line_Item
618
-     * @throws EE_Error
619
-     * @throws InvalidArgumentException
620
-     * @throws InvalidDataTypeException
621
-     * @throws InvalidInterfaceException
622
-     * @throws ReflectionException
623
-     */
624
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
625
-    {
626
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
627
-        return $pre_tax_subtotal instanceof EE_Line_Item
628
-            ? $pre_tax_subtotal
629
-            : self::create_pre_tax_subtotal($total_line_item);
630
-    }
631
-
632
-
633
-    /**
634
-     * Gets the line item for the taxes subtotal
635
-     *
636
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
637
-     * @return EE_Line_Item
638
-     * @throws EE_Error
639
-     * @throws InvalidArgumentException
640
-     * @throws InvalidDataTypeException
641
-     * @throws InvalidInterfaceException
642
-     * @throws ReflectionException
643
-     */
644
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
645
-    {
646
-        $taxes = $total_line_item->get_child_line_item('taxes');
647
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
648
-    }
649
-
650
-
651
-    /**
652
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
653
-     *
654
-     * @param EE_Line_Item   $line_item
655
-     * @param EE_Transaction $transaction
656
-     * @return void
657
-     * @throws EE_Error
658
-     * @throws InvalidArgumentException
659
-     * @throws InvalidDataTypeException
660
-     * @throws InvalidInterfaceException
661
-     * @throws ReflectionException
662
-     */
663
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
664
-    {
665
-        if ($transaction) {
666
-            /** @type EEM_Transaction $EEM_Transaction */
667
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
668
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
669
-            $line_item->set_TXN_ID($TXN_ID);
670
-        }
671
-    }
672
-
673
-
674
-    /**
675
-     * Creates a new default total line item for the transaction,
676
-     * and its tickets subtotal and taxes subtotal line items (and adds the
677
-     * existing taxes as children of the taxes subtotal line item)
678
-     *
679
-     * @param EE_Transaction $transaction
680
-     * @return EE_Line_Item of type total
681
-     * @throws EE_Error
682
-     * @throws InvalidArgumentException
683
-     * @throws InvalidDataTypeException
684
-     * @throws InvalidInterfaceException
685
-     * @throws ReflectionException
686
-     */
687
-    public static function create_total_line_item($transaction = null)
688
-    {
689
-        $total_line_item = EE_Line_Item::new_instance(array(
690
-            'LIN_code' => 'total',
691
-            'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
692
-            'LIN_type' => EEM_Line_Item::type_total,
693
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
694
-        ));
695
-        $total_line_item = apply_filters(
696
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
697
-            $total_line_item
698
-        );
699
-        self::set_TXN_ID($total_line_item, $transaction);
700
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
701
-        self::create_taxes_subtotal($total_line_item, $transaction);
702
-        return $total_line_item;
703
-    }
704
-
705
-
706
-    /**
707
-     * Creates a default items subtotal line item
708
-     *
709
-     * @param EE_Line_Item   $total_line_item
710
-     * @param EE_Transaction $transaction
711
-     * @return EE_Line_Item
712
-     * @throws EE_Error
713
-     * @throws InvalidArgumentException
714
-     * @throws InvalidDataTypeException
715
-     * @throws InvalidInterfaceException
716
-     * @throws ReflectionException
717
-     */
718
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
719
-    {
720
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
721
-            'LIN_code' => 'pre-tax-subtotal',
722
-            'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
723
-            'LIN_type' => EEM_Line_Item::type_sub_total,
724
-        ));
725
-        $pre_tax_line_item = apply_filters(
726
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
727
-            $pre_tax_line_item
728
-        );
729
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
730
-        $total_line_item->add_child_line_item($pre_tax_line_item);
731
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
732
-        return $pre_tax_line_item;
733
-    }
734
-
735
-
736
-    /**
737
-     * Creates a line item for the taxes subtotal and finds all the tax prices
738
-     * and applies taxes to it
739
-     *
740
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
741
-     * @param EE_Transaction $transaction
742
-     * @return EE_Line_Item
743
-     * @throws EE_Error
744
-     * @throws InvalidArgumentException
745
-     * @throws InvalidDataTypeException
746
-     * @throws InvalidInterfaceException
747
-     * @throws ReflectionException
748
-     */
749
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
750
-    {
751
-        $tax_line_item = EE_Line_Item::new_instance(array(
752
-            'LIN_code'  => 'taxes',
753
-            'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
754
-            'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
755
-            'LIN_order' => 1000,// this should always come last
756
-        ));
757
-        $tax_line_item = apply_filters(
758
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
759
-            $tax_line_item
760
-        );
761
-        self::set_TXN_ID($tax_line_item, $transaction);
762
-        $total_line_item->add_child_line_item($tax_line_item);
763
-        // and lastly, add the actual taxes
764
-        self::apply_taxes($total_line_item);
765
-        return $tax_line_item;
766
-    }
767
-
768
-
769
-    /**
770
-     * Creates a default items subtotal line item
771
-     *
772
-     * @param EE_Line_Item   $pre_tax_line_item
773
-     * @param EE_Transaction $transaction
774
-     * @param EE_Event       $event
775
-     * @return EE_Line_Item
776
-     * @throws EE_Error
777
-     * @throws InvalidArgumentException
778
-     * @throws InvalidDataTypeException
779
-     * @throws InvalidInterfaceException
780
-     * @throws ReflectionException
781
-     */
782
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
783
-    {
784
-        $event_line_item = EE_Line_Item::new_instance(array(
785
-            'LIN_code' => self::get_event_code($event),
786
-            'LIN_name' => self::get_event_name($event),
787
-            'LIN_desc' => self::get_event_desc($event),
788
-            'LIN_type' => EEM_Line_Item::type_sub_total,
789
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
790
-            'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
791
-        ));
792
-        $event_line_item = apply_filters(
793
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
794
-            $event_line_item
795
-        );
796
-        self::set_TXN_ID($event_line_item, $transaction);
797
-        $pre_tax_line_item->add_child_line_item($event_line_item);
798
-        return $event_line_item;
799
-    }
800
-
801
-
802
-    /**
803
-     * Gets what the event ticket's code SHOULD be
804
-     *
805
-     * @param EE_Event $event
806
-     * @return string
807
-     * @throws EE_Error
808
-     */
809
-    public static function get_event_code($event)
810
-    {
811
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
812
-    }
813
-
814
-
815
-    /**
816
-     * Gets the event name
817
-     *
818
-     * @param EE_Event $event
819
-     * @return string
820
-     * @throws EE_Error
821
-     */
822
-    public static function get_event_name($event)
823
-    {
824
-        return $event instanceof EE_Event
825
-            ? mb_substr($event->name(), 0, 245)
826
-            : esc_html__('Event', 'event_espresso');
827
-    }
828
-
829
-
830
-    /**
831
-     * Gets the event excerpt
832
-     *
833
-     * @param EE_Event $event
834
-     * @return string
835
-     * @throws EE_Error
836
-     */
837
-    public static function get_event_desc($event)
838
-    {
839
-        return $event instanceof EE_Event ? $event->short_description() : '';
840
-    }
841
-
842
-
843
-    /**
844
-     * Given the grand total line item and a ticket, finds the event sub-total
845
-     * line item the ticket's purchase should be added onto
846
-     *
847
-     * @access public
848
-     * @param EE_Line_Item $grand_total the grand total line item
849
-     * @param EE_Ticket    $ticket
850
-     * @return EE_Line_Item
851
-     * @throws EE_Error
852
-     * @throws InvalidArgumentException
853
-     * @throws InvalidDataTypeException
854
-     * @throws InvalidInterfaceException
855
-     * @throws ReflectionException
856
-     */
857
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
858
-    {
859
-        $first_datetime = $ticket->first_datetime();
860
-        if (! $first_datetime instanceof EE_Datetime) {
861
-            throw new EE_Error(
862
-                sprintf(
863
-                    __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
864
-                    $ticket->ID()
865
-                )
866
-            );
867
-        }
868
-        $event = $first_datetime->event();
869
-        if (! $event instanceof EE_Event) {
870
-            throw new EE_Error(
871
-                sprintf(
872
-                    esc_html__(
873
-                        'The supplied ticket (ID %d) has no event data associated with it.',
874
-                        'event_espresso'
875
-                    ),
876
-                    $ticket->ID()
877
-                )
878
-            );
879
-        }
880
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
881
-        if (! $events_sub_total instanceof EE_Line_Item) {
882
-            throw new EE_Error(
883
-                sprintf(
884
-                    esc_html__(
885
-                        'There is no events sub-total for ticket %s on total line item %d',
886
-                        'event_espresso'
887
-                    ),
888
-                    $ticket->ID(),
889
-                    $grand_total->ID()
890
-                )
891
-            );
892
-        }
893
-        return $events_sub_total;
894
-    }
895
-
896
-
897
-    /**
898
-     * Gets the event line item
899
-     *
900
-     * @param EE_Line_Item $grand_total
901
-     * @param EE_Event     $event
902
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
903
-     * @throws EE_Error
904
-     * @throws InvalidArgumentException
905
-     * @throws InvalidDataTypeException
906
-     * @throws InvalidInterfaceException
907
-     * @throws ReflectionException
908
-     */
909
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
910
-    {
911
-        /** @type EE_Event $event */
912
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
913
-        $event_line_item = null;
914
-        $found = false;
915
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
916
-            // default event subtotal, we should only ever find this the first time this method is called
917
-            if (! $event_line_item->OBJ_ID()) {
918
-                // let's use this! but first... set the event details
919
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
920
-                $found = true;
921
-                break;
922
-            }
923
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
924
-                // found existing line item for this event in the cart, so break out of loop and use this one
925
-                $found = true;
926
-                break;
927
-            }
928
-        }
929
-        if (! $found) {
930
-            // there is no event sub-total yet, so add it
931
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
932
-            // create a new "event" subtotal below that
933
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
934
-            // and set the event details
935
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
936
-        }
937
-        return $event_line_item;
938
-    }
939
-
940
-
941
-    /**
942
-     * Creates a default items subtotal line item
943
-     *
944
-     * @param EE_Line_Item   $event_line_item
945
-     * @param EE_Event       $event
946
-     * @param EE_Transaction $transaction
947
-     * @return void
948
-     * @throws EE_Error
949
-     * @throws InvalidArgumentException
950
-     * @throws InvalidDataTypeException
951
-     * @throws InvalidInterfaceException
952
-     * @throws ReflectionException
953
-     */
954
-    public static function set_event_subtotal_details(
955
-        EE_Line_Item $event_line_item,
956
-        EE_Event $event,
957
-        $transaction = null
958
-    ) {
959
-        if ($event instanceof EE_Event) {
960
-            $event_line_item->set_code(self::get_event_code($event));
961
-            $event_line_item->set_name(self::get_event_name($event));
962
-            $event_line_item->set_desc(self::get_event_desc($event));
963
-            $event_line_item->set_OBJ_ID($event->ID());
964
-        }
965
-        self::set_TXN_ID($event_line_item, $transaction);
966
-    }
967
-
968
-
969
-    /**
970
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
971
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
972
-     * any old taxes are removed
973
-     *
974
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
975
-     * @param bool         $update_txn_status
976
-     * @return bool
977
-     * @throws EE_Error
978
-     * @throws InvalidArgumentException
979
-     * @throws InvalidDataTypeException
980
-     * @throws InvalidInterfaceException
981
-     * @throws ReflectionException
982
-     * @throws RuntimeException
983
-     */
984
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
985
-    {
986
-        $total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
987
-        $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
988
-        $apply_global_taxes = false;
989
-        foreach ($ticket_line_items as $ticket_line_item) {
990
-            if ($ticket_line_item instanceof EE_Line_Item && $ticket_line_item->is_taxable()) {
991
-                $apply_global_taxes = true;
992
-                break;
993
-            }
994
-        }
995
-        if (! $apply_global_taxes) {
996
-            return false;
997
-        }
998
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
999
-        // just to be safe, remove its old tax line items
1000
-        $deleted = $taxes_line_item->delete_children_line_items();
1001
-        $updates = false;
1002
-        // loop thru taxes
1003
-        $global_taxes = EEH_Line_Item::getGlobalTaxes();
1004
-        foreach ($global_taxes as $order => $taxes) {
1005
-            foreach ($taxes as $tax) {
1006
-                if ($tax instanceof EE_Price) {
1007
-                    $tax_line_item = EE_Line_Item::new_instance(
1008
-                        array(
1009
-                            'LIN_name'       => $tax->name(),
1010
-                            'LIN_desc'       => $tax->desc(),
1011
-                            'LIN_percent'    => $tax->amount(),
1012
-                            'LIN_is_taxable' => false,
1013
-                            'LIN_order'      => $order,
1014
-                            'LIN_total'      => 0,
1015
-                            'LIN_type'       => EEM_Line_Item::type_tax,
1016
-                            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1017
-                            'OBJ_ID'         => $tax->ID(),
1018
-                        )
1019
-                    );
1020
-                    $tax_line_item = apply_filters(
1021
-                        'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1022
-                        $tax_line_item
1023
-                    );
1024
-                    $updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1025
-                }
1026
-            }
1027
-        }
1028
-        // only recalculate totals if something changed
1029
-        if ($deleted || $updates) {
1030
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1031
-            return true;
1032
-        }
1033
-        return false;
1034
-    }
1035
-
1036
-
1037
-    /**
1038
-     * Ensures that taxes have been applied to the order, if not applies them.
1039
-     * Returns the total amount of tax
1040
-     *
1041
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1042
-     * @return float
1043
-     * @throws EE_Error
1044
-     * @throws InvalidArgumentException
1045
-     * @throws InvalidDataTypeException
1046
-     * @throws InvalidInterfaceException
1047
-     * @throws ReflectionException
1048
-     */
1049
-    public static function ensure_taxes_applied($total_line_item)
1050
-    {
1051
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1052
-        if (! $taxes_subtotal->children()) {
1053
-            self::apply_taxes($total_line_item);
1054
-        }
1055
-        return $taxes_subtotal->total();
1056
-    }
1057
-
1058
-
1059
-    /**
1060
-     * Deletes ALL children of the passed line item
1061
-     *
1062
-     * @param EE_Line_Item $parent_line_item
1063
-     * @return bool
1064
-     * @throws EE_Error
1065
-     * @throws InvalidArgumentException
1066
-     * @throws InvalidDataTypeException
1067
-     * @throws InvalidInterfaceException
1068
-     * @throws ReflectionException
1069
-     */
1070
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1071
-    {
1072
-        $deleted = 0;
1073
-        foreach ($parent_line_item->children() as $child_line_item) {
1074
-            if ($child_line_item instanceof EE_Line_Item) {
1075
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1076
-                if ($child_line_item->ID()) {
1077
-                    $child_line_item->delete();
1078
-                    unset($child_line_item);
1079
-                } else {
1080
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1081
-                }
1082
-                $deleted++;
1083
-            }
1084
-        }
1085
-        return $deleted;
1086
-    }
1087
-
1088
-
1089
-    /**
1090
-     * Deletes the line items as indicated by the line item code(s) provided,
1091
-     * regardless of where they're found in the line item tree. Automatically
1092
-     * re-calculates the line item totals and updates the related transaction. But
1093
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1094
-     * should probably change because of this).
1095
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1096
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1097
-     *
1098
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1099
-     * @param array|bool|string $line_item_codes
1100
-     * @return int number of items successfully removed
1101
-     * @throws EE_Error
1102
-     */
1103
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1104
-    {
1105
-
1106
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1107
-            EE_Error::doing_it_wrong(
1108
-                'EEH_Line_Item::delete_items',
1109
-                esc_html__(
1110
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1111
-                    'event_espresso'
1112
-                ),
1113
-                '4.6.18'
1114
-            );
1115
-        }
1116
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1117
-
1118
-        // check if only a single line_item_id was passed
1119
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1120
-            // place single line_item_id in an array to appear as multiple line_item_ids
1121
-            $line_item_codes = array($line_item_codes);
1122
-        }
1123
-        $removals = 0;
1124
-        // cycle thru line_item_ids
1125
-        foreach ($line_item_codes as $line_item_id) {
1126
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1127
-        }
1128
-
1129
-        if ($removals > 0) {
1130
-            $total_line_item->recalculate_taxes_and_tax_total();
1131
-            return $removals;
1132
-        } else {
1133
-            return false;
1134
-        }
1135
-    }
1136
-
1137
-
1138
-    /**
1139
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1140
-     * tax and updates the total line item accordingly
1141
-     *
1142
-     * @param EE_Line_Item $total_line_item
1143
-     * @param float        $amount
1144
-     * @param string       $name
1145
-     * @param string       $description
1146
-     * @param string       $code
1147
-     * @param boolean      $add_to_existing_line_item
1148
-     *                          if true, and a duplicate line item with the same code is found,
1149
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1150
-     * @return EE_Line_Item the new tax line item created
1151
-     * @throws EE_Error
1152
-     * @throws InvalidArgumentException
1153
-     * @throws InvalidDataTypeException
1154
-     * @throws InvalidInterfaceException
1155
-     * @throws ReflectionException
1156
-     */
1157
-    public static function set_total_tax_to(
1158
-        EE_Line_Item $total_line_item,
1159
-        $amount,
1160
-        $name = null,
1161
-        $description = null,
1162
-        $code = null,
1163
-        $add_to_existing_line_item = false
1164
-    ) {
1165
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
1166
-        $taxable_total = $total_line_item->taxable_total();
1167
-
1168
-        if ($add_to_existing_line_item) {
1169
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1170
-            EEM_Line_Item::instance()->delete(
1171
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1172
-            );
1173
-        } else {
1174
-            $new_tax = null;
1175
-            $tax_subtotal->delete_children_line_items();
1176
-        }
1177
-        if ($new_tax) {
1178
-            $new_tax->set_total($new_tax->total() + $amount);
1179
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1180
-        } else {
1181
-            // no existing tax item. Create it
1182
-            $new_tax = EE_Line_Item::new_instance(array(
1183
-                'TXN_ID'      => $total_line_item->TXN_ID(),
1184
-                'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1185
-                'LIN_desc'    => $description ? $description : '',
1186
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1187
-                'LIN_total'   => $amount,
1188
-                'LIN_parent'  => $tax_subtotal->ID(),
1189
-                'LIN_type'    => EEM_Line_Item::type_tax,
1190
-                'LIN_code'    => $code,
1191
-            ));
1192
-        }
1193
-
1194
-        $new_tax = apply_filters(
1195
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1196
-            $new_tax,
1197
-            $total_line_item
1198
-        );
1199
-        $new_tax->save();
1200
-        $tax_subtotal->set_total($new_tax->total());
1201
-        $tax_subtotal->save();
1202
-        $total_line_item->recalculate_total_including_taxes();
1203
-        return $new_tax;
1204
-    }
1205
-
1206
-
1207
-    /**
1208
-     * Makes all the line items which are children of $line_item taxable (or not).
1209
-     * Does NOT save the line items
1210
-     *
1211
-     * @param EE_Line_Item $line_item
1212
-     * @param boolean      $taxable
1213
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1214
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1215
-     * @throws EE_Error
1216
-     */
1217
-    public static function set_line_items_taxable(
1218
-        EE_Line_Item $line_item,
1219
-        $taxable = true,
1220
-        $code_substring_for_whitelist = null
1221
-    ) {
1222
-        $whitelisted = false;
1223
-        if ($code_substring_for_whitelist !== null) {
1224
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1225
-        }
1226
-        if (! $whitelisted && $line_item->is_line_item()) {
1227
-            $line_item->set_is_taxable($taxable);
1228
-        }
1229
-        foreach ($line_item->children() as $child_line_item) {
1230
-            EEH_Line_Item::set_line_items_taxable(
1231
-                $child_line_item,
1232
-                $taxable,
1233
-                $code_substring_for_whitelist
1234
-            );
1235
-        }
1236
-    }
1237
-
1238
-
1239
-    /**
1240
-     * Gets all descendants that are event subtotals
1241
-     *
1242
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1243
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1244
-     * @return EE_Line_Item[]
1245
-     * @throws EE_Error
1246
-     */
1247
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1248
-    {
1249
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1250
-    }
1251
-
1252
-
1253
-    /**
1254
-     * Gets all descendants subtotals that match the supplied object type
1255
-     *
1256
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1257
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1258
-     * @param string       $obj_type
1259
-     * @return EE_Line_Item[]
1260
-     * @throws EE_Error
1261
-     */
1262
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1263
-    {
1264
-        return self::_get_descendants_by_type_and_object_type(
1265
-            $parent_line_item,
1266
-            EEM_Line_Item::type_sub_total,
1267
-            $obj_type
1268
-        );
1269
-    }
1270
-
1271
-
1272
-    /**
1273
-     * Gets all descendants that are tickets
1274
-     *
1275
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1276
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1277
-     * @return EE_Line_Item[]
1278
-     * @throws EE_Error
1279
-     */
1280
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1281
-    {
1282
-        return self::get_line_items_of_object_type(
1283
-            $parent_line_item,
1284
-            EEM_Line_Item::OBJ_TYPE_TICKET
1285
-        );
1286
-    }
1287
-
1288
-
1289
-    /**
1290
-     * Gets all descendants subtotals that match the supplied object type
1291
-     *
1292
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1293
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1294
-     * @param string       $obj_type
1295
-     * @return EE_Line_Item[]
1296
-     * @throws EE_Error
1297
-     */
1298
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1299
-    {
1300
-        return self::_get_descendants_by_type_and_object_type(
1301
-            $parent_line_item,
1302
-            EEM_Line_Item::type_line_item,
1303
-            $obj_type
1304
-        );
1305
-    }
1306
-
1307
-
1308
-    /**
1309
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1310
-     *
1311
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1312
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1313
-     * @return EE_Line_Item[]
1314
-     * @throws EE_Error
1315
-     */
1316
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1317
-    {
1318
-        return EEH_Line_Item::get_descendants_of_type(
1319
-            $parent_line_item,
1320
-            EEM_Line_Item::type_tax
1321
-        );
1322
-    }
1323
-
1324
-
1325
-    /**
1326
-     * Gets all the real items purchased which are children of this item
1327
-     *
1328
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1329
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1330
-     * @return EE_Line_Item[]
1331
-     * @throws EE_Error
1332
-     */
1333
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1334
-    {
1335
-        return EEH_Line_Item::get_descendants_of_type(
1336
-            $parent_line_item,
1337
-            EEM_Line_Item::type_line_item
1338
-        );
1339
-    }
1340
-
1341
-
1342
-    /**
1343
-     * Gets all descendants of supplied line item that match the supplied line item type
1344
-     *
1345
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1346
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1347
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1348
-     * @return EE_Line_Item[]
1349
-     * @throws EE_Error
1350
-     */
1351
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1352
-    {
1353
-        return self::_get_descendants_by_type_and_object_type(
1354
-            $parent_line_item,
1355
-            $line_item_type,
1356
-            null
1357
-        );
1358
-    }
1359
-
1360
-
1361
-    /**
1362
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1363
-     * as well
1364
-     *
1365
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1366
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1367
-     * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1368
-     *                                        searching
1369
-     * @return EE_Line_Item[]
1370
-     * @throws EE_Error
1371
-     */
1372
-    protected static function _get_descendants_by_type_and_object_type(
1373
-        EE_Line_Item $parent_line_item,
1374
-        $line_item_type,
1375
-        $obj_type = null
1376
-    ) {
1377
-        $objects = array();
1378
-        foreach ($parent_line_item->children() as $child_line_item) {
1379
-            if ($child_line_item instanceof EE_Line_Item) {
1380
-                if (
1381
-                    $child_line_item->type() === $line_item_type
1382
-                    && (
1383
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1384
-                    )
1385
-                ) {
1386
-                    $objects[] = $child_line_item;
1387
-                } else {
1388
-                    // go-through-all-its children looking for more matches
1389
-                    $objects = array_merge(
1390
-                        $objects,
1391
-                        self::_get_descendants_by_type_and_object_type(
1392
-                            $child_line_item,
1393
-                            $line_item_type,
1394
-                            $obj_type
1395
-                        )
1396
-                    );
1397
-                }
1398
-            }
1399
-        }
1400
-        return $objects;
1401
-    }
1402
-
1403
-
1404
-    /**
1405
-     * Gets all descendants subtotals that match the supplied object type
1406
-     *
1407
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1408
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1409
-     * @param string       $OBJ_type         object type (like Event)
1410
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1411
-     * @return EE_Line_Item[]
1412
-     * @throws EE_Error
1413
-     */
1414
-    public static function get_line_items_by_object_type_and_IDs(
1415
-        EE_Line_Item $parent_line_item,
1416
-        $OBJ_type = '',
1417
-        $OBJ_IDs = array()
1418
-    ) {
1419
-        return self::_get_descendants_by_object_type_and_object_ID(
1420
-            $parent_line_item,
1421
-            $OBJ_type,
1422
-            $OBJ_IDs
1423
-        );
1424
-    }
1425
-
1426
-
1427
-    /**
1428
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1429
-     * as well
1430
-     *
1431
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
-     * @param string       $OBJ_type         object type (like Event)
1433
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1434
-     * @return EE_Line_Item[]
1435
-     * @throws EE_Error
1436
-     */
1437
-    protected static function _get_descendants_by_object_type_and_object_ID(
1438
-        EE_Line_Item $parent_line_item,
1439
-        $OBJ_type,
1440
-        $OBJ_IDs
1441
-    ) {
1442
-        $objects = array();
1443
-        foreach ($parent_line_item->children() as $child_line_item) {
1444
-            if ($child_line_item instanceof EE_Line_Item) {
1445
-                if (
1446
-                    $child_line_item->OBJ_type() === $OBJ_type
1447
-                    && is_array($OBJ_IDs)
1448
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1449
-                ) {
1450
-                    $objects[] = $child_line_item;
1451
-                } else {
1452
-                    // go-through-all-its children looking for more matches
1453
-                    $objects = array_merge(
1454
-                        $objects,
1455
-                        self::_get_descendants_by_object_type_and_object_ID(
1456
-                            $child_line_item,
1457
-                            $OBJ_type,
1458
-                            $OBJ_IDs
1459
-                        )
1460
-                    );
1461
-                }
1462
-            }
1463
-        }
1464
-        return $objects;
1465
-    }
1466
-
1467
-
1468
-    /**
1469
-     * Uses a breadth-first-search in order to find the nearest descendant of
1470
-     * the specified type and returns it, else NULL
1471
-     *
1472
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1473
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1474
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1475
-     * @return EE_Line_Item
1476
-     * @throws EE_Error
1477
-     * @throws InvalidArgumentException
1478
-     * @throws InvalidDataTypeException
1479
-     * @throws InvalidInterfaceException
1480
-     * @throws ReflectionException
1481
-     */
1482
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1483
-    {
1484
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1485
-    }
1486
-
1487
-
1488
-    /**
1489
-     * Uses a breadth-first-search in order to find the nearest descendant
1490
-     * having the specified LIN_code and returns it, else NULL
1491
-     *
1492
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1493
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1494
-     * @param string       $code             any value used for LIN_code
1495
-     * @return EE_Line_Item
1496
-     * @throws EE_Error
1497
-     * @throws InvalidArgumentException
1498
-     * @throws InvalidDataTypeException
1499
-     * @throws InvalidInterfaceException
1500
-     * @throws ReflectionException
1501
-     */
1502
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1503
-    {
1504
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1505
-    }
1506
-
1507
-
1508
-    /**
1509
-     * Uses a breadth-first-search in order to find the nearest descendant
1510
-     * having the specified LIN_code and returns it, else NULL
1511
-     *
1512
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1513
-     * @param string       $search_field     name of EE_Line_Item property
1514
-     * @param string       $value            any value stored in $search_field
1515
-     * @return EE_Line_Item
1516
-     * @throws EE_Error
1517
-     * @throws InvalidArgumentException
1518
-     * @throws InvalidDataTypeException
1519
-     * @throws InvalidInterfaceException
1520
-     * @throws ReflectionException
1521
-     */
1522
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1523
-    {
1524
-        foreach ($parent_line_item->children() as $child) {
1525
-            if ($child->get($search_field) == $value) {
1526
-                return $child;
1527
-            }
1528
-        }
1529
-        foreach ($parent_line_item->children() as $child) {
1530
-            $descendant_found = self::_get_nearest_descendant(
1531
-                $child,
1532
-                $search_field,
1533
-                $value
1534
-            );
1535
-            if ($descendant_found) {
1536
-                return $descendant_found;
1537
-            }
1538
-        }
1539
-        return null;
1540
-    }
1541
-
1542
-
1543
-    /**
1544
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1545
-     * else recursively walks up the line item tree until a parent of type total is found,
1546
-     *
1547
-     * @param EE_Line_Item $line_item
1548
-     * @return EE_Line_Item
1549
-     * @throws EE_Error
1550
-     * @throws ReflectionException
1551
-     */
1552
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1553
-    {
1554
-        if ($line_item->is_total()) {
1555
-            return $line_item;
1556
-        }
1557
-        if ($line_item->TXN_ID()) {
1558
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1559
-            if ($total_line_item instanceof EE_Line_Item) {
1560
-                return $total_line_item;
1561
-            }
1562
-        } else {
1563
-            $line_item_parent = $line_item->parent();
1564
-            if ($line_item_parent instanceof EE_Line_Item) {
1565
-                if ($line_item_parent->is_total()) {
1566
-                    return $line_item_parent;
1567
-                }
1568
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1569
-            }
1570
-        }
1571
-        throw new EE_Error(
1572
-            sprintf(
1573
-                esc_html__(
1574
-                    'A valid grand total for line item %1$d was not found.',
1575
-                    'event_espresso'
1576
-                ),
1577
-                $line_item->ID()
1578
-            )
1579
-        );
1580
-    }
1581
-
1582
-
1583
-    /**
1584
-     * Prints out a representation of the line item tree
1585
-     *
1586
-     * @param EE_Line_Item $line_item
1587
-     * @param int          $indentation
1588
-     * @return void
1589
-     * @throws EE_Error
1590
-     */
1591
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1592
-    {
1593
-        echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1594
-        if (! $indentation) {
1595
-            echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1596
-        }
1597
-        echo str_repeat('. ', $indentation);
1598
-        $breakdown = '';
1599
-        if ($line_item->is_line_item()) {
1600
-            if ($line_item->is_percent()) {
1601
-                $breakdown = "{$line_item->percent()}%";
1602
-            } else {
1603
-                $breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1604
-            }
1605
-        }
1606
-        echo $line_item->name();
1607
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1608
-        echo "\${$line_item->total()}";
1609
-        if ($breakdown) {
1610
-            echo " ( {$breakdown} )";
1611
-        }
1612
-        if ($line_item->is_taxable()) {
1613
-            echo '  * taxable';
1614
-        }
1615
-        if ($line_item->children()) {
1616
-            foreach ($line_item->children() as $child) {
1617
-                self::visualize($child, $indentation + 1);
1618
-            }
1619
-        }
1620
-    }
1621
-
1622
-
1623
-    /**
1624
-     * Calculates the registration's final price, taking into account that they
1625
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1626
-     * and receive a portion of any transaction-wide discounts.
1627
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1628
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1629
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1630
-     * and brent's final price should be $5.50.
1631
-     * In order to do this, we basically need to traverse the line item tree calculating
1632
-     * the running totals (just as if we were recalculating the total), but when we identify
1633
-     * regular line items, we need to keep track of their share of the grand total.
1634
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1635
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1636
-     * when there are non-taxable items; otherwise they would be the same)
1637
-     *
1638
-     * @param EE_Line_Item $line_item
1639
-     * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1640
-     *                                                  can be included in price calculations at this moment
1641
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1642
-     *                                                  plus the key 'total', and 'taxable' which also has keys of all
1643
-     *                                                  the ticket IDs.
1644
-     *                                                  Eg array(
1645
-     *                                                      12 => 4.3
1646
-     *                                                      23 => 8.0
1647
-     *                                                      'total' => 16.6,
1648
-     *                                                      'taxable' => array(
1649
-     *                                                          12 => 10,
1650
-     *                                                          23 => 4
1651
-     *                                                      ).
1652
-     *                                                  So to find which registrations have which final price, we need
1653
-     *                                                  to find which line item is theirs, which can be done with
1654
-     *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1655
-     *                                                  $registration );`
1656
-     * @throws EE_Error
1657
-     * @throws InvalidArgumentException
1658
-     * @throws InvalidDataTypeException
1659
-     * @throws InvalidInterfaceException
1660
-     * @throws ReflectionException
1661
-     */
1662
-    public static function calculate_reg_final_prices_per_line_item(
1663
-        EE_Line_Item $line_item,
1664
-        $billable_ticket_quantities = array()
1665
-    ) {
1666
-        $running_totals = [
1667
-            'total'   => 0,
1668
-            'taxable' => ['total' => 0]
1669
-        ];
1670
-        foreach ($line_item->children() as $child_line_item) {
1671
-            switch ($child_line_item->type()) {
1672
-                case EEM_Line_Item::type_sub_total:
1673
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1674
-                        $child_line_item,
1675
-                        $billable_ticket_quantities
1676
-                    );
1677
-                    // combine arrays but preserve numeric keys
1678
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1679
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1680
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1681
-                    break;
1682
-
1683
-                case EEM_Line_Item::type_tax_sub_total:
1684
-                    // find how much the taxes percentage is
1685
-                    if ($child_line_item->percent() !== 0) {
1686
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1687
-                    } else {
1688
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1689
-                    }
1690
-                    // and apply to all the taxable totals, and add to the pretax totals
1691
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1692
-                        // "total" and "taxable" array key is an exception
1693
-                        if ($line_item_id === 'taxable') {
1694
-                            continue;
1695
-                        }
1696
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1697
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1698
-                    }
1699
-                    break;
1700
-
1701
-                case EEM_Line_Item::type_line_item:
1702
-                    // ticket line items or ????
1703
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1704
-                        // kk it's a ticket
1705
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1706
-                            // huh? that shouldn't happen.
1707
-                            $running_totals['total'] += $child_line_item->total();
1708
-                        } else {
1709
-                            // its not in our running totals yet. great.
1710
-                            if ($child_line_item->is_taxable()) {
1711
-                                $taxable_amount = $child_line_item->unit_price();
1712
-                            } else {
1713
-                                $taxable_amount = 0;
1714
-                            }
1715
-                            // are we only calculating totals for some tickets?
1716
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1717
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1718
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1719
-                                    ? $child_line_item->unit_price()
1720
-                                    : 0;
1721
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1722
-                                    ? $taxable_amount
1723
-                                    : 0;
1724
-                            } else {
1725
-                                $quantity = $child_line_item->quantity();
1726
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1727
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1728
-                            }
1729
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1730
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1731
-                        }
1732
-                    } else {
1733
-                        // it's some other type of item added to the cart
1734
-                        // it should affect the running totals
1735
-                        // basically we want to convert it into a PERCENT modifier. Because
1736
-                        // more clearly affect all registration's final price equally
1737
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1738
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1739
-                            : 1;
1740
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1741
-                            // the "taxable" array key is an exception
1742
-                            if ($line_item_id === 'taxable') {
1743
-                                continue;
1744
-                            }
1745
-                            // update the running totals
1746
-                            // yes this actually even works for the running grand total!
1747
-                            $running_totals[ $line_item_id ] =
1748
-                                $line_items_percent_of_running_total * $this_running_total;
1749
-
1750
-                            if ($child_line_item->is_taxable()) {
1751
-                                $running_totals['taxable'][ $line_item_id ] =
1752
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1753
-                            }
1754
-                        }
1755
-                    }
1756
-                    break;
1757
-            }
1758
-        }
1759
-        return $running_totals;
1760
-    }
1761
-
1762
-
1763
-    /**
1764
-     * @param EE_Line_Item $total_line_item
1765
-     * @param EE_Line_Item $ticket_line_item
1766
-     * @return float | null
1767
-     * @throws EE_Error
1768
-     * @throws InvalidArgumentException
1769
-     * @throws InvalidDataTypeException
1770
-     * @throws InvalidInterfaceException
1771
-     * @throws OutOfRangeException
1772
-     * @throws ReflectionException
1773
-     */
1774
-    public static function calculate_final_price_for_ticket_line_item(
1775
-        EE_Line_Item $total_line_item,
1776
-        EE_Line_Item $ticket_line_item
1777
-    ) {
1778
-        static $final_prices_per_ticket_line_item = array();
1779
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1780
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1781
-                $total_line_item
1782
-            );
1783
-        }
1784
-        // ok now find this new registration's final price
1785
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1786
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1787
-        }
1788
-        $message = sprintf(
1789
-            esc_html__(
1790
-                'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1791
-                'event_espresso'
1792
-            ),
1793
-            $ticket_line_item->ID()
1794
-        );
1795
-        if (WP_DEBUG) {
1796
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1797
-            throw new OutOfRangeException($message);
1798
-        }
1799
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1800
-        return null;
1801
-    }
1802
-
1803
-
1804
-    /**
1805
-     * Creates a duplicate of the line item tree, except only includes billable items
1806
-     * and the portion of line items attributed to billable things
1807
-     *
1808
-     * @param EE_Line_Item      $line_item
1809
-     * @param EE_Registration[] $registrations
1810
-     * @return EE_Line_Item
1811
-     * @throws EE_Error
1812
-     * @throws InvalidArgumentException
1813
-     * @throws InvalidDataTypeException
1814
-     * @throws InvalidInterfaceException
1815
-     * @throws ReflectionException
1816
-     */
1817
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1818
-    {
1819
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1820
-        foreach ($line_item->children() as $child_li) {
1821
-            $copy_li->add_child_line_item(
1822
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1823
-            );
1824
-        }
1825
-        // if this is the grand total line item, make sure the totals all add up
1826
-        // (we could have duplicated this logic AS we copied the line items, but
1827
-        // it seems DRYer this way)
1828
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1829
-            $copy_li->recalculate_total_including_taxes();
1830
-        }
1831
-        return $copy_li;
1832
-    }
1833
-
1834
-
1835
-    /**
1836
-     * Creates a new, unsaved line item from $line_item that factors in the
1837
-     * number of billable registrations on $registrations.
1838
-     *
1839
-     * @param EE_Line_Item      $line_item
1840
-     * @param EE_Registration[] $registrations
1841
-     * @return EE_Line_Item
1842
-     * @throws EE_Error
1843
-     * @throws InvalidArgumentException
1844
-     * @throws InvalidDataTypeException
1845
-     * @throws InvalidInterfaceException
1846
-     * @throws ReflectionException
1847
-     */
1848
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1849
-    {
1850
-        $new_li_fields = $line_item->model_field_array();
1851
-        if (
1852
-            $line_item->type() === EEM_Line_Item::type_line_item &&
1853
-            $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1854
-        ) {
1855
-            $count = 0;
1856
-            foreach ($registrations as $registration) {
1857
-                if (
1858
-                    $line_item->OBJ_ID() === $registration->ticket_ID() &&
1859
-                    in_array(
1860
-                        $registration->status_ID(),
1861
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1862
-                        true
1863
-                    )
1864
-                ) {
1865
-                    $count++;
1866
-                }
1867
-            }
1868
-            $new_li_fields['LIN_quantity'] = $count;
1869
-        }
1870
-        // don't set the total. We'll leave that up to the code that calculates it
1871
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1872
-        return EE_Line_Item::new_instance($new_li_fields);
1873
-    }
1874
-
1875
-
1876
-    /**
1877
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1878
-     * are removed, and line items with a quantity of 0
1879
-     *
1880
-     * @param EE_Line_Item $line_item |null
1881
-     * @return EE_Line_Item|null
1882
-     * @throws EE_Error
1883
-     * @throws InvalidArgumentException
1884
-     * @throws InvalidDataTypeException
1885
-     * @throws InvalidInterfaceException
1886
-     * @throws ReflectionException
1887
-     */
1888
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1889
-    {
1890
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1891
-        if ($copied_li === null) {
1892
-            return null;
1893
-        }
1894
-        // if this is an event subtotal, we want to only include it if it
1895
-        // has a non-zero total and at least one ticket line item child
1896
-        $ticket_children = 0;
1897
-        foreach ($line_item->children() as $child_li) {
1898
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1899
-            if ($child_li_copy !== null) {
1900
-                $copied_li->add_child_line_item($child_li_copy);
1901
-                if (
1902
-                    $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1903
-                    $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1904
-                ) {
1905
-                    $ticket_children++;
1906
-                }
1907
-            }
1908
-        }
1909
-        // if this is an event subtotal with NO ticket children
1910
-        // we basically want to ignore it
1911
-        if (
1912
-            $ticket_children === 0
1913
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1914
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1915
-            && $line_item->total() === 0
1916
-        ) {
1917
-            return null;
1918
-        }
1919
-        return $copied_li;
1920
-    }
1921
-
1922
-
1923
-    /**
1924
-     * Creates a new, unsaved line item, but if it's a ticket line item
1925
-     * with a total of 0, or a subtotal of 0, returns null instead
1926
-     *
1927
-     * @param EE_Line_Item $line_item
1928
-     * @return EE_Line_Item
1929
-     * @throws EE_Error
1930
-     * @throws InvalidArgumentException
1931
-     * @throws InvalidDataTypeException
1932
-     * @throws InvalidInterfaceException
1933
-     * @throws ReflectionException
1934
-     */
1935
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1936
-    {
1937
-        if (
1938
-            $line_item->type() === EEM_Line_Item::type_line_item
1939
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1940
-            && $line_item->quantity() === 0
1941
-        ) {
1942
-            return null;
1943
-        }
1944
-        $new_li_fields = $line_item->model_field_array();
1945
-        // don't set the total. We'll leave that up to the code that calculates it
1946
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1947
-        return EE_Line_Item::new_instance($new_li_fields);
1948
-    }
1949
-
1950
-
1951
-    /**
1952
-     * Cycles through all of the ticket line items for the supplied total line item
1953
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1954
-     *
1955
-     * @param EE_Line_Item $total_line_item
1956
-     * @since 4.9.79.p
1957
-     * @throws EE_Error
1958
-     * @throws InvalidArgumentException
1959
-     * @throws InvalidDataTypeException
1960
-     * @throws InvalidInterfaceException
1961
-     * @throws ReflectionException
1962
-     */
1963
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1964
-    {
1965
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1966
-        foreach ($ticket_line_items as $ticket_line_item) {
1967
-            if (
1968
-                $ticket_line_item instanceof EE_Line_Item
1969
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1970
-            ) {
1971
-                $ticket = $ticket_line_item->ticket();
1972
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
1973
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
1974
-                    $ticket_line_item->save();
1975
-                }
1976
-            }
1977
-        }
1978
-    }
1979
-
1980
-
1981
-    /**
1982
-     * @return EE_Line_Item[]
1983
-     * @throws EE_Error
1984
-     * @throws ReflectionException
1985
-     * @since   $VID:$
1986
-     */
1987
-    private static function getGlobalTaxes(): array
1988
-    {
1989
-        if (EEH_Line_Item::$global_taxes === null) {
1990
-
1991
-            /** @type EEM_Price $EEM_Price */
1992
-            $EEM_Price = EE_Registry::instance()->load_model('Price');
1993
-            // get array of taxes via Price Model
1994
-            EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
1995
-            ksort(EEH_Line_Item::$global_taxes);
1996
-        }
1997
-        return EEH_Line_Item::$global_taxes;
1998
-    }
1999
-
2000
-
2001
-
2002
-    /**************************************** @DEPRECATED METHODS *************************************** */
2003
-    /**
2004
-     * @deprecated
2005
-     * @param EE_Line_Item $total_line_item
2006
-     * @return EE_Line_Item
2007
-     * @throws EE_Error
2008
-     * @throws InvalidArgumentException
2009
-     * @throws InvalidDataTypeException
2010
-     * @throws InvalidInterfaceException
2011
-     * @throws ReflectionException
2012
-     */
2013
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
2014
-    {
2015
-        EE_Error::doing_it_wrong(
2016
-            'EEH_Line_Item::get_items_subtotal()',
2017
-            sprintf(
2018
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2019
-                'EEH_Line_Item::get_pre_tax_subtotal()'
2020
-            ),
2021
-            '4.6.0'
2022
-        );
2023
-        return self::get_pre_tax_subtotal($total_line_item);
2024
-    }
2025
-
2026
-
2027
-    /**
2028
-     * @deprecated
2029
-     * @param EE_Transaction $transaction
2030
-     * @return EE_Line_Item
2031
-     * @throws EE_Error
2032
-     * @throws InvalidArgumentException
2033
-     * @throws InvalidDataTypeException
2034
-     * @throws InvalidInterfaceException
2035
-     * @throws ReflectionException
2036
-     */
2037
-    public static function create_default_total_line_item($transaction = null)
2038
-    {
2039
-        EE_Error::doing_it_wrong(
2040
-            'EEH_Line_Item::create_default_total_line_item()',
2041
-            sprintf(
2042
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2043
-                'EEH_Line_Item::create_total_line_item()'
2044
-            ),
2045
-            '4.6.0'
2046
-        );
2047
-        return self::create_total_line_item($transaction);
2048
-    }
2049
-
2050
-
2051
-    /**
2052
-     * @deprecated
2053
-     * @param EE_Line_Item   $total_line_item
2054
-     * @param EE_Transaction $transaction
2055
-     * @return EE_Line_Item
2056
-     * @throws EE_Error
2057
-     * @throws InvalidArgumentException
2058
-     * @throws InvalidDataTypeException
2059
-     * @throws InvalidInterfaceException
2060
-     * @throws ReflectionException
2061
-     */
2062
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2063
-    {
2064
-        EE_Error::doing_it_wrong(
2065
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2066
-            sprintf(
2067
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2068
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2069
-            ),
2070
-            '4.6.0'
2071
-        );
2072
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2073
-    }
2074
-
2075
-
2076
-    /**
2077
-     * @deprecated
2078
-     * @param EE_Line_Item   $total_line_item
2079
-     * @param EE_Transaction $transaction
2080
-     * @return EE_Line_Item
2081
-     * @throws EE_Error
2082
-     * @throws InvalidArgumentException
2083
-     * @throws InvalidDataTypeException
2084
-     * @throws InvalidInterfaceException
2085
-     * @throws ReflectionException
2086
-     */
2087
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2088
-    {
2089
-        EE_Error::doing_it_wrong(
2090
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2091
-            sprintf(
2092
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2093
-                'EEH_Line_Item::create_taxes_subtotal()'
2094
-            ),
2095
-            '4.6.0'
2096
-        );
2097
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2098
-    }
2099
-
2100
-
2101
-    /**
2102
-     * @deprecated
2103
-     * @param EE_Line_Item   $total_line_item
2104
-     * @param EE_Transaction $transaction
2105
-     * @return EE_Line_Item
2106
-     * @throws EE_Error
2107
-     * @throws InvalidArgumentException
2108
-     * @throws InvalidDataTypeException
2109
-     * @throws InvalidInterfaceException
2110
-     * @throws ReflectionException
2111
-     */
2112
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2113
-    {
2114
-        EE_Error::doing_it_wrong(
2115
-            'EEH_Line_Item::create_default_event_subtotal()',
2116
-            sprintf(
2117
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2118
-                'EEH_Line_Item::create_event_subtotal()'
2119
-            ),
2120
-            '4.6.0'
2121
-        );
2122
-        return self::create_event_subtotal($total_line_item, $transaction);
2123
-    }
24
+	/**
25
+	 * @var EE_Line_Item[]
26
+	 */
27
+	private static $global_taxes;
28
+
29
+	/**
30
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
31
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
32
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
33
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
34
+	 * to keep the registration final prices in-sync with the transaction's total.
35
+	 *
36
+	 * @param EE_Line_Item $parent_line_item
37
+	 * @param string       $name
38
+	 * @param float        $unit_price
39
+	 * @param string       $description
40
+	 * @param int          $quantity
41
+	 * @param boolean      $taxable
42
+	 * @param boolean      $code if set to a value, ensures there is only one line item with that code
43
+	 * @return boolean success
44
+	 * @throws EE_Error
45
+	 * @throws InvalidArgumentException
46
+	 * @throws InvalidDataTypeException
47
+	 * @throws InvalidInterfaceException
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function add_unrelated_item(
51
+		EE_Line_Item $parent_line_item,
52
+		$name,
53
+		$unit_price,
54
+		$description = '',
55
+		$quantity = 1,
56
+		$taxable = false,
57
+		$code = null
58
+	) {
59
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
60
+		$line_item = EE_Line_Item::new_instance(array(
61
+			'LIN_name'       => $name,
62
+			'LIN_desc'       => $description,
63
+			'LIN_unit_price' => $unit_price,
64
+			'LIN_quantity'   => $quantity,
65
+			'LIN_percent'    => null,
66
+			'LIN_is_taxable' => $taxable,
67
+			'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
68
+			'LIN_total'      => (float) $unit_price * (int) $quantity,
69
+			'LIN_type'       => EEM_Line_Item::type_line_item,
70
+			'LIN_code'       => $code,
71
+		));
72
+		$line_item = apply_filters(
73
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
74
+			$line_item,
75
+			$parent_line_item
76
+		);
77
+		return self::add_item($parent_line_item, $line_item);
78
+	}
79
+
80
+
81
+	/**
82
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
83
+	 * in the correct spot in the line item tree. Does not automatically
84
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
85
+	 * registrations' final prices (which should probably change because of this).
86
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
87
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
88
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
89
+	 *
90
+	 * @param EE_Line_Item $parent_line_item
91
+	 * @param string       $name
92
+	 * @param float        $percentage_amount
93
+	 * @param string       $description
94
+	 * @param boolean      $taxable
95
+	 * @return boolean success
96
+	 * @throws EE_Error
97
+	 */
98
+	public static function add_percentage_based_item(
99
+		EE_Line_Item $parent_line_item,
100
+		$name,
101
+		$percentage_amount,
102
+		$description = '',
103
+		$taxable = false
104
+	) {
105
+		$line_item = EE_Line_Item::new_instance(array(
106
+			'LIN_name'       => $name,
107
+			'LIN_desc'       => $description,
108
+			'LIN_unit_price' => 0,
109
+			'LIN_percent'    => $percentage_amount,
110
+			'LIN_quantity'   => 1,
111
+			'LIN_is_taxable' => $taxable,
112
+			'LIN_total'      => (float) ($percentage_amount * ($parent_line_item->total() / 100)),
113
+			'LIN_type'       => EEM_Line_Item::type_line_item,
114
+			'LIN_parent'     => $parent_line_item->ID(),
115
+		));
116
+		$line_item = apply_filters(
117
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
118
+			$line_item
119
+		);
120
+		return $parent_line_item->add_child_line_item($line_item, false);
121
+	}
122
+
123
+
124
+	/**
125
+	 * Returns the new line item created by adding a purchase of the ticket
126
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
127
+	 * If this ticket has already been purchased, just increments its count.
128
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
129
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
130
+	 * should probably change because of this).
131
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
132
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
133
+	 *
134
+	 * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
135
+	 * @param EE_Ticket    $ticket
136
+	 * @param int          $qty
137
+	 * @return EE_Line_Item
138
+	 * @throws EE_Error
139
+	 * @throws InvalidArgumentException
140
+	 * @throws InvalidDataTypeException
141
+	 * @throws InvalidInterfaceException
142
+	 * @throws ReflectionException
143
+	 */
144
+	public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
145
+	{
146
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
147
+			throw new EE_Error(
148
+				sprintf(
149
+					esc_html__(
150
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
151
+						'event_espresso'
152
+					),
153
+					$ticket->ID(),
154
+					$total_line_item->ID()
155
+				)
156
+			);
157
+		}
158
+		// either increment the qty for an existing ticket
159
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
160
+		// or add a new one
161
+		if (! $line_item instanceof EE_Line_Item) {
162
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
163
+		}
164
+		$total_line_item->recalculate_total_including_taxes();
165
+		return $line_item;
166
+	}
167
+
168
+
169
+	/**
170
+	 * Returns the new line item created by adding a purchase of the ticket
171
+	 *
172
+	 * @param EE_Line_Item $total_line_item
173
+	 * @param EE_Ticket    $ticket
174
+	 * @param int          $qty
175
+	 * @return EE_Line_Item
176
+	 * @throws EE_Error
177
+	 * @throws InvalidArgumentException
178
+	 * @throws InvalidDataTypeException
179
+	 * @throws InvalidInterfaceException
180
+	 * @throws ReflectionException
181
+	 */
182
+	public static function increment_ticket_qty_if_already_in_cart(
183
+		EE_Line_Item $total_line_item,
184
+		EE_Ticket $ticket,
185
+		$qty = 1
186
+	) {
187
+		$line_item = null;
188
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
189
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
190
+			foreach ((array) $ticket_line_items as $ticket_line_item) {
191
+				if (
192
+					$ticket_line_item instanceof EE_Line_Item
193
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
194
+				) {
195
+					$line_item = $ticket_line_item;
196
+					break;
197
+				}
198
+			}
199
+		}
200
+		if ($line_item instanceof EE_Line_Item) {
201
+			EEH_Line_Item::increment_quantity($line_item, $qty);
202
+			return $line_item;
203
+		}
204
+		return null;
205
+	}
206
+
207
+
208
+	/**
209
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
210
+	 * Does NOT save or recalculate other line items totals
211
+	 *
212
+	 * @param EE_Line_Item $line_item
213
+	 * @param int          $qty
214
+	 * @return void
215
+	 * @throws EE_Error
216
+	 * @throws InvalidArgumentException
217
+	 * @throws InvalidDataTypeException
218
+	 * @throws InvalidInterfaceException
219
+	 * @throws ReflectionException
220
+	 */
221
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
222
+	{
223
+		if (! $line_item->is_percent()) {
224
+			$qty += $line_item->quantity();
225
+			$line_item->set_quantity($qty);
226
+			$line_item->set_total($line_item->unit_price() * $qty);
227
+			$line_item->save();
228
+		}
229
+		foreach ($line_item->children() as $child) {
230
+			if ($child->is_sub_line_item()) {
231
+				EEH_Line_Item::update_quantity($child, $qty);
232
+			}
233
+		}
234
+	}
235
+
236
+
237
+	/**
238
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
239
+	 * Does NOT save or recalculate other line items totals
240
+	 *
241
+	 * @param EE_Line_Item $line_item
242
+	 * @param int          $qty
243
+	 * @return void
244
+	 * @throws EE_Error
245
+	 * @throws InvalidArgumentException
246
+	 * @throws InvalidDataTypeException
247
+	 * @throws InvalidInterfaceException
248
+	 * @throws ReflectionException
249
+	 */
250
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
251
+	{
252
+		if (! $line_item->is_percent()) {
253
+			$qty = $line_item->quantity() - $qty;
254
+			$qty = max($qty, 0);
255
+			$line_item->set_quantity($qty);
256
+			$line_item->set_total($line_item->unit_price() * $qty);
257
+			$line_item->save();
258
+		}
259
+		foreach ($line_item->children() as $child) {
260
+			if ($child->is_sub_line_item()) {
261
+				EEH_Line_Item::update_quantity($child, $qty);
262
+			}
263
+		}
264
+	}
265
+
266
+
267
+	/**
268
+	 * Updates the line item and its children's quantities to the specified number.
269
+	 * Does NOT save them or recalculate totals.
270
+	 *
271
+	 * @param EE_Line_Item $line_item
272
+	 * @param int          $new_quantity
273
+	 * @throws EE_Error
274
+	 * @throws InvalidArgumentException
275
+	 * @throws InvalidDataTypeException
276
+	 * @throws InvalidInterfaceException
277
+	 * @throws ReflectionException
278
+	 */
279
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
280
+	{
281
+		if (! $line_item->is_percent()) {
282
+			$line_item->set_quantity($new_quantity);
283
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
284
+			$line_item->save();
285
+		}
286
+		foreach ($line_item->children() as $child) {
287
+			if ($child->is_sub_line_item()) {
288
+				EEH_Line_Item::update_quantity($child, $new_quantity);
289
+			}
290
+		}
291
+	}
292
+
293
+
294
+	/**
295
+	 * Returns the new line item created by adding a purchase of the ticket
296
+	 *
297
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
298
+	 * @param EE_Ticket    $ticket
299
+	 * @param int          $qty
300
+	 * @return EE_Line_Item
301
+	 * @throws EE_Error
302
+	 * @throws InvalidArgumentException
303
+	 * @throws InvalidDataTypeException
304
+	 * @throws InvalidInterfaceException
305
+	 * @throws ReflectionException
306
+	 */
307
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
308
+	{
309
+		$datetimes = $ticket->datetimes();
310
+		$first_datetime = reset($datetimes);
311
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
312
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
313
+			$first_datetime_name = $first_datetime->event()->name();
314
+		}
315
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
316
+		// get event subtotal line
317
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
318
+		$taxes = $ticket->tax_price_modifiers();
319
+		// add $ticket to cart
320
+		$line_item = EE_Line_Item::new_instance(array(
321
+			'LIN_name'       => $ticket->name(),
322
+			'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
323
+			'LIN_unit_price' => $ticket->price(),
324
+			'LIN_quantity'   => $qty,
325
+			'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
326
+			'LIN_order'      => count($events_sub_total->children()),
327
+			'LIN_total'      => $ticket->price() * $qty,
328
+			'LIN_type'       => EEM_Line_Item::type_line_item,
329
+			'OBJ_ID'         => $ticket->ID(),
330
+			'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
331
+		));
332
+		$line_item = apply_filters(
333
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
334
+			$line_item
335
+		);
336
+		if (!$line_item instanceof EE_Line_Item) {
337
+			throw new DomainException(
338
+				esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
339
+			);
340
+		}
341
+		$events_sub_total->add_child_line_item($line_item);
342
+		// now add the sub-line items
343
+		$running_total = 0;
344
+		$running_pre_tax_total = 0;
345
+		foreach ($ticket->prices() as $price) {
346
+			$sign = $price->is_discount() ? -1 : 1;
347
+			$price_total = $price->is_percent()
348
+				? $running_pre_tax_total * $price->amount() / 100
349
+				: $price->amount() * $qty;
350
+			if ($price->is_percent()) {
351
+				$percent = $sign * $price->amount();
352
+				$unit_price = 0;
353
+			} else {
354
+				$percent    = 0;
355
+				$unit_price = $sign * $price->amount();
356
+			}
357
+			$sub_line_item = EE_Line_Item::new_instance(array(
358
+				'LIN_name'       => $price->name(),
359
+				'LIN_desc'       => $price->desc(),
360
+				'LIN_quantity'   => $price->is_percent() ? null : $qty,
361
+				'LIN_is_taxable' => false,
362
+				'LIN_order'      => $price->order(),
363
+				'LIN_total'      => $price_total,
364
+				'LIN_pretax'     => 0,
365
+				'LIN_unit_price' => $unit_price,
366
+				'LIN_percent'    => $percent,
367
+				'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
368
+				'OBJ_ID'         => $price->ID(),
369
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
370
+			));
371
+			$sub_line_item = apply_filters(
372
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
373
+				$sub_line_item
374
+			);
375
+			$running_total += $sign * $price_total;
376
+			$running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
377
+			$line_item->add_child_line_item($sub_line_item);
378
+		}
379
+		$line_item->setPretaxTotal($running_pre_tax_total);
380
+		return $line_item;
381
+	}
382
+
383
+
384
+	/**
385
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
386
+	 * re-calculates the line item totals and updates the related transaction. But
387
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
388
+	 * should probably change because of this).
389
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
390
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
391
+	 *
392
+	 * @param EE_Line_Item $total_line_item
393
+	 * @param EE_Line_Item $item to be added
394
+	 * @return boolean
395
+	 * @throws EE_Error
396
+	 * @throws InvalidArgumentException
397
+	 * @throws InvalidDataTypeException
398
+	 * @throws InvalidInterfaceException
399
+	 * @throws ReflectionException
400
+	 */
401
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
402
+	{
403
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
404
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
405
+			$success = $pre_tax_subtotal->add_child_line_item($item);
406
+		} else {
407
+			return false;
408
+		}
409
+		$total_line_item->recalculate_total_including_taxes();
410
+		return $success;
411
+	}
412
+
413
+
414
+	/**
415
+	 * cancels an existing ticket line item,
416
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
417
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
418
+	 *
419
+	 * @param EE_Line_Item $ticket_line_item
420
+	 * @param int          $qty
421
+	 * @return bool success
422
+	 * @throws EE_Error
423
+	 * @throws InvalidArgumentException
424
+	 * @throws InvalidDataTypeException
425
+	 * @throws InvalidInterfaceException
426
+	 * @throws ReflectionException
427
+	 */
428
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
429
+	{
430
+		// validate incoming line_item
431
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
432
+			throw new EE_Error(
433
+				sprintf(
434
+					esc_html__(
435
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
436
+						'event_espresso'
437
+					),
438
+					$ticket_line_item->type()
439
+				)
440
+			);
441
+		}
442
+		if ($ticket_line_item->quantity() < $qty) {
443
+			throw new EE_Error(
444
+				sprintf(
445
+					esc_html__(
446
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
447
+						'event_espresso'
448
+					),
449
+					$qty,
450
+					$ticket_line_item->quantity()
451
+				)
452
+			);
453
+		}
454
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
455
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
456
+		foreach ($ticket_line_item->children() as $child_line_item) {
457
+			if (
458
+				$child_line_item->is_sub_line_item()
459
+				&& ! $child_line_item->is_percent()
460
+				&& ! $child_line_item->is_cancellation()
461
+			) {
462
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
463
+			}
464
+		}
465
+		// get cancellation sub line item
466
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
467
+			$ticket_line_item,
468
+			EEM_Line_Item::type_cancellation
469
+		);
470
+		$cancellation_line_item = reset($cancellation_line_item);
471
+		// verify that this ticket was indeed previously cancelled
472
+		if ($cancellation_line_item instanceof EE_Line_Item) {
473
+			// increment cancelled quantity
474
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
475
+		} else {
476
+			// create cancellation sub line item
477
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
478
+				'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
479
+				'LIN_desc'       => sprintf(
480
+					esc_html_x(
481
+						'Cancelled %1$s : %2$s',
482
+						'Cancelled Ticket Name : 2015-01-01 11:11',
483
+						'event_espresso'
484
+					),
485
+					$ticket_line_item->name(),
486
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
487
+				),
488
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
489
+				'LIN_quantity'   => $qty,
490
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
491
+				'LIN_order'      => count($ticket_line_item->children()),
492
+				'LIN_total'      => 0, // $ticket_line_item->unit_price()
493
+				'LIN_type'       => EEM_Line_Item::type_cancellation,
494
+			));
495
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
496
+		}
497
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
498
+			// decrement parent line item quantity
499
+			$event_line_item = $ticket_line_item->parent();
500
+			if (
501
+				$event_line_item instanceof EE_Line_Item
502
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
503
+			) {
504
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
505
+				$event_line_item->save();
506
+			}
507
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
508
+			return true;
509
+		}
510
+		return false;
511
+	}
512
+
513
+
514
+	/**
515
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
516
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
517
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
518
+	 *
519
+	 * @param EE_Line_Item $ticket_line_item
520
+	 * @param int          $qty
521
+	 * @return bool success
522
+	 * @throws EE_Error
523
+	 * @throws InvalidArgumentException
524
+	 * @throws InvalidDataTypeException
525
+	 * @throws InvalidInterfaceException
526
+	 * @throws ReflectionException
527
+	 */
528
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
529
+	{
530
+		// validate incoming line_item
531
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
532
+			throw new EE_Error(
533
+				sprintf(
534
+					esc_html__(
535
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
536
+						'event_espresso'
537
+					),
538
+					$ticket_line_item->type()
539
+				)
540
+			);
541
+		}
542
+		// get cancellation sub line item
543
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
544
+			$ticket_line_item,
545
+			EEM_Line_Item::type_cancellation
546
+		);
547
+		$cancellation_line_item = reset($cancellation_line_item);
548
+		// verify that this ticket was indeed previously cancelled
549
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
550
+			return false;
551
+		}
552
+		if ($cancellation_line_item->quantity() > $qty) {
553
+			// decrement cancelled quantity
554
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
555
+		} elseif ($cancellation_line_item->quantity() === $qty) {
556
+			// decrement cancelled quantity in case anyone still has the object kicking around
557
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
558
+			// delete because quantity will end up as 0
559
+			$cancellation_line_item->delete();
560
+			// and attempt to destroy the object,
561
+			// even though PHP won't actually destroy it until it needs the memory
562
+			unset($cancellation_line_item);
563
+		} else {
564
+			// what ?!?! negative quantity ?!?!
565
+			throw new EE_Error(
566
+				sprintf(
567
+					esc_html__(
568
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
569
+						'event_espresso'
570
+					),
571
+					$qty,
572
+					$cancellation_line_item->quantity()
573
+				)
574
+			);
575
+		}
576
+		// increment ticket quantity
577
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
578
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
579
+			// increment parent line item quantity
580
+			$event_line_item = $ticket_line_item->parent();
581
+			if (
582
+				$event_line_item instanceof EE_Line_Item
583
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
584
+			) {
585
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
586
+			}
587
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
588
+			return true;
589
+		}
590
+		return false;
591
+	}
592
+
593
+
594
+	/**
595
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
596
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
597
+	 *
598
+	 * @param EE_Line_Item $line_item
599
+	 * @return float
600
+	 * @throws EE_Error
601
+	 * @throws InvalidArgumentException
602
+	 * @throws InvalidDataTypeException
603
+	 * @throws InvalidInterfaceException
604
+	 * @throws ReflectionException
605
+	 */
606
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
607
+	{
608
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
609
+		return $grand_total_line_item->recalculate_total_including_taxes();
610
+	}
611
+
612
+
613
+	/**
614
+	 * Gets the line item which contains the subtotal of all the items
615
+	 *
616
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
617
+	 * @return EE_Line_Item
618
+	 * @throws EE_Error
619
+	 * @throws InvalidArgumentException
620
+	 * @throws InvalidDataTypeException
621
+	 * @throws InvalidInterfaceException
622
+	 * @throws ReflectionException
623
+	 */
624
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
625
+	{
626
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
627
+		return $pre_tax_subtotal instanceof EE_Line_Item
628
+			? $pre_tax_subtotal
629
+			: self::create_pre_tax_subtotal($total_line_item);
630
+	}
631
+
632
+
633
+	/**
634
+	 * Gets the line item for the taxes subtotal
635
+	 *
636
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
637
+	 * @return EE_Line_Item
638
+	 * @throws EE_Error
639
+	 * @throws InvalidArgumentException
640
+	 * @throws InvalidDataTypeException
641
+	 * @throws InvalidInterfaceException
642
+	 * @throws ReflectionException
643
+	 */
644
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
645
+	{
646
+		$taxes = $total_line_item->get_child_line_item('taxes');
647
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
648
+	}
649
+
650
+
651
+	/**
652
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
653
+	 *
654
+	 * @param EE_Line_Item   $line_item
655
+	 * @param EE_Transaction $transaction
656
+	 * @return void
657
+	 * @throws EE_Error
658
+	 * @throws InvalidArgumentException
659
+	 * @throws InvalidDataTypeException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws ReflectionException
662
+	 */
663
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
664
+	{
665
+		if ($transaction) {
666
+			/** @type EEM_Transaction $EEM_Transaction */
667
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
668
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
669
+			$line_item->set_TXN_ID($TXN_ID);
670
+		}
671
+	}
672
+
673
+
674
+	/**
675
+	 * Creates a new default total line item for the transaction,
676
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
677
+	 * existing taxes as children of the taxes subtotal line item)
678
+	 *
679
+	 * @param EE_Transaction $transaction
680
+	 * @return EE_Line_Item of type total
681
+	 * @throws EE_Error
682
+	 * @throws InvalidArgumentException
683
+	 * @throws InvalidDataTypeException
684
+	 * @throws InvalidInterfaceException
685
+	 * @throws ReflectionException
686
+	 */
687
+	public static function create_total_line_item($transaction = null)
688
+	{
689
+		$total_line_item = EE_Line_Item::new_instance(array(
690
+			'LIN_code' => 'total',
691
+			'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
692
+			'LIN_type' => EEM_Line_Item::type_total,
693
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
694
+		));
695
+		$total_line_item = apply_filters(
696
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
697
+			$total_line_item
698
+		);
699
+		self::set_TXN_ID($total_line_item, $transaction);
700
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
701
+		self::create_taxes_subtotal($total_line_item, $transaction);
702
+		return $total_line_item;
703
+	}
704
+
705
+
706
+	/**
707
+	 * Creates a default items subtotal line item
708
+	 *
709
+	 * @param EE_Line_Item   $total_line_item
710
+	 * @param EE_Transaction $transaction
711
+	 * @return EE_Line_Item
712
+	 * @throws EE_Error
713
+	 * @throws InvalidArgumentException
714
+	 * @throws InvalidDataTypeException
715
+	 * @throws InvalidInterfaceException
716
+	 * @throws ReflectionException
717
+	 */
718
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
719
+	{
720
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
721
+			'LIN_code' => 'pre-tax-subtotal',
722
+			'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
723
+			'LIN_type' => EEM_Line_Item::type_sub_total,
724
+		));
725
+		$pre_tax_line_item = apply_filters(
726
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
727
+			$pre_tax_line_item
728
+		);
729
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
730
+		$total_line_item->add_child_line_item($pre_tax_line_item);
731
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
732
+		return $pre_tax_line_item;
733
+	}
734
+
735
+
736
+	/**
737
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
738
+	 * and applies taxes to it
739
+	 *
740
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
741
+	 * @param EE_Transaction $transaction
742
+	 * @return EE_Line_Item
743
+	 * @throws EE_Error
744
+	 * @throws InvalidArgumentException
745
+	 * @throws InvalidDataTypeException
746
+	 * @throws InvalidInterfaceException
747
+	 * @throws ReflectionException
748
+	 */
749
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
750
+	{
751
+		$tax_line_item = EE_Line_Item::new_instance(array(
752
+			'LIN_code'  => 'taxes',
753
+			'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
754
+			'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
755
+			'LIN_order' => 1000,// this should always come last
756
+		));
757
+		$tax_line_item = apply_filters(
758
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
759
+			$tax_line_item
760
+		);
761
+		self::set_TXN_ID($tax_line_item, $transaction);
762
+		$total_line_item->add_child_line_item($tax_line_item);
763
+		// and lastly, add the actual taxes
764
+		self::apply_taxes($total_line_item);
765
+		return $tax_line_item;
766
+	}
767
+
768
+
769
+	/**
770
+	 * Creates a default items subtotal line item
771
+	 *
772
+	 * @param EE_Line_Item   $pre_tax_line_item
773
+	 * @param EE_Transaction $transaction
774
+	 * @param EE_Event       $event
775
+	 * @return EE_Line_Item
776
+	 * @throws EE_Error
777
+	 * @throws InvalidArgumentException
778
+	 * @throws InvalidDataTypeException
779
+	 * @throws InvalidInterfaceException
780
+	 * @throws ReflectionException
781
+	 */
782
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
783
+	{
784
+		$event_line_item = EE_Line_Item::new_instance(array(
785
+			'LIN_code' => self::get_event_code($event),
786
+			'LIN_name' => self::get_event_name($event),
787
+			'LIN_desc' => self::get_event_desc($event),
788
+			'LIN_type' => EEM_Line_Item::type_sub_total,
789
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
790
+			'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
791
+		));
792
+		$event_line_item = apply_filters(
793
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
794
+			$event_line_item
795
+		);
796
+		self::set_TXN_ID($event_line_item, $transaction);
797
+		$pre_tax_line_item->add_child_line_item($event_line_item);
798
+		return $event_line_item;
799
+	}
800
+
801
+
802
+	/**
803
+	 * Gets what the event ticket's code SHOULD be
804
+	 *
805
+	 * @param EE_Event $event
806
+	 * @return string
807
+	 * @throws EE_Error
808
+	 */
809
+	public static function get_event_code($event)
810
+	{
811
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
812
+	}
813
+
814
+
815
+	/**
816
+	 * Gets the event name
817
+	 *
818
+	 * @param EE_Event $event
819
+	 * @return string
820
+	 * @throws EE_Error
821
+	 */
822
+	public static function get_event_name($event)
823
+	{
824
+		return $event instanceof EE_Event
825
+			? mb_substr($event->name(), 0, 245)
826
+			: esc_html__('Event', 'event_espresso');
827
+	}
828
+
829
+
830
+	/**
831
+	 * Gets the event excerpt
832
+	 *
833
+	 * @param EE_Event $event
834
+	 * @return string
835
+	 * @throws EE_Error
836
+	 */
837
+	public static function get_event_desc($event)
838
+	{
839
+		return $event instanceof EE_Event ? $event->short_description() : '';
840
+	}
841
+
842
+
843
+	/**
844
+	 * Given the grand total line item and a ticket, finds the event sub-total
845
+	 * line item the ticket's purchase should be added onto
846
+	 *
847
+	 * @access public
848
+	 * @param EE_Line_Item $grand_total the grand total line item
849
+	 * @param EE_Ticket    $ticket
850
+	 * @return EE_Line_Item
851
+	 * @throws EE_Error
852
+	 * @throws InvalidArgumentException
853
+	 * @throws InvalidDataTypeException
854
+	 * @throws InvalidInterfaceException
855
+	 * @throws ReflectionException
856
+	 */
857
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
858
+	{
859
+		$first_datetime = $ticket->first_datetime();
860
+		if (! $first_datetime instanceof EE_Datetime) {
861
+			throw new EE_Error(
862
+				sprintf(
863
+					__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
864
+					$ticket->ID()
865
+				)
866
+			);
867
+		}
868
+		$event = $first_datetime->event();
869
+		if (! $event instanceof EE_Event) {
870
+			throw new EE_Error(
871
+				sprintf(
872
+					esc_html__(
873
+						'The supplied ticket (ID %d) has no event data associated with it.',
874
+						'event_espresso'
875
+					),
876
+					$ticket->ID()
877
+				)
878
+			);
879
+		}
880
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
881
+		if (! $events_sub_total instanceof EE_Line_Item) {
882
+			throw new EE_Error(
883
+				sprintf(
884
+					esc_html__(
885
+						'There is no events sub-total for ticket %s on total line item %d',
886
+						'event_espresso'
887
+					),
888
+					$ticket->ID(),
889
+					$grand_total->ID()
890
+				)
891
+			);
892
+		}
893
+		return $events_sub_total;
894
+	}
895
+
896
+
897
+	/**
898
+	 * Gets the event line item
899
+	 *
900
+	 * @param EE_Line_Item $grand_total
901
+	 * @param EE_Event     $event
902
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
903
+	 * @throws EE_Error
904
+	 * @throws InvalidArgumentException
905
+	 * @throws InvalidDataTypeException
906
+	 * @throws InvalidInterfaceException
907
+	 * @throws ReflectionException
908
+	 */
909
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
910
+	{
911
+		/** @type EE_Event $event */
912
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
913
+		$event_line_item = null;
914
+		$found = false;
915
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
916
+			// default event subtotal, we should only ever find this the first time this method is called
917
+			if (! $event_line_item->OBJ_ID()) {
918
+				// let's use this! but first... set the event details
919
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
920
+				$found = true;
921
+				break;
922
+			}
923
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
924
+				// found existing line item for this event in the cart, so break out of loop and use this one
925
+				$found = true;
926
+				break;
927
+			}
928
+		}
929
+		if (! $found) {
930
+			// there is no event sub-total yet, so add it
931
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
932
+			// create a new "event" subtotal below that
933
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
934
+			// and set the event details
935
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
936
+		}
937
+		return $event_line_item;
938
+	}
939
+
940
+
941
+	/**
942
+	 * Creates a default items subtotal line item
943
+	 *
944
+	 * @param EE_Line_Item   $event_line_item
945
+	 * @param EE_Event       $event
946
+	 * @param EE_Transaction $transaction
947
+	 * @return void
948
+	 * @throws EE_Error
949
+	 * @throws InvalidArgumentException
950
+	 * @throws InvalidDataTypeException
951
+	 * @throws InvalidInterfaceException
952
+	 * @throws ReflectionException
953
+	 */
954
+	public static function set_event_subtotal_details(
955
+		EE_Line_Item $event_line_item,
956
+		EE_Event $event,
957
+		$transaction = null
958
+	) {
959
+		if ($event instanceof EE_Event) {
960
+			$event_line_item->set_code(self::get_event_code($event));
961
+			$event_line_item->set_name(self::get_event_name($event));
962
+			$event_line_item->set_desc(self::get_event_desc($event));
963
+			$event_line_item->set_OBJ_ID($event->ID());
964
+		}
965
+		self::set_TXN_ID($event_line_item, $transaction);
966
+	}
967
+
968
+
969
+	/**
970
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
971
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
972
+	 * any old taxes are removed
973
+	 *
974
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
975
+	 * @param bool         $update_txn_status
976
+	 * @return bool
977
+	 * @throws EE_Error
978
+	 * @throws InvalidArgumentException
979
+	 * @throws InvalidDataTypeException
980
+	 * @throws InvalidInterfaceException
981
+	 * @throws ReflectionException
982
+	 * @throws RuntimeException
983
+	 */
984
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
985
+	{
986
+		$total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
987
+		$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
988
+		$apply_global_taxes = false;
989
+		foreach ($ticket_line_items as $ticket_line_item) {
990
+			if ($ticket_line_item instanceof EE_Line_Item && $ticket_line_item->is_taxable()) {
991
+				$apply_global_taxes = true;
992
+				break;
993
+			}
994
+		}
995
+		if (! $apply_global_taxes) {
996
+			return false;
997
+		}
998
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
999
+		// just to be safe, remove its old tax line items
1000
+		$deleted = $taxes_line_item->delete_children_line_items();
1001
+		$updates = false;
1002
+		// loop thru taxes
1003
+		$global_taxes = EEH_Line_Item::getGlobalTaxes();
1004
+		foreach ($global_taxes as $order => $taxes) {
1005
+			foreach ($taxes as $tax) {
1006
+				if ($tax instanceof EE_Price) {
1007
+					$tax_line_item = EE_Line_Item::new_instance(
1008
+						array(
1009
+							'LIN_name'       => $tax->name(),
1010
+							'LIN_desc'       => $tax->desc(),
1011
+							'LIN_percent'    => $tax->amount(),
1012
+							'LIN_is_taxable' => false,
1013
+							'LIN_order'      => $order,
1014
+							'LIN_total'      => 0,
1015
+							'LIN_type'       => EEM_Line_Item::type_tax,
1016
+							'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1017
+							'OBJ_ID'         => $tax->ID(),
1018
+						)
1019
+					);
1020
+					$tax_line_item = apply_filters(
1021
+						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1022
+						$tax_line_item
1023
+					);
1024
+					$updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1025
+				}
1026
+			}
1027
+		}
1028
+		// only recalculate totals if something changed
1029
+		if ($deleted || $updates) {
1030
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1031
+			return true;
1032
+		}
1033
+		return false;
1034
+	}
1035
+
1036
+
1037
+	/**
1038
+	 * Ensures that taxes have been applied to the order, if not applies them.
1039
+	 * Returns the total amount of tax
1040
+	 *
1041
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1042
+	 * @return float
1043
+	 * @throws EE_Error
1044
+	 * @throws InvalidArgumentException
1045
+	 * @throws InvalidDataTypeException
1046
+	 * @throws InvalidInterfaceException
1047
+	 * @throws ReflectionException
1048
+	 */
1049
+	public static function ensure_taxes_applied($total_line_item)
1050
+	{
1051
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1052
+		if (! $taxes_subtotal->children()) {
1053
+			self::apply_taxes($total_line_item);
1054
+		}
1055
+		return $taxes_subtotal->total();
1056
+	}
1057
+
1058
+
1059
+	/**
1060
+	 * Deletes ALL children of the passed line item
1061
+	 *
1062
+	 * @param EE_Line_Item $parent_line_item
1063
+	 * @return bool
1064
+	 * @throws EE_Error
1065
+	 * @throws InvalidArgumentException
1066
+	 * @throws InvalidDataTypeException
1067
+	 * @throws InvalidInterfaceException
1068
+	 * @throws ReflectionException
1069
+	 */
1070
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1071
+	{
1072
+		$deleted = 0;
1073
+		foreach ($parent_line_item->children() as $child_line_item) {
1074
+			if ($child_line_item instanceof EE_Line_Item) {
1075
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1076
+				if ($child_line_item->ID()) {
1077
+					$child_line_item->delete();
1078
+					unset($child_line_item);
1079
+				} else {
1080
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1081
+				}
1082
+				$deleted++;
1083
+			}
1084
+		}
1085
+		return $deleted;
1086
+	}
1087
+
1088
+
1089
+	/**
1090
+	 * Deletes the line items as indicated by the line item code(s) provided,
1091
+	 * regardless of where they're found in the line item tree. Automatically
1092
+	 * re-calculates the line item totals and updates the related transaction. But
1093
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1094
+	 * should probably change because of this).
1095
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1096
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1097
+	 *
1098
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1099
+	 * @param array|bool|string $line_item_codes
1100
+	 * @return int number of items successfully removed
1101
+	 * @throws EE_Error
1102
+	 */
1103
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1104
+	{
1105
+
1106
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1107
+			EE_Error::doing_it_wrong(
1108
+				'EEH_Line_Item::delete_items',
1109
+				esc_html__(
1110
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1111
+					'event_espresso'
1112
+				),
1113
+				'4.6.18'
1114
+			);
1115
+		}
1116
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1117
+
1118
+		// check if only a single line_item_id was passed
1119
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1120
+			// place single line_item_id in an array to appear as multiple line_item_ids
1121
+			$line_item_codes = array($line_item_codes);
1122
+		}
1123
+		$removals = 0;
1124
+		// cycle thru line_item_ids
1125
+		foreach ($line_item_codes as $line_item_id) {
1126
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1127
+		}
1128
+
1129
+		if ($removals > 0) {
1130
+			$total_line_item->recalculate_taxes_and_tax_total();
1131
+			return $removals;
1132
+		} else {
1133
+			return false;
1134
+		}
1135
+	}
1136
+
1137
+
1138
+	/**
1139
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1140
+	 * tax and updates the total line item accordingly
1141
+	 *
1142
+	 * @param EE_Line_Item $total_line_item
1143
+	 * @param float        $amount
1144
+	 * @param string       $name
1145
+	 * @param string       $description
1146
+	 * @param string       $code
1147
+	 * @param boolean      $add_to_existing_line_item
1148
+	 *                          if true, and a duplicate line item with the same code is found,
1149
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1150
+	 * @return EE_Line_Item the new tax line item created
1151
+	 * @throws EE_Error
1152
+	 * @throws InvalidArgumentException
1153
+	 * @throws InvalidDataTypeException
1154
+	 * @throws InvalidInterfaceException
1155
+	 * @throws ReflectionException
1156
+	 */
1157
+	public static function set_total_tax_to(
1158
+		EE_Line_Item $total_line_item,
1159
+		$amount,
1160
+		$name = null,
1161
+		$description = null,
1162
+		$code = null,
1163
+		$add_to_existing_line_item = false
1164
+	) {
1165
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
1166
+		$taxable_total = $total_line_item->taxable_total();
1167
+
1168
+		if ($add_to_existing_line_item) {
1169
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1170
+			EEM_Line_Item::instance()->delete(
1171
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1172
+			);
1173
+		} else {
1174
+			$new_tax = null;
1175
+			$tax_subtotal->delete_children_line_items();
1176
+		}
1177
+		if ($new_tax) {
1178
+			$new_tax->set_total($new_tax->total() + $amount);
1179
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1180
+		} else {
1181
+			// no existing tax item. Create it
1182
+			$new_tax = EE_Line_Item::new_instance(array(
1183
+				'TXN_ID'      => $total_line_item->TXN_ID(),
1184
+				'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1185
+				'LIN_desc'    => $description ? $description : '',
1186
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1187
+				'LIN_total'   => $amount,
1188
+				'LIN_parent'  => $tax_subtotal->ID(),
1189
+				'LIN_type'    => EEM_Line_Item::type_tax,
1190
+				'LIN_code'    => $code,
1191
+			));
1192
+		}
1193
+
1194
+		$new_tax = apply_filters(
1195
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1196
+			$new_tax,
1197
+			$total_line_item
1198
+		);
1199
+		$new_tax->save();
1200
+		$tax_subtotal->set_total($new_tax->total());
1201
+		$tax_subtotal->save();
1202
+		$total_line_item->recalculate_total_including_taxes();
1203
+		return $new_tax;
1204
+	}
1205
+
1206
+
1207
+	/**
1208
+	 * Makes all the line items which are children of $line_item taxable (or not).
1209
+	 * Does NOT save the line items
1210
+	 *
1211
+	 * @param EE_Line_Item $line_item
1212
+	 * @param boolean      $taxable
1213
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1214
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1215
+	 * @throws EE_Error
1216
+	 */
1217
+	public static function set_line_items_taxable(
1218
+		EE_Line_Item $line_item,
1219
+		$taxable = true,
1220
+		$code_substring_for_whitelist = null
1221
+	) {
1222
+		$whitelisted = false;
1223
+		if ($code_substring_for_whitelist !== null) {
1224
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1225
+		}
1226
+		if (! $whitelisted && $line_item->is_line_item()) {
1227
+			$line_item->set_is_taxable($taxable);
1228
+		}
1229
+		foreach ($line_item->children() as $child_line_item) {
1230
+			EEH_Line_Item::set_line_items_taxable(
1231
+				$child_line_item,
1232
+				$taxable,
1233
+				$code_substring_for_whitelist
1234
+			);
1235
+		}
1236
+	}
1237
+
1238
+
1239
+	/**
1240
+	 * Gets all descendants that are event subtotals
1241
+	 *
1242
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1243
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1244
+	 * @return EE_Line_Item[]
1245
+	 * @throws EE_Error
1246
+	 */
1247
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1248
+	{
1249
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1250
+	}
1251
+
1252
+
1253
+	/**
1254
+	 * Gets all descendants subtotals that match the supplied object type
1255
+	 *
1256
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1257
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1258
+	 * @param string       $obj_type
1259
+	 * @return EE_Line_Item[]
1260
+	 * @throws EE_Error
1261
+	 */
1262
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1263
+	{
1264
+		return self::_get_descendants_by_type_and_object_type(
1265
+			$parent_line_item,
1266
+			EEM_Line_Item::type_sub_total,
1267
+			$obj_type
1268
+		);
1269
+	}
1270
+
1271
+
1272
+	/**
1273
+	 * Gets all descendants that are tickets
1274
+	 *
1275
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1276
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1277
+	 * @return EE_Line_Item[]
1278
+	 * @throws EE_Error
1279
+	 */
1280
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1281
+	{
1282
+		return self::get_line_items_of_object_type(
1283
+			$parent_line_item,
1284
+			EEM_Line_Item::OBJ_TYPE_TICKET
1285
+		);
1286
+	}
1287
+
1288
+
1289
+	/**
1290
+	 * Gets all descendants subtotals that match the supplied object type
1291
+	 *
1292
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1293
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1294
+	 * @param string       $obj_type
1295
+	 * @return EE_Line_Item[]
1296
+	 * @throws EE_Error
1297
+	 */
1298
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1299
+	{
1300
+		return self::_get_descendants_by_type_and_object_type(
1301
+			$parent_line_item,
1302
+			EEM_Line_Item::type_line_item,
1303
+			$obj_type
1304
+		);
1305
+	}
1306
+
1307
+
1308
+	/**
1309
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1310
+	 *
1311
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1312
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1313
+	 * @return EE_Line_Item[]
1314
+	 * @throws EE_Error
1315
+	 */
1316
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1317
+	{
1318
+		return EEH_Line_Item::get_descendants_of_type(
1319
+			$parent_line_item,
1320
+			EEM_Line_Item::type_tax
1321
+		);
1322
+	}
1323
+
1324
+
1325
+	/**
1326
+	 * Gets all the real items purchased which are children of this item
1327
+	 *
1328
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1329
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1330
+	 * @return EE_Line_Item[]
1331
+	 * @throws EE_Error
1332
+	 */
1333
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1334
+	{
1335
+		return EEH_Line_Item::get_descendants_of_type(
1336
+			$parent_line_item,
1337
+			EEM_Line_Item::type_line_item
1338
+		);
1339
+	}
1340
+
1341
+
1342
+	/**
1343
+	 * Gets all descendants of supplied line item that match the supplied line item type
1344
+	 *
1345
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1346
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1347
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1348
+	 * @return EE_Line_Item[]
1349
+	 * @throws EE_Error
1350
+	 */
1351
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1352
+	{
1353
+		return self::_get_descendants_by_type_and_object_type(
1354
+			$parent_line_item,
1355
+			$line_item_type,
1356
+			null
1357
+		);
1358
+	}
1359
+
1360
+
1361
+	/**
1362
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1363
+	 * as well
1364
+	 *
1365
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1366
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1367
+	 * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1368
+	 *                                        searching
1369
+	 * @return EE_Line_Item[]
1370
+	 * @throws EE_Error
1371
+	 */
1372
+	protected static function _get_descendants_by_type_and_object_type(
1373
+		EE_Line_Item $parent_line_item,
1374
+		$line_item_type,
1375
+		$obj_type = null
1376
+	) {
1377
+		$objects = array();
1378
+		foreach ($parent_line_item->children() as $child_line_item) {
1379
+			if ($child_line_item instanceof EE_Line_Item) {
1380
+				if (
1381
+					$child_line_item->type() === $line_item_type
1382
+					&& (
1383
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1384
+					)
1385
+				) {
1386
+					$objects[] = $child_line_item;
1387
+				} else {
1388
+					// go-through-all-its children looking for more matches
1389
+					$objects = array_merge(
1390
+						$objects,
1391
+						self::_get_descendants_by_type_and_object_type(
1392
+							$child_line_item,
1393
+							$line_item_type,
1394
+							$obj_type
1395
+						)
1396
+					);
1397
+				}
1398
+			}
1399
+		}
1400
+		return $objects;
1401
+	}
1402
+
1403
+
1404
+	/**
1405
+	 * Gets all descendants subtotals that match the supplied object type
1406
+	 *
1407
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1408
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1409
+	 * @param string       $OBJ_type         object type (like Event)
1410
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1411
+	 * @return EE_Line_Item[]
1412
+	 * @throws EE_Error
1413
+	 */
1414
+	public static function get_line_items_by_object_type_and_IDs(
1415
+		EE_Line_Item $parent_line_item,
1416
+		$OBJ_type = '',
1417
+		$OBJ_IDs = array()
1418
+	) {
1419
+		return self::_get_descendants_by_object_type_and_object_ID(
1420
+			$parent_line_item,
1421
+			$OBJ_type,
1422
+			$OBJ_IDs
1423
+		);
1424
+	}
1425
+
1426
+
1427
+	/**
1428
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1429
+	 * as well
1430
+	 *
1431
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
+	 * @param string       $OBJ_type         object type (like Event)
1433
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1434
+	 * @return EE_Line_Item[]
1435
+	 * @throws EE_Error
1436
+	 */
1437
+	protected static function _get_descendants_by_object_type_and_object_ID(
1438
+		EE_Line_Item $parent_line_item,
1439
+		$OBJ_type,
1440
+		$OBJ_IDs
1441
+	) {
1442
+		$objects = array();
1443
+		foreach ($parent_line_item->children() as $child_line_item) {
1444
+			if ($child_line_item instanceof EE_Line_Item) {
1445
+				if (
1446
+					$child_line_item->OBJ_type() === $OBJ_type
1447
+					&& is_array($OBJ_IDs)
1448
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1449
+				) {
1450
+					$objects[] = $child_line_item;
1451
+				} else {
1452
+					// go-through-all-its children looking for more matches
1453
+					$objects = array_merge(
1454
+						$objects,
1455
+						self::_get_descendants_by_object_type_and_object_ID(
1456
+							$child_line_item,
1457
+							$OBJ_type,
1458
+							$OBJ_IDs
1459
+						)
1460
+					);
1461
+				}
1462
+			}
1463
+		}
1464
+		return $objects;
1465
+	}
1466
+
1467
+
1468
+	/**
1469
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1470
+	 * the specified type and returns it, else NULL
1471
+	 *
1472
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1473
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1474
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1475
+	 * @return EE_Line_Item
1476
+	 * @throws EE_Error
1477
+	 * @throws InvalidArgumentException
1478
+	 * @throws InvalidDataTypeException
1479
+	 * @throws InvalidInterfaceException
1480
+	 * @throws ReflectionException
1481
+	 */
1482
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1483
+	{
1484
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1485
+	}
1486
+
1487
+
1488
+	/**
1489
+	 * Uses a breadth-first-search in order to find the nearest descendant
1490
+	 * having the specified LIN_code and returns it, else NULL
1491
+	 *
1492
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1493
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1494
+	 * @param string       $code             any value used for LIN_code
1495
+	 * @return EE_Line_Item
1496
+	 * @throws EE_Error
1497
+	 * @throws InvalidArgumentException
1498
+	 * @throws InvalidDataTypeException
1499
+	 * @throws InvalidInterfaceException
1500
+	 * @throws ReflectionException
1501
+	 */
1502
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1503
+	{
1504
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1505
+	}
1506
+
1507
+
1508
+	/**
1509
+	 * Uses a breadth-first-search in order to find the nearest descendant
1510
+	 * having the specified LIN_code and returns it, else NULL
1511
+	 *
1512
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1513
+	 * @param string       $search_field     name of EE_Line_Item property
1514
+	 * @param string       $value            any value stored in $search_field
1515
+	 * @return EE_Line_Item
1516
+	 * @throws EE_Error
1517
+	 * @throws InvalidArgumentException
1518
+	 * @throws InvalidDataTypeException
1519
+	 * @throws InvalidInterfaceException
1520
+	 * @throws ReflectionException
1521
+	 */
1522
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1523
+	{
1524
+		foreach ($parent_line_item->children() as $child) {
1525
+			if ($child->get($search_field) == $value) {
1526
+				return $child;
1527
+			}
1528
+		}
1529
+		foreach ($parent_line_item->children() as $child) {
1530
+			$descendant_found = self::_get_nearest_descendant(
1531
+				$child,
1532
+				$search_field,
1533
+				$value
1534
+			);
1535
+			if ($descendant_found) {
1536
+				return $descendant_found;
1537
+			}
1538
+		}
1539
+		return null;
1540
+	}
1541
+
1542
+
1543
+	/**
1544
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1545
+	 * else recursively walks up the line item tree until a parent of type total is found,
1546
+	 *
1547
+	 * @param EE_Line_Item $line_item
1548
+	 * @return EE_Line_Item
1549
+	 * @throws EE_Error
1550
+	 * @throws ReflectionException
1551
+	 */
1552
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1553
+	{
1554
+		if ($line_item->is_total()) {
1555
+			return $line_item;
1556
+		}
1557
+		if ($line_item->TXN_ID()) {
1558
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1559
+			if ($total_line_item instanceof EE_Line_Item) {
1560
+				return $total_line_item;
1561
+			}
1562
+		} else {
1563
+			$line_item_parent = $line_item->parent();
1564
+			if ($line_item_parent instanceof EE_Line_Item) {
1565
+				if ($line_item_parent->is_total()) {
1566
+					return $line_item_parent;
1567
+				}
1568
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1569
+			}
1570
+		}
1571
+		throw new EE_Error(
1572
+			sprintf(
1573
+				esc_html__(
1574
+					'A valid grand total for line item %1$d was not found.',
1575
+					'event_espresso'
1576
+				),
1577
+				$line_item->ID()
1578
+			)
1579
+		);
1580
+	}
1581
+
1582
+
1583
+	/**
1584
+	 * Prints out a representation of the line item tree
1585
+	 *
1586
+	 * @param EE_Line_Item $line_item
1587
+	 * @param int          $indentation
1588
+	 * @return void
1589
+	 * @throws EE_Error
1590
+	 */
1591
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1592
+	{
1593
+		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1594
+		if (! $indentation) {
1595
+			echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1596
+		}
1597
+		echo str_repeat('. ', $indentation);
1598
+		$breakdown = '';
1599
+		if ($line_item->is_line_item()) {
1600
+			if ($line_item->is_percent()) {
1601
+				$breakdown = "{$line_item->percent()}%";
1602
+			} else {
1603
+				$breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1604
+			}
1605
+		}
1606
+		echo $line_item->name();
1607
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1608
+		echo "\${$line_item->total()}";
1609
+		if ($breakdown) {
1610
+			echo " ( {$breakdown} )";
1611
+		}
1612
+		if ($line_item->is_taxable()) {
1613
+			echo '  * taxable';
1614
+		}
1615
+		if ($line_item->children()) {
1616
+			foreach ($line_item->children() as $child) {
1617
+				self::visualize($child, $indentation + 1);
1618
+			}
1619
+		}
1620
+	}
1621
+
1622
+
1623
+	/**
1624
+	 * Calculates the registration's final price, taking into account that they
1625
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1626
+	 * and receive a portion of any transaction-wide discounts.
1627
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1628
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1629
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1630
+	 * and brent's final price should be $5.50.
1631
+	 * In order to do this, we basically need to traverse the line item tree calculating
1632
+	 * the running totals (just as if we were recalculating the total), but when we identify
1633
+	 * regular line items, we need to keep track of their share of the grand total.
1634
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1635
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1636
+	 * when there are non-taxable items; otherwise they would be the same)
1637
+	 *
1638
+	 * @param EE_Line_Item $line_item
1639
+	 * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1640
+	 *                                                  can be included in price calculations at this moment
1641
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1642
+	 *                                                  plus the key 'total', and 'taxable' which also has keys of all
1643
+	 *                                                  the ticket IDs.
1644
+	 *                                                  Eg array(
1645
+	 *                                                      12 => 4.3
1646
+	 *                                                      23 => 8.0
1647
+	 *                                                      'total' => 16.6,
1648
+	 *                                                      'taxable' => array(
1649
+	 *                                                          12 => 10,
1650
+	 *                                                          23 => 4
1651
+	 *                                                      ).
1652
+	 *                                                  So to find which registrations have which final price, we need
1653
+	 *                                                  to find which line item is theirs, which can be done with
1654
+	 *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1655
+	 *                                                  $registration );`
1656
+	 * @throws EE_Error
1657
+	 * @throws InvalidArgumentException
1658
+	 * @throws InvalidDataTypeException
1659
+	 * @throws InvalidInterfaceException
1660
+	 * @throws ReflectionException
1661
+	 */
1662
+	public static function calculate_reg_final_prices_per_line_item(
1663
+		EE_Line_Item $line_item,
1664
+		$billable_ticket_quantities = array()
1665
+	) {
1666
+		$running_totals = [
1667
+			'total'   => 0,
1668
+			'taxable' => ['total' => 0]
1669
+		];
1670
+		foreach ($line_item->children() as $child_line_item) {
1671
+			switch ($child_line_item->type()) {
1672
+				case EEM_Line_Item::type_sub_total:
1673
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1674
+						$child_line_item,
1675
+						$billable_ticket_quantities
1676
+					);
1677
+					// combine arrays but preserve numeric keys
1678
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1679
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1680
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1681
+					break;
1682
+
1683
+				case EEM_Line_Item::type_tax_sub_total:
1684
+					// find how much the taxes percentage is
1685
+					if ($child_line_item->percent() !== 0) {
1686
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1687
+					} else {
1688
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1689
+					}
1690
+					// and apply to all the taxable totals, and add to the pretax totals
1691
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1692
+						// "total" and "taxable" array key is an exception
1693
+						if ($line_item_id === 'taxable') {
1694
+							continue;
1695
+						}
1696
+						$taxable_total = $running_totals['taxable'][ $line_item_id ];
1697
+						$running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1698
+					}
1699
+					break;
1700
+
1701
+				case EEM_Line_Item::type_line_item:
1702
+					// ticket line items or ????
1703
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1704
+						// kk it's a ticket
1705
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1706
+							// huh? that shouldn't happen.
1707
+							$running_totals['total'] += $child_line_item->total();
1708
+						} else {
1709
+							// its not in our running totals yet. great.
1710
+							if ($child_line_item->is_taxable()) {
1711
+								$taxable_amount = $child_line_item->unit_price();
1712
+							} else {
1713
+								$taxable_amount = 0;
1714
+							}
1715
+							// are we only calculating totals for some tickets?
1716
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1717
+								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1718
+								$running_totals[ $child_line_item->ID() ] = $quantity
1719
+									? $child_line_item->unit_price()
1720
+									: 0;
1721
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1722
+									? $taxable_amount
1723
+									: 0;
1724
+							} else {
1725
+								$quantity = $child_line_item->quantity();
1726
+								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1727
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1728
+							}
1729
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1730
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1731
+						}
1732
+					} else {
1733
+						// it's some other type of item added to the cart
1734
+						// it should affect the running totals
1735
+						// basically we want to convert it into a PERCENT modifier. Because
1736
+						// more clearly affect all registration's final price equally
1737
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1738
+							? ($child_line_item->total() / $running_totals['total']) + 1
1739
+							: 1;
1740
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1741
+							// the "taxable" array key is an exception
1742
+							if ($line_item_id === 'taxable') {
1743
+								continue;
1744
+							}
1745
+							// update the running totals
1746
+							// yes this actually even works for the running grand total!
1747
+							$running_totals[ $line_item_id ] =
1748
+								$line_items_percent_of_running_total * $this_running_total;
1749
+
1750
+							if ($child_line_item->is_taxable()) {
1751
+								$running_totals['taxable'][ $line_item_id ] =
1752
+									$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1753
+							}
1754
+						}
1755
+					}
1756
+					break;
1757
+			}
1758
+		}
1759
+		return $running_totals;
1760
+	}
1761
+
1762
+
1763
+	/**
1764
+	 * @param EE_Line_Item $total_line_item
1765
+	 * @param EE_Line_Item $ticket_line_item
1766
+	 * @return float | null
1767
+	 * @throws EE_Error
1768
+	 * @throws InvalidArgumentException
1769
+	 * @throws InvalidDataTypeException
1770
+	 * @throws InvalidInterfaceException
1771
+	 * @throws OutOfRangeException
1772
+	 * @throws ReflectionException
1773
+	 */
1774
+	public static function calculate_final_price_for_ticket_line_item(
1775
+		EE_Line_Item $total_line_item,
1776
+		EE_Line_Item $ticket_line_item
1777
+	) {
1778
+		static $final_prices_per_ticket_line_item = array();
1779
+		if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1780
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1781
+				$total_line_item
1782
+			);
1783
+		}
1784
+		// ok now find this new registration's final price
1785
+		if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1786
+			return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1787
+		}
1788
+		$message = sprintf(
1789
+			esc_html__(
1790
+				'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1791
+				'event_espresso'
1792
+			),
1793
+			$ticket_line_item->ID()
1794
+		);
1795
+		if (WP_DEBUG) {
1796
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1797
+			throw new OutOfRangeException($message);
1798
+		}
1799
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1800
+		return null;
1801
+	}
1802
+
1803
+
1804
+	/**
1805
+	 * Creates a duplicate of the line item tree, except only includes billable items
1806
+	 * and the portion of line items attributed to billable things
1807
+	 *
1808
+	 * @param EE_Line_Item      $line_item
1809
+	 * @param EE_Registration[] $registrations
1810
+	 * @return EE_Line_Item
1811
+	 * @throws EE_Error
1812
+	 * @throws InvalidArgumentException
1813
+	 * @throws InvalidDataTypeException
1814
+	 * @throws InvalidInterfaceException
1815
+	 * @throws ReflectionException
1816
+	 */
1817
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1818
+	{
1819
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1820
+		foreach ($line_item->children() as $child_li) {
1821
+			$copy_li->add_child_line_item(
1822
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1823
+			);
1824
+		}
1825
+		// if this is the grand total line item, make sure the totals all add up
1826
+		// (we could have duplicated this logic AS we copied the line items, but
1827
+		// it seems DRYer this way)
1828
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1829
+			$copy_li->recalculate_total_including_taxes();
1830
+		}
1831
+		return $copy_li;
1832
+	}
1833
+
1834
+
1835
+	/**
1836
+	 * Creates a new, unsaved line item from $line_item that factors in the
1837
+	 * number of billable registrations on $registrations.
1838
+	 *
1839
+	 * @param EE_Line_Item      $line_item
1840
+	 * @param EE_Registration[] $registrations
1841
+	 * @return EE_Line_Item
1842
+	 * @throws EE_Error
1843
+	 * @throws InvalidArgumentException
1844
+	 * @throws InvalidDataTypeException
1845
+	 * @throws InvalidInterfaceException
1846
+	 * @throws ReflectionException
1847
+	 */
1848
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1849
+	{
1850
+		$new_li_fields = $line_item->model_field_array();
1851
+		if (
1852
+			$line_item->type() === EEM_Line_Item::type_line_item &&
1853
+			$line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1854
+		) {
1855
+			$count = 0;
1856
+			foreach ($registrations as $registration) {
1857
+				if (
1858
+					$line_item->OBJ_ID() === $registration->ticket_ID() &&
1859
+					in_array(
1860
+						$registration->status_ID(),
1861
+						EEM_Registration::reg_statuses_that_allow_payment(),
1862
+						true
1863
+					)
1864
+				) {
1865
+					$count++;
1866
+				}
1867
+			}
1868
+			$new_li_fields['LIN_quantity'] = $count;
1869
+		}
1870
+		// don't set the total. We'll leave that up to the code that calculates it
1871
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1872
+		return EE_Line_Item::new_instance($new_li_fields);
1873
+	}
1874
+
1875
+
1876
+	/**
1877
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1878
+	 * are removed, and line items with a quantity of 0
1879
+	 *
1880
+	 * @param EE_Line_Item $line_item |null
1881
+	 * @return EE_Line_Item|null
1882
+	 * @throws EE_Error
1883
+	 * @throws InvalidArgumentException
1884
+	 * @throws InvalidDataTypeException
1885
+	 * @throws InvalidInterfaceException
1886
+	 * @throws ReflectionException
1887
+	 */
1888
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1889
+	{
1890
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1891
+		if ($copied_li === null) {
1892
+			return null;
1893
+		}
1894
+		// if this is an event subtotal, we want to only include it if it
1895
+		// has a non-zero total and at least one ticket line item child
1896
+		$ticket_children = 0;
1897
+		foreach ($line_item->children() as $child_li) {
1898
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1899
+			if ($child_li_copy !== null) {
1900
+				$copied_li->add_child_line_item($child_li_copy);
1901
+				if (
1902
+					$child_li_copy->type() === EEM_Line_Item::type_line_item &&
1903
+					$child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1904
+				) {
1905
+					$ticket_children++;
1906
+				}
1907
+			}
1908
+		}
1909
+		// if this is an event subtotal with NO ticket children
1910
+		// we basically want to ignore it
1911
+		if (
1912
+			$ticket_children === 0
1913
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1914
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1915
+			&& $line_item->total() === 0
1916
+		) {
1917
+			return null;
1918
+		}
1919
+		return $copied_li;
1920
+	}
1921
+
1922
+
1923
+	/**
1924
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1925
+	 * with a total of 0, or a subtotal of 0, returns null instead
1926
+	 *
1927
+	 * @param EE_Line_Item $line_item
1928
+	 * @return EE_Line_Item
1929
+	 * @throws EE_Error
1930
+	 * @throws InvalidArgumentException
1931
+	 * @throws InvalidDataTypeException
1932
+	 * @throws InvalidInterfaceException
1933
+	 * @throws ReflectionException
1934
+	 */
1935
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1936
+	{
1937
+		if (
1938
+			$line_item->type() === EEM_Line_Item::type_line_item
1939
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1940
+			&& $line_item->quantity() === 0
1941
+		) {
1942
+			return null;
1943
+		}
1944
+		$new_li_fields = $line_item->model_field_array();
1945
+		// don't set the total. We'll leave that up to the code that calculates it
1946
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1947
+		return EE_Line_Item::new_instance($new_li_fields);
1948
+	}
1949
+
1950
+
1951
+	/**
1952
+	 * Cycles through all of the ticket line items for the supplied total line item
1953
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1954
+	 *
1955
+	 * @param EE_Line_Item $total_line_item
1956
+	 * @since 4.9.79.p
1957
+	 * @throws EE_Error
1958
+	 * @throws InvalidArgumentException
1959
+	 * @throws InvalidDataTypeException
1960
+	 * @throws InvalidInterfaceException
1961
+	 * @throws ReflectionException
1962
+	 */
1963
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1964
+	{
1965
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1966
+		foreach ($ticket_line_items as $ticket_line_item) {
1967
+			if (
1968
+				$ticket_line_item instanceof EE_Line_Item
1969
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1970
+			) {
1971
+				$ticket = $ticket_line_item->ticket();
1972
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
1973
+					$ticket_line_item->set_is_taxable($ticket->taxable());
1974
+					$ticket_line_item->save();
1975
+				}
1976
+			}
1977
+		}
1978
+	}
1979
+
1980
+
1981
+	/**
1982
+	 * @return EE_Line_Item[]
1983
+	 * @throws EE_Error
1984
+	 * @throws ReflectionException
1985
+	 * @since   $VID:$
1986
+	 */
1987
+	private static function getGlobalTaxes(): array
1988
+	{
1989
+		if (EEH_Line_Item::$global_taxes === null) {
1990
+
1991
+			/** @type EEM_Price $EEM_Price */
1992
+			$EEM_Price = EE_Registry::instance()->load_model('Price');
1993
+			// get array of taxes via Price Model
1994
+			EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
1995
+			ksort(EEH_Line_Item::$global_taxes);
1996
+		}
1997
+		return EEH_Line_Item::$global_taxes;
1998
+	}
1999
+
2000
+
2001
+
2002
+	/**************************************** @DEPRECATED METHODS *************************************** */
2003
+	/**
2004
+	 * @deprecated
2005
+	 * @param EE_Line_Item $total_line_item
2006
+	 * @return EE_Line_Item
2007
+	 * @throws EE_Error
2008
+	 * @throws InvalidArgumentException
2009
+	 * @throws InvalidDataTypeException
2010
+	 * @throws InvalidInterfaceException
2011
+	 * @throws ReflectionException
2012
+	 */
2013
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
2014
+	{
2015
+		EE_Error::doing_it_wrong(
2016
+			'EEH_Line_Item::get_items_subtotal()',
2017
+			sprintf(
2018
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2019
+				'EEH_Line_Item::get_pre_tax_subtotal()'
2020
+			),
2021
+			'4.6.0'
2022
+		);
2023
+		return self::get_pre_tax_subtotal($total_line_item);
2024
+	}
2025
+
2026
+
2027
+	/**
2028
+	 * @deprecated
2029
+	 * @param EE_Transaction $transaction
2030
+	 * @return EE_Line_Item
2031
+	 * @throws EE_Error
2032
+	 * @throws InvalidArgumentException
2033
+	 * @throws InvalidDataTypeException
2034
+	 * @throws InvalidInterfaceException
2035
+	 * @throws ReflectionException
2036
+	 */
2037
+	public static function create_default_total_line_item($transaction = null)
2038
+	{
2039
+		EE_Error::doing_it_wrong(
2040
+			'EEH_Line_Item::create_default_total_line_item()',
2041
+			sprintf(
2042
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2043
+				'EEH_Line_Item::create_total_line_item()'
2044
+			),
2045
+			'4.6.0'
2046
+		);
2047
+		return self::create_total_line_item($transaction);
2048
+	}
2049
+
2050
+
2051
+	/**
2052
+	 * @deprecated
2053
+	 * @param EE_Line_Item   $total_line_item
2054
+	 * @param EE_Transaction $transaction
2055
+	 * @return EE_Line_Item
2056
+	 * @throws EE_Error
2057
+	 * @throws InvalidArgumentException
2058
+	 * @throws InvalidDataTypeException
2059
+	 * @throws InvalidInterfaceException
2060
+	 * @throws ReflectionException
2061
+	 */
2062
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2063
+	{
2064
+		EE_Error::doing_it_wrong(
2065
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2066
+			sprintf(
2067
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2068
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2069
+			),
2070
+			'4.6.0'
2071
+		);
2072
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2073
+	}
2074
+
2075
+
2076
+	/**
2077
+	 * @deprecated
2078
+	 * @param EE_Line_Item   $total_line_item
2079
+	 * @param EE_Transaction $transaction
2080
+	 * @return EE_Line_Item
2081
+	 * @throws EE_Error
2082
+	 * @throws InvalidArgumentException
2083
+	 * @throws InvalidDataTypeException
2084
+	 * @throws InvalidInterfaceException
2085
+	 * @throws ReflectionException
2086
+	 */
2087
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2088
+	{
2089
+		EE_Error::doing_it_wrong(
2090
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2091
+			sprintf(
2092
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2093
+				'EEH_Line_Item::create_taxes_subtotal()'
2094
+			),
2095
+			'4.6.0'
2096
+		);
2097
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2098
+	}
2099
+
2100
+
2101
+	/**
2102
+	 * @deprecated
2103
+	 * @param EE_Line_Item   $total_line_item
2104
+	 * @param EE_Transaction $transaction
2105
+	 * @return EE_Line_Item
2106
+	 * @throws EE_Error
2107
+	 * @throws InvalidArgumentException
2108
+	 * @throws InvalidDataTypeException
2109
+	 * @throws InvalidInterfaceException
2110
+	 * @throws ReflectionException
2111
+	 */
2112
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2113
+	{
2114
+		EE_Error::doing_it_wrong(
2115
+			'EEH_Line_Item::create_default_event_subtotal()',
2116
+			sprintf(
2117
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2118
+				'EEH_Line_Item::create_event_subtotal()'
2119
+			),
2120
+			'4.6.0'
2121
+		);
2122
+		return self::create_event_subtotal($total_line_item, $transaction);
2123
+	}
2124 2124
 }
Please login to merge, or discard this patch.
Spacing   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -143,7 +143,7 @@  discard block
 block discarded – undo
143 143
      */
144 144
     public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
145 145
     {
146
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
146
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
147 147
             throw new EE_Error(
148 148
                 sprintf(
149 149
                     esc_html__(
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
         // either increment the qty for an existing ticket
159 159
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
160 160
         // or add a new one
161
-        if (! $line_item instanceof EE_Line_Item) {
161
+        if ( ! $line_item instanceof EE_Line_Item) {
162 162
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
163 163
         }
164 164
         $total_line_item->recalculate_total_including_taxes();
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
      */
221 221
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
222 222
     {
223
-        if (! $line_item->is_percent()) {
223
+        if ( ! $line_item->is_percent()) {
224 224
             $qty += $line_item->quantity();
225 225
             $line_item->set_quantity($qty);
226 226
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
      */
250 250
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
251 251
     {
252
-        if (! $line_item->is_percent()) {
252
+        if ( ! $line_item->is_percent()) {
253 253
             $qty = $line_item->quantity() - $qty;
254 254
             $qty = max($qty, 0);
255 255
             $line_item->set_quantity($qty);
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
      */
279 279
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
280 280
     {
281
-        if (! $line_item->is_percent()) {
281
+        if ( ! $line_item->is_percent()) {
282 282
             $line_item->set_quantity($new_quantity);
283 283
             $line_item->set_total($line_item->unit_price() * $new_quantity);
284 284
             $line_item->save();
@@ -319,7 +319,7 @@  discard block
 block discarded – undo
319 319
         // add $ticket to cart
320 320
         $line_item = EE_Line_Item::new_instance(array(
321 321
             'LIN_name'       => $ticket->name(),
322
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
322
+            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
323 323
             'LIN_unit_price' => $ticket->price(),
324 324
             'LIN_quantity'   => $qty,
325 325
             'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
             'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
334 334
             $line_item
335 335
         );
336
-        if (!$line_item instanceof EE_Line_Item) {
336
+        if ( ! $line_item instanceof EE_Line_Item) {
337 337
             throw new DomainException(
338 338
                 esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
339 339
             );
@@ -483,7 +483,7 @@  discard block
 block discarded – undo
483 483
                         'event_espresso'
484 484
                     ),
485 485
                     $ticket_line_item->name(),
486
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
486
+                    current_time(get_option('date_format').' '.get_option('time_format'))
487 487
                 ),
488 488
                 'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
489 489
                 'LIN_quantity'   => $qty,
@@ -546,7 +546,7 @@  discard block
 block discarded – undo
546 546
         );
547 547
         $cancellation_line_item = reset($cancellation_line_item);
548 548
         // verify that this ticket was indeed previously cancelled
549
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
549
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
550 550
             return false;
551 551
         }
552 552
         if ($cancellation_line_item->quantity() > $qty) {
@@ -752,7 +752,7 @@  discard block
 block discarded – undo
752 752
             'LIN_code'  => 'taxes',
753 753
             'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
754 754
             'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
755
-            'LIN_order' => 1000,// this should always come last
755
+            'LIN_order' => 1000, // this should always come last
756 756
         ));
757 757
         $tax_line_item = apply_filters(
758 758
             'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
@@ -808,7 +808,7 @@  discard block
 block discarded – undo
808 808
      */
809 809
     public static function get_event_code($event)
810 810
     {
811
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
811
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
812 812
     }
813 813
 
814 814
 
@@ -857,7 +857,7 @@  discard block
 block discarded – undo
857 857
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
858 858
     {
859 859
         $first_datetime = $ticket->first_datetime();
860
-        if (! $first_datetime instanceof EE_Datetime) {
860
+        if ( ! $first_datetime instanceof EE_Datetime) {
861 861
             throw new EE_Error(
862 862
                 sprintf(
863 863
                     __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
@@ -866,7 +866,7 @@  discard block
 block discarded – undo
866 866
             );
867 867
         }
868 868
         $event = $first_datetime->event();
869
-        if (! $event instanceof EE_Event) {
869
+        if ( ! $event instanceof EE_Event) {
870 870
             throw new EE_Error(
871 871
                 sprintf(
872 872
                     esc_html__(
@@ -878,7 +878,7 @@  discard block
 block discarded – undo
878 878
             );
879 879
         }
880 880
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
881
-        if (! $events_sub_total instanceof EE_Line_Item) {
881
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
882 882
             throw new EE_Error(
883 883
                 sprintf(
884 884
                     esc_html__(
@@ -914,7 +914,7 @@  discard block
 block discarded – undo
914 914
         $found = false;
915 915
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
916 916
             // default event subtotal, we should only ever find this the first time this method is called
917
-            if (! $event_line_item->OBJ_ID()) {
917
+            if ( ! $event_line_item->OBJ_ID()) {
918 918
                 // let's use this! but first... set the event details
919 919
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
920 920
                 $found = true;
@@ -926,7 +926,7 @@  discard block
 block discarded – undo
926 926
                 break;
927 927
             }
928 928
         }
929
-        if (! $found) {
929
+        if ( ! $found) {
930 930
             // there is no event sub-total yet, so add it
931 931
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
932 932
             // create a new "event" subtotal below that
@@ -992,7 +992,7 @@  discard block
 block discarded – undo
992 992
                 break;
993 993
             }
994 994
         }
995
-        if (! $apply_global_taxes) {
995
+        if ( ! $apply_global_taxes) {
996 996
             return false;
997 997
         }
998 998
         $taxes_line_item = self::get_taxes_subtotal($total_line_item);
@@ -1049,7 +1049,7 @@  discard block
 block discarded – undo
1049 1049
     public static function ensure_taxes_applied($total_line_item)
1050 1050
     {
1051 1051
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1052
-        if (! $taxes_subtotal->children()) {
1052
+        if ( ! $taxes_subtotal->children()) {
1053 1053
             self::apply_taxes($total_line_item);
1054 1054
         }
1055 1055
         return $taxes_subtotal->total();
@@ -1116,7 +1116,7 @@  discard block
 block discarded – undo
1116 1116
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1117 1117
 
1118 1118
         // check if only a single line_item_id was passed
1119
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1119
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
1120 1120
             // place single line_item_id in an array to appear as multiple line_item_ids
1121 1121
             $line_item_codes = array($line_item_codes);
1122 1122
         }
@@ -1223,7 +1223,7 @@  discard block
 block discarded – undo
1223 1223
         if ($code_substring_for_whitelist !== null) {
1224 1224
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1225 1225
         }
1226
-        if (! $whitelisted && $line_item->is_line_item()) {
1226
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1227 1227
             $line_item->set_is_taxable($taxable);
1228 1228
         }
1229 1229
         foreach ($line_item->children() as $child_line_item) {
@@ -1591,7 +1591,7 @@  discard block
 block discarded – undo
1591 1591
     public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1592 1592
     {
1593 1593
         echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1594
-        if (! $indentation) {
1594
+        if ( ! $indentation) {
1595 1595
             echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1596 1596
         }
1597 1597
         echo str_repeat('. ', $indentation);
@@ -1693,8 +1693,8 @@  discard block
 block discarded – undo
1693 1693
                         if ($line_item_id === 'taxable') {
1694 1694
                             continue;
1695 1695
                         }
1696
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1697
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1696
+                        $taxable_total = $running_totals['taxable'][$line_item_id];
1697
+                        $running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1698 1698
                     }
1699 1699
                     break;
1700 1700
 
@@ -1702,7 +1702,7 @@  discard block
 block discarded – undo
1702 1702
                     // ticket line items or ????
1703 1703
                     if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1704 1704
                         // kk it's a ticket
1705
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1705
+                        if (isset($running_totals[$child_line_item->ID()])) {
1706 1706
                             // huh? that shouldn't happen.
1707 1707
                             $running_totals['total'] += $child_line_item->total();
1708 1708
                         } else {
@@ -1713,18 +1713,18 @@  discard block
 block discarded – undo
1713 1713
                                 $taxable_amount = 0;
1714 1714
                             }
1715 1715
                             // are we only calculating totals for some tickets?
1716
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1717
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1718
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1716
+                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1717
+                                $quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1718
+                                $running_totals[$child_line_item->ID()] = $quantity
1719 1719
                                     ? $child_line_item->unit_price()
1720 1720
                                     : 0;
1721
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1721
+                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1722 1722
                                     ? $taxable_amount
1723 1723
                                     : 0;
1724 1724
                             } else {
1725 1725
                                 $quantity = $child_line_item->quantity();
1726
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1727
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1726
+                                $running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1727
+                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1728 1728
                             }
1729 1729
                             $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1730 1730
                             $running_totals['total'] += $child_line_item->unit_price() * $quantity;
@@ -1744,12 +1744,12 @@  discard block
 block discarded – undo
1744 1744
                             }
1745 1745
                             // update the running totals
1746 1746
                             // yes this actually even works for the running grand total!
1747
-                            $running_totals[ $line_item_id ] =
1747
+                            $running_totals[$line_item_id] =
1748 1748
                                 $line_items_percent_of_running_total * $this_running_total;
1749 1749
 
1750 1750
                             if ($child_line_item->is_taxable()) {
1751
-                                $running_totals['taxable'][ $line_item_id ] =
1752
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1751
+                                $running_totals['taxable'][$line_item_id] =
1752
+                                    $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1753 1753
                             }
1754 1754
                         }
1755 1755
                     }
@@ -1776,14 +1776,14 @@  discard block
 block discarded – undo
1776 1776
         EE_Line_Item $ticket_line_item
1777 1777
     ) {
1778 1778
         static $final_prices_per_ticket_line_item = array();
1779
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1780
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1779
+        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[$total_line_item->ID()])) {
1780
+            $final_prices_per_ticket_line_item[$total_line_item->ID()] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1781 1781
                 $total_line_item
1782 1782
             );
1783 1783
         }
1784 1784
         // ok now find this new registration's final price
1785
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1786
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1785
+        if (isset($final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()])) {
1786
+            return $final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()];
1787 1787
         }
1788 1788
         $message = sprintf(
1789 1789
             esc_html__(
@@ -1793,7 +1793,7 @@  discard block
 block discarded – undo
1793 1793
             $ticket_line_item->ID()
1794 1794
         );
1795 1795
         if (WP_DEBUG) {
1796
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1796
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1797 1797
             throw new OutOfRangeException($message);
1798 1798
         }
1799 1799
         EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/interfaces/EEI_Transaction.interface.php 1 patch
Indentation   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -6,63 +6,63 @@
 block discarded – undo
6 6
 interface EEI_Transaction extends EEI_Base
7 7
 {
8 8
 
9
-    /**
10
-     * @return EEI_Payment
11
-     */
12
-    public function last_payment();
9
+	/**
10
+	 * @return EEI_Payment
11
+	 */
12
+	public function last_payment();
13 13
 
14 14
 
15 15
 
16
-    /**
17
-     * Gets the total that should eb paid for this transaction
18
-     *
19
-     * @return float
20
-     */
21
-    public function total();
16
+	/**
17
+	 * Gets the total that should eb paid for this transaction
18
+	 *
19
+	 * @return float
20
+	 */
21
+	public function total();
22 22
 
23 23
 
24 24
 
25
-    /**
26
-     * Get the line item that represents the total for the transaction
27
-     *
28
-     * @return EE_Line_Item
29
-     */
30
-    public function total_line_item();
25
+	/**
26
+	 * Get the line item that represents the total for the transaction
27
+	 *
28
+	 * @return EE_Line_Item
29
+	 */
30
+	public function total_line_item();
31 31
 
32 32
 
33 33
 
34
-    /**
35
-     * Gets the primary registration for this transaction
36
-     *
37
-     * @return EEI_Registration
38
-     */
39
-    public function primary_registration();
34
+	/**
35
+	 * Gets the primary registration for this transaction
36
+	 *
37
+	 * @return EEI_Registration
38
+	 */
39
+	public function primary_registration();
40 40
 
41 41
 
42 42
 
43
-    /**
44
-     * Returns the balance due on the transaction
45
-     *
46
-     * @return float
47
-     */
48
-    public function remaining();
43
+	/**
44
+	 * Returns the balance due on the transaction
45
+	 *
46
+	 * @return float
47
+	 */
48
+	public function remaining();
49 49
 
50 50
 
51 51
 
52
-    /**
53
-     *        get Total Amount Paid to Date
54
-     *
55
-     * @access        public
56
-     * @return float
57
-     */
58
-    public function paid();
52
+	/**
53
+	 *        get Total Amount Paid to Date
54
+	 *
55
+	 * @access        public
56
+	 * @return float
57
+	 */
58
+	public function paid();
59 59
 
60 60
 
61 61
 
62
-    /**
63
-     * Retrieves all the pending payments on this transaction
64
-     *
65
-     * @return EEI_Payment[]
66
-     */
67
-    public function pending_payments();
62
+	/**
63
+	 * Retrieves all the pending payments on this transaction
64
+	 *
65
+	 * @return EEI_Payment[]
66
+	 */
67
+	public function pending_payments();
68 68
 }
Please login to merge, or discard this patch.
core/interfaces/line_items/EE_Line_Item_Filter.interface.php 1 patch
Indentation   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -15,11 +15,11 @@
 block discarded – undo
15 15
  */
16 16
 interface EE_Line_Item_Filter
17 17
 {
18
-    /**
19
-     * process
20
-     *
21
-     * @param EE_Line_Item $line_item
22
-     * @return EE_Line_Item
23
-     */
24
-    public function process(EE_Line_Item $line_item): ?EE_Line_Item;
18
+	/**
19
+	 * process
20
+	 *
21
+	 * @param EE_Line_Item $line_item
22
+	 * @return EE_Line_Item
23
+	 */
24
+	public function process(EE_Line_Item $line_item): ?EE_Line_Item;
25 25
 }
Please login to merge, or discard this patch.