Completed
Branch BUG/3560-ticket-taxes (b6e55a)
by
unknown
04:48 queued 02:35
created
core/services/helpers/DecimalValues.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -6,57 +6,57 @@
 block discarded – undo
6 6
 
7 7
 class DecimalValues
8 8
 {
9
-    /**
10
-     * number of decimal places to round numbers to when performing calculations
11
-     *
12
-     * @var integer
13
-     */
14
-    protected $decimal_precision = 6;
15
-
16
-    /**
17
-     * number of decimal places to round numbers to for display
18
-     *
19
-     * @var integer
20
-     */
21
-    protected $locale_precision = 6;
22
-
23
-
24
-    /**
25
-     * @param EE_Currency_Config $currency_config
26
-     */
27
-    public function __construct(EE_Currency_Config $currency_config)
28
-    {
29
-        $this->locale_precision = $currency_config->dec_plc;
30
-    }
31
-
32
-    /**
33
-     * strips formatting, rounds the provided number, and returns a float
34
-     * if $round is set to true, then the decimal precision for the site locale will be used,
35
-     * otherwise the default decimal precision of 6 will be used
36
-     *
37
-     * @param float|int|string $number unformatted number value, ex: 1234.5678956789
38
-     * @param bool             $round  whether to round the price off according to the locale settings
39
-     * @return float                      rounded value, ex: 1,234.567896
40
-     */
41
-    public function roundDecimalValue($number, bool $round = false): float
42
-    {
43
-        $precision = $round ? $this->locale_precision : $this->decimal_precision;
44
-        return round(
45
-            $this->filterDecimalValue($number),
46
-            $precision ?? $this->decimal_precision,
47
-            PHP_ROUND_HALF_UP
48
-        );
49
-    }
50
-
51
-
52
-    /**
53
-     * Removes all characters except digits, +- and .
54
-     *
55
-     * @param float|int|string $number
56
-     * @return float
57
-     */
58
-    public function filterDecimalValue($number): float
59
-    {
60
-        return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
61
-    }
9
+	/**
10
+	 * number of decimal places to round numbers to when performing calculations
11
+	 *
12
+	 * @var integer
13
+	 */
14
+	protected $decimal_precision = 6;
15
+
16
+	/**
17
+	 * number of decimal places to round numbers to for display
18
+	 *
19
+	 * @var integer
20
+	 */
21
+	protected $locale_precision = 6;
22
+
23
+
24
+	/**
25
+	 * @param EE_Currency_Config $currency_config
26
+	 */
27
+	public function __construct(EE_Currency_Config $currency_config)
28
+	{
29
+		$this->locale_precision = $currency_config->dec_plc;
30
+	}
31
+
32
+	/**
33
+	 * strips formatting, rounds the provided number, and returns a float
34
+	 * if $round is set to true, then the decimal precision for the site locale will be used,
35
+	 * otherwise the default decimal precision of 6 will be used
36
+	 *
37
+	 * @param float|int|string $number unformatted number value, ex: 1234.5678956789
38
+	 * @param bool             $round  whether to round the price off according to the locale settings
39
+	 * @return float                      rounded value, ex: 1,234.567896
40
+	 */
41
+	public function roundDecimalValue($number, bool $round = false): float
42
+	{
43
+		$precision = $round ? $this->locale_precision : $this->decimal_precision;
44
+		return round(
45
+			$this->filterDecimalValue($number),
46
+			$precision ?? $this->decimal_precision,
47
+			PHP_ROUND_HALF_UP
48
+		);
49
+	}
50
+
51
+
52
+	/**
53
+	 * Removes all characters except digits, +- and .
54
+	 *
55
+	 * @param float|int|string $number
56
+	 * @return float
57
+	 */
58
+	public function filterDecimalValue($number): float
59
+	{
60
+		return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
61
+	}
62 62
 }
Please login to merge, or discard this patch.
core/services/calculators/LineItemCalculator.php 2 patches
Indentation   +710 added lines, -710 removed lines patch added patch discarded remove patch
@@ -21,714 +21,714 @@
 block discarded – undo
21 21
 class LineItemCalculator
22 22
 {
23 23
 
24
-    /**
25
-     * @var DecimalValues
26
-     */
27
-    protected $decimal_values;
28
-
29
-    /**
30
-     * @var array
31
-     */
32
-    protected $default_query_params = [
33
-        ['LIN_type' => ['!=', EEM_Line_Item::type_cancellation]]
34
-    ];
35
-
36
-
37
-    /**
38
-     * @param DecimalValues $decimal_values
39
-     */
40
-    public function __construct(DecimalValues $decimal_values)
41
-    {
42
-        $this->decimal_values = $decimal_values;
43
-    }
44
-
45
-
46
-    /**
47
-     * Gets the final total on this item, taking taxes into account.
48
-     * Has the side-effect of setting the sub-total as it was just calculated.
49
-     * If this is used on a grand-total line item, also updates the transaction's
50
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
51
-     * want to change a persistable transaction with info from a non-persistent line item)
52
-     *
53
-     * @param EE_Line_Item $line_item
54
-     * @param bool         $update_txn_status
55
-     * @return float
56
-     * @throws EE_Error
57
-     * @throws ReflectionException
58
-     */
59
-    public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
60
-    {
61
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
62
-        $ticket_line_items = EEH_Line_Item::get_ticket_line_items($line_item);
63
-        if (empty($ticket_line_items)) {
64
-            return 0;
65
-        }
66
-        [, $pretax] = $this->recalculateLineItemTotals($line_item);
67
-        $total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
68
-        // no negative totals plz
69
-        $grand_total  = max($pretax + $total_tax, 0);
70
-        $this->updatePreTaxTotal($line_item, $pretax, true);
71
-        $grand_total  = $this->updateTotal($line_item, $grand_total, true);
72
-        $this->updateTransaction($line_item, $grand_total, $update_txn_status);
73
-        return $grand_total;
74
-    }
75
-
76
-
77
-    /**
78
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
79
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
80
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
81
-     * when this is called on the grand total
82
-     *
83
-     * @param EE_Line_Item $line_item
84
-     * @param float        $total
85
-     * @param float        $pretax
86
-     * @return array
87
-     * @throws EE_Error
88
-     * @throws ReflectionException
89
-     */
90
-    public function recalculateLineItemTotals(
91
-        EE_Line_Item $line_item,
92
-        float $total = 0,
93
-        float $pretax = 0
94
-    ): array {
95
-        $new_total = $new_pretax = 0;
96
-        switch ($line_item->type()) {
97
-            case EEM_Line_Item::type_total:
98
-            case EEM_Line_Item::type_sub_total:
99
-                [$new_total, $new_pretax] = $this->recalculateSubTotal($line_item, $total, $pretax);
100
-                break;
101
-
102
-            case EEM_Line_Item::type_line_item:
103
-                [$new_total, $new_pretax] = $this->recalculateLineItem($line_item, $total, $pretax);
104
-                break;
105
-
106
-            case EEM_Line_Item::type_sub_line_item:
107
-                // sub line items operate on the total and update both the total AND the pre-tax total
108
-                $new_total = $new_pretax = $this->recalculateSubLineItem($line_item, $total);
109
-                break;
110
-
111
-            case EEM_Line_Item::type_sub_tax:
112
-                // sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
113
-                $new_total = $this->recalculateSubTax($line_item, $pretax);
114
-                break;
115
-
116
-            case EEM_Line_Item::type_tax_sub_total:
117
-            case EEM_Line_Item::type_tax:
118
-            case EEM_Line_Item::type_cancellation:
119
-                // completely ignore tax totals, tax sub-totals, and cancelled line items
120
-                // when calculating the pre-tax-total
121
-                break;
122
-        }
123
-        return [$new_total, $new_pretax];
124
-    }
125
-
126
-
127
-    /**
128
-     * @param EE_Line_Item $line_item
129
-     * @param float        $total
130
-     * @param float        $pretax
131
-     * @return array
132
-     * @throws EE_Error
133
-     * @throws ReflectionException
134
-     */
135
-    private function recalculateSubTotal(
136
-        EE_Line_Item $line_item,
137
-        float $total = 0,
138
-        float $pretax = 0
139
-    ): array {
140
-        if ($line_item->is_total()) {
141
-            // if this is the grand total line item
142
-            // then first update ALL of the line item quantities (if need be)
143
-            $this->updateLineItemQuantities($line_item);
144
-        }
145
-        // recursively loop through children and recalculate their totals
146
-        $children = $line_item->children($this->default_query_params);
147
-        if (empty($children)) {
148
-            return [$total, $pretax];
149
-        }
150
-        // reset the total and pretax total to zero since we are recalculating them
151
-        $pretax = $total = 0;
152
-        foreach ($children as $child_line_item) {
153
-            [$child_total, $child_pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
154
-            $total  += $child_total;
155
-            $pretax += $child_pretax;
156
-        }
157
-        // for the actual pre-tax sub total line item, we want to save the pretax value for everything
158
-        if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
159
-            $this->updateUnitPrice($line_item, $pretax);
160
-            $this->updateTotal($line_item, $pretax, true);
161
-        } elseif ($line_item->is_total()) {
162
-            // only update the unit price for the total line item, because that will need to include taxes
163
-            $this->updateUnitPrice($line_item, $total);
164
-        } else {
165
-            $this->updateUnitPrice($line_item, $total);
166
-            $total = $this->updateTotal($line_item, $total, true);
167
-        }
168
-        $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
169
-        return [$total, $pretax];
170
-    }
171
-
172
-
173
-    /**
174
-     * @param EE_Line_Item $line_item
175
-     * @param float        $total
176
-     * @param float        $pretax
177
-     * @return array
178
-     * @throws EE_Error
179
-     * @throws ReflectionException
180
-     */
181
-    private function recalculateLineItem(
182
-        EE_Line_Item $line_item,
183
-        float $total = 0,
184
-        float $pretax = 0
185
-    ): array {
186
-        if ($line_item->is_percent()) {
187
-            $total = $this->calculatePercentage($total, $line_item->percent());
188
-            $pretax = $this->calculatePercentage($pretax, $line_item->percent());
189
-        } else {
190
-            // recursively loop through children and recalculate their totals
191
-            $children = $line_item->children($this->default_query_params);
192
-            if (! empty($children)) {
193
-                // reset the total and pretax total to zero since we are recalculating them
194
-                $pretax = $total = 0;
195
-                foreach ($children as $child_line_item) {
196
-                    [$child_total, $child_pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $total);
197
-                    $total  += $child_total;
198
-                    $pretax += $child_pretax;
199
-                }
200
-            } else {
201
-                // no child line items, so recalculate the total from the unit price and quantity
202
-                // and set the pretax total to match since their are obviously no sub-taxes
203
-                $pretax = $total = $this->calculateTotal($line_item);
204
-            }
205
-        }
206
-        $total  = $this->updateTotal($line_item, $total, true);
207
-        $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
208
-
209
-        if (! $line_item->is_percent()) {
210
-            // need to also adjust unit price too if the pretax total or quantity has been updated
211
-            $this->updateUnitPrice($line_item, $pretax);
212
-        }
213
-        return [$total, $pretax];
214
-    }
215
-
216
-
217
-    /**
218
-     * @param EE_Line_Item $line_item
219
-     * @param float|int    $total
220
-     * @return float
221
-     * @throws EE_Error
222
-     * @throws ReflectionException
223
-     */
224
-    private function recalculateSubLineItem(EE_Line_Item $line_item, float $total = 0): float
225
-    {
226
-        $total = $line_item->is_percent()
227
-            ? $this->calculatePercentage($total, $line_item->percent())
228
-            : $this->calculateTotal($line_item);
229
-        return $this->updateTotal($line_item, $total);
230
-    }
231
-
232
-
233
-    /**
234
-     * @param EE_Line_Item $line_item
235
-     * @param float|int    $total
236
-     * @return float
237
-     * @throws EE_Error
238
-     * @throws ReflectionException
239
-     */
240
-    private function recalculateSubTax(EE_Line_Item $line_item, float $total = 0): float
241
-    {
242
-        $total = $this->calculatePercentage($total, $line_item->percent());
243
-        return $this->updateTotal($line_item, $total);
244
-    }
245
-
246
-
247
-    /**
248
-     * recursively loops through the entire line item tree updating line item quantities accordingly.
249
-     * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
250
-     *
251
-     * @param EE_Line_Item $line_item
252
-     * @param int          $quantity
253
-     * @return int
254
-     * @throws EE_Error
255
-     * @throws ReflectionException
256
-     */
257
-    private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
258
-    {
259
-        switch ($line_item->type()) {
260
-            case EEM_Line_Item::type_total:
261
-            case EEM_Line_Item::type_sub_total:
262
-            case EEM_Line_Item::type_tax_sub_total:
263
-                // first, loop through children and set their quantities
264
-                $count = 0;
265
-                $children = $line_item->children($this->default_query_params);
266
-                foreach ($children as $child_line_item) {
267
-                    $count += $this->updateLineItemQuantities($child_line_item);
268
-                }
269
-                // totals and subtotals should have a quantity of 1
270
-                // unless their children have all been removed, in which case we can set them to 0
271
-                $quantity = $count > 0 ? 1 : 0;
272
-                $this->updateQuantity($line_item, $quantity);
273
-                return $quantity;
274
-
275
-            case EEM_Line_Item::type_line_item:
276
-                // line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
277
-                // but if this is a percentage based line item, then ensure its quantity is 1
278
-                if ($line_item->is_percent()) {
279
-                    $this->updateQuantity($line_item, 1);
280
-                }
281
-                // and we also need to loop through all of the sub items and ensure those quantities match this parent.
282
-                $children = $line_item->children($this->default_query_params);
283
-                $quantity = $line_item->quantity();
284
-                foreach ($children as $child_line_item) {
285
-                    $this->updateLineItemQuantities($child_line_item, $quantity);
286
-                }
287
-                // percentage line items should not increment their parent's count, so they return 0
288
-                return ! $line_item->is_percent() ? $quantity : 0;
289
-
290
-            case EEM_Line_Item::type_sub_line_item:
291
-                // percentage based items need their quantity set to 1,
292
-                // all others use the incoming value from the parent line item
293
-                $quantity = $line_item->is_percent() ? 1 : $quantity;
294
-                $this->updateQuantity($line_item, $quantity);
295
-                // percentage line items should not increment their parent's count, so they return 0
296
-                return ! $line_item->is_percent() ? $quantity : 0;
297
-
298
-            case EEM_Line_Item::type_tax:
299
-            case EEM_Line_Item::type_sub_tax:
300
-                // taxes should have a quantity of 1
301
-                $this->updateQuantity($line_item, 1);
302
-                return 1;
303
-
304
-            case EEM_Line_Item::type_cancellation:
305
-                // cancellations will be ignored for all calculations
306
-                // because their parent quantities should have already been adjusted when they were added
307
-                // so assume that things are already set correctly
308
-                return 0;
309
-        }
310
-        return 0;
311
-    }
312
-
313
-
314
-    /**
315
-     * @param float $total
316
-     * @param float $percent
317
-     * @param bool  $round
318
-     * @return float
319
-     */
320
-    private function calculatePercentage(float $total, float $percent, bool $round = false): float
321
-    {
322
-        $amount = $total * $percent / 100;
323
-        return $this->decimal_values->roundDecimalValue($amount, $round);
324
-    }
325
-
326
-
327
-    /**
328
-     * @param EE_Line_Item $line_item
329
-     * @return float
330
-     * @throws EE_Error
331
-     * @throws ReflectionException
332
-     */
333
-    private function calculateTotal(EE_Line_Item $line_item): float
334
-    {
335
-        $total = $line_item->unit_price() * $line_item->quantity();
336
-        return $this->decimal_values->roundDecimalValue($total);
337
-    }
338
-
339
-
340
-    /**
341
-     * @param EE_Line_Item $line_item
342
-     * @param float        $percent
343
-     * @throws EE_Error
344
-     * @throws ReflectionException
345
-     */
346
-    private function updatePercent(EE_Line_Item $line_item, float $percent)
347
-    {
348
-        // update and save new percent only if incoming value does not match existing value
349
-        if ($line_item->percent() !== $percent) {
350
-            $line_item->set_percent($percent);
351
-            $line_item->maybe_save();
352
-        }
353
-    }
354
-
355
-
356
-    /**
357
-     * @param EE_Line_Item $line_item
358
-     * @param int          $quantity
359
-     * @throws EE_Error
360
-     * @throws ReflectionException
361
-     */
362
-    private function updateQuantity(EE_Line_Item $line_item, int $quantity)
363
-    {
364
-        // update and save new quantity only if incoming value does not match existing value
365
-        if ($line_item->quantity() !== $quantity) {
366
-            $line_item->set_quantity($quantity);
367
-            $line_item->maybe_save();
368
-        }
369
-    }
370
-
371
-
372
-    /**
373
-     * @param EE_Line_Item $line_item
374
-     * @param float        $pretax_total
375
-     * @param bool         $round
376
-     * @return float
377
-     * @throws EE_Error
378
-     * @throws ReflectionException
379
-     */
380
-    private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
381
-    {
382
-        $pretax_total = $this->decimal_values->roundDecimalValue($pretax_total, $round);
383
-        // update and save new total only if incoming value does not match existing value
384
-        if ($line_item->preTaxTotal() !== $pretax_total) {
385
-            $line_item->setPreTaxTotal($pretax_total);
386
-            $line_item->maybe_save();
387
-        }
388
-        return $pretax_total;
389
-    }
390
-
391
-
392
-    /**
393
-     * @param EE_Line_Item $line_item
394
-     * @param float        $total
395
-     * @param bool         $round
396
-     * @return float
397
-     * @throws EE_Error
398
-     * @throws ReflectionException
399
-     */
400
-    private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
401
-    {
402
-        $total = $this->decimal_values->roundDecimalValue($total, $round);
403
-        // update and save new total only if incoming value does not match existing value
404
-        if ($line_item->total() !== $total) {
405
-            $line_item->set_total($total);
406
-            $line_item->maybe_save();
407
-        }
408
-        return $total;
409
-    }
410
-
411
-
412
-    /**
413
-     * @param EE_Line_Item $line_item
414
-     * @param float        $total
415
-     * @param bool         $update_status
416
-     * @return void
417
-     * @throws EE_Error
418
-     * @throws ReflectionException
419
-     */
420
-    private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
421
-    {
422
-        // only update the related transaction's total
423
-        // if we intend to save this line item and its a grand total
424
-        if ($line_item->allow_persist()) {
425
-            $transaction = $line_item->transaction();
426
-            if ($transaction instanceof EE_Transaction) {
427
-                $transaction->set_total($total);
428
-                if ($update_status) {
429
-                    // don't save the TXN because that will be done below
430
-                    // and the following method only saves if the status changes
431
-                    $transaction->update_status_based_on_total_paid(false);
432
-                }
433
-                if ($transaction->ID()) {
434
-                    $transaction->save();
435
-                }
436
-            }
437
-        }
438
-    }
439
-
440
-
441
-    /**
442
-     * @param EE_Line_Item $line_item
443
-     * @param float        $total
444
-     * @return void
445
-     * @throws EE_Error
446
-     * @throws ReflectionException
447
-     */
448
-    private function updateUnitPrice(EE_Line_Item $line_item, float $total)
449
-    {
450
-        $quantity = $line_item->quantity();
451
-        // don't divide by zero else you'll create a singularity and implode the interweb
452
-        if ($quantity === 0) {
453
-            return;
454
-        }
455
-        // $new_unit_price = $quantity !== 0 ? $total / $quantity : 0;
456
-        $new_unit_price = $total / $quantity;
457
-        $new_unit_price = $this->decimal_values->roundDecimalValue($new_unit_price);
458
-        // update and save new total only if incoming value does not match existing value
459
-        if ($line_item->unit_price() !== $new_unit_price) {
460
-            $line_item->set_unit_price($new_unit_price);
461
-            $line_item->maybe_save();
462
-        }
463
-    }
464
-
465
-
466
-    /**
467
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
468
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
469
-     * and tax sub-total if already in the DB
470
-     *
471
-     * @param EE_Line_Item $total_line_item
472
-     * @return float
473
-     * @throws EE_Error
474
-     * @throws ReflectionException
475
-     */
476
-    public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
477
-    {
478
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
479
-        // calculate the total taxable amount for globally applied taxes
480
-        $taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
481
-        $global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
482
-        $non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
483
-        $all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
484
-        $this->recalculateTaxSubTotal($total_line_item);
485
-        return $all_tax_total;
486
-    }
487
-
488
-
489
-    /**
490
-     * @param EE_Line_Item $total_line_item
491
-     * @param float        $taxable_total
492
-     * @return float
493
-     * @throws EE_Error
494
-     * @throws ReflectionException
495
-     */
496
-    private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
497
-    {
498
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
499
-        $total_tax = 0;
500
-        // loop through all global taxes all taxes
501
-        $global_taxes = $total_line_item->tax_descendants();
502
-        foreach ($global_taxes as $tax) {
503
-            $tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
504
-            $tax_total = $this->updateTotal($tax, $tax_total, true);
505
-            $total_tax += $tax_total;
506
-        }
507
-        return $this->decimal_values->roundDecimalValue($total_tax, true);
508
-    }
509
-
510
-
511
-    /**
512
-     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
513
-     *
514
-     * @param EE_Line_Item $line_item
515
-     * @return void
516
-     * @throws EE_Error
517
-     * @throws ReflectionException
518
-     */
519
-    private function recalculateTaxSubTotal(EE_Line_Item $line_item)
520
-    {
521
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
522
-        foreach ($line_item->children() as $maybe_tax_subtotal) {
523
-            if (
524
-                $this->validateLineItemAndType($maybe_tax_subtotal)
525
-                && $maybe_tax_subtotal->is_tax_sub_total()
526
-            ) {
527
-                $total         = 0;
528
-                $total_percent = 0;
529
-                // simply loop through all its children (which should be taxes) and sum their total
530
-                foreach ($maybe_tax_subtotal->children() as $child_tax) {
531
-                    if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
532
-                        $total         += $child_tax->total();
533
-                        $total_percent += $child_tax->percent();
534
-                    }
535
-                }
536
-                $this->updateTotal($maybe_tax_subtotal, $total, true);
537
-                $this->updatePercent($maybe_tax_subtotal, $total_percent);
538
-            }
539
-        }
540
-    }
541
-
542
-
543
-    /**
544
-     * returns an array of tax details like:
545
-     *  [
546
-     *      'GST_7' => [
547
-     *          'name'  => 'GST',
548
-     *          'rate'  => float(7),
549
-     *          'total' => float(4.9),
550
-     *      ]
551
-     *  ]
552
-     *
553
-     * @param EE_Line_Item $total_line_item
554
-     * @param array        $non_global_taxes
555
-     * @param float        $line_item_total
556
-     * @return array
557
-     * @throws EE_Error
558
-     * @throws ReflectionException
559
-     */
560
-    private function calculateNonGlobalTaxes(
561
-        EE_Line_Item $total_line_item,
562
-        array $non_global_taxes = [],
563
-        float $line_item_total = 0
564
-    ): array {
565
-        foreach ($total_line_item->children() as $line_item) {
566
-            if ($this->validateLineItemAndType($line_item)) {
567
-                if ($line_item->is_sub_total()) {
568
-                    $non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
569
-                } elseif ($line_item->is_line_item()) {
570
-                    $non_global_taxes = $this->calculateNonGlobalTaxes(
571
-                        $line_item,
572
-                        $non_global_taxes,
573
-                        $line_item->pretaxTotal()
574
-                    );
575
-                } elseif ($line_item->isSubTax()) {
576
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
577
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
578
-                        $non_global_taxes[ $tax_ID ] = [
579
-                            'name'  => $line_item->name(),
580
-                            'rate'  => $line_item->percent(),
581
-                            'total' => 0,
582
-                            'obj'   => $line_item->OBJ_type(),
583
-                            'objID' => $line_item->OBJ_ID(),
584
-                        ];
585
-                    }
586
-                    $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
587
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
588
-                }
589
-            }
590
-        }
591
-        return $non_global_taxes;
592
-    }
593
-
594
-
595
-    /**
596
-     * @param EE_Line_Item $total_line_item
597
-     * @param float        $tax_total
598
-     * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
599
-     * @return float
600
-     * @throws EE_Error
601
-     * @throws ReflectionException
602
-     */
603
-    private function applyNonGlobalTaxes(
604
-        EE_Line_Item $total_line_item,
605
-        float $tax_total,
606
-        array $non_global_taxes
607
-    ): float {
608
-        $global_taxes   = $total_line_item->tax_descendants();
609
-        $taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
610
-        foreach ($non_global_taxes as $non_global_tax) {
611
-            $found = false;
612
-            foreach ($global_taxes as $global_tax) {
613
-                if (
614
-                    $this->validateLineItemAndType($global_tax)
615
-                    && $non_global_tax['obj'] === $global_tax->OBJ_type()
616
-                    && $non_global_tax['objID'] === $global_tax->OBJ_ID()
617
-                ) {
618
-                    $found = true;
619
-                    $new_total = $global_tax->total() + $non_global_tax['total'];
620
-                    // add non global tax to matching global tax AND the tax total
621
-                    $global_tax->set_total($new_total);
622
-                    $global_tax->maybe_save();
623
-                    $tax_total += $non_global_tax['total'];
624
-                }
625
-            }
626
-            if (! $found) {
627
-                // add a new line item for this non global tax
628
-                $taxes_subtotal->add_child_line_item(
629
-                    EE_Line_Item::new_instance(
630
-                        [
631
-                            'LIN_name'       => $non_global_tax['name'],
632
-                            'LIN_percent'    => $non_global_tax['rate'],
633
-                            'LIN_is_taxable' => false,
634
-                            'LIN_total'      => $non_global_tax['total'],
635
-                            'LIN_type'       => EEM_Line_Item::type_tax,
636
-                            'OBJ_type'       => $non_global_tax['obj'],
637
-                            'OBJ_ID'         => $non_global_tax['objID'],
638
-                        ]
639
-                    )
640
-                );
641
-                $tax_total += $non_global_tax['total'];
642
-            }
643
-        }
644
-        return $this->decimal_values->roundDecimalValue($tax_total, true);
645
-    }
646
-
647
-
648
-    /**
649
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
650
-     * recalculate_taxes_and_total
651
-     *
652
-     * @param EE_Line_Item $line_item
653
-     * @return float
654
-     * @throws EE_Error
655
-     * @throws ReflectionException
656
-     */
657
-    public function getTotalTax(EE_Line_Item $line_item): float
658
-    {
659
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
660
-        $this->recalculateTaxSubTotal($line_item);
661
-        $total = 0;
662
-        foreach ($line_item->tax_descendants() as $tax_line_item) {
663
-            if ($this->validateLineItemAndType($tax_line_item)) {
664
-                $total += $tax_line_item->total();
665
-            }
666
-        }
667
-        return $this->decimal_values->roundDecimalValue($total, true);
668
-    }
669
-
670
-
671
-    /**
672
-     * Returns the amount taxable among this line item's children (or if it has no children,
673
-     * how much of it is taxable). Does not recalculate totals or subtotals.
674
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
675
-     * but there is a "Taxable" discount), returns 0.
676
-     *
677
-     * @param EE_Line_Item|null $line_item
678
-     * @return float
679
-     * @throws EE_Error
680
-     * @throws ReflectionException
681
-     */
682
-    public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
683
-    {
684
-        $total      = 0;
685
-        $child_line_items = $line_item->children($this->default_query_params);
686
-        foreach ($child_line_items as $child_line_item) {
687
-            $this->validateLineItemAndType($child_line_item);
688
-            if ($child_line_item->is_sub_total()) {
689
-                $total += $this->taxableAmountForGlobalTaxes($child_line_item);
690
-            } elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
691
-                // if it's a percent item, only take into account
692
-                // the percentage that's taxable (the taxable total so far)
693
-                if ($child_line_item->is_percent()) {
694
-                    $total += $this->calculatePercentage($total, $child_line_item->percent(), true);
695
-                } else {
696
-                    // pretax total will be equal to the total for line items with globally applied taxes
697
-                    $pretax_total = $this->calculateTotal($child_line_item);
698
-                    $total += $this->updatePreTaxTotal($child_line_item, $pretax_total);
699
-                }
700
-            }
701
-        }
702
-        return max($total, 0);
703
-    }
704
-
705
-
706
-    /**
707
-     * @param EE_Line_Item|null $line_item
708
-     * @param string|null       $type
709
-     * @return bool
710
-     * @throws EE_Error
711
-     * @throws ReflectionException
712
-     */
713
-    private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
714
-    {
715
-        if (! $line_item instanceof EE_Line_Item) {
716
-            throw new DomainException(
717
-                esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
718
-            );
719
-        }
720
-        if ($type && $line_item->type() !== $type) {
721
-            throw new DomainException(
722
-                sprintf(
723
-                    esc_html__(
724
-                        'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
725
-                        'event_espresso'
726
-                    ),
727
-                    $line_item->type(),
728
-                    $type
729
-                )
730
-            );
731
-        }
732
-        return true;
733
-    }
24
+	/**
25
+	 * @var DecimalValues
26
+	 */
27
+	protected $decimal_values;
28
+
29
+	/**
30
+	 * @var array
31
+	 */
32
+	protected $default_query_params = [
33
+		['LIN_type' => ['!=', EEM_Line_Item::type_cancellation]]
34
+	];
35
+
36
+
37
+	/**
38
+	 * @param DecimalValues $decimal_values
39
+	 */
40
+	public function __construct(DecimalValues $decimal_values)
41
+	{
42
+		$this->decimal_values = $decimal_values;
43
+	}
44
+
45
+
46
+	/**
47
+	 * Gets the final total on this item, taking taxes into account.
48
+	 * Has the side-effect of setting the sub-total as it was just calculated.
49
+	 * If this is used on a grand-total line item, also updates the transaction's
50
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
51
+	 * want to change a persistable transaction with info from a non-persistent line item)
52
+	 *
53
+	 * @param EE_Line_Item $line_item
54
+	 * @param bool         $update_txn_status
55
+	 * @return float
56
+	 * @throws EE_Error
57
+	 * @throws ReflectionException
58
+	 */
59
+	public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
60
+	{
61
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
62
+		$ticket_line_items = EEH_Line_Item::get_ticket_line_items($line_item);
63
+		if (empty($ticket_line_items)) {
64
+			return 0;
65
+		}
66
+		[, $pretax] = $this->recalculateLineItemTotals($line_item);
67
+		$total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
68
+		// no negative totals plz
69
+		$grand_total  = max($pretax + $total_tax, 0);
70
+		$this->updatePreTaxTotal($line_item, $pretax, true);
71
+		$grand_total  = $this->updateTotal($line_item, $grand_total, true);
72
+		$this->updateTransaction($line_item, $grand_total, $update_txn_status);
73
+		return $grand_total;
74
+	}
75
+
76
+
77
+	/**
78
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
79
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
80
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
81
+	 * when this is called on the grand total
82
+	 *
83
+	 * @param EE_Line_Item $line_item
84
+	 * @param float        $total
85
+	 * @param float        $pretax
86
+	 * @return array
87
+	 * @throws EE_Error
88
+	 * @throws ReflectionException
89
+	 */
90
+	public function recalculateLineItemTotals(
91
+		EE_Line_Item $line_item,
92
+		float $total = 0,
93
+		float $pretax = 0
94
+	): array {
95
+		$new_total = $new_pretax = 0;
96
+		switch ($line_item->type()) {
97
+			case EEM_Line_Item::type_total:
98
+			case EEM_Line_Item::type_sub_total:
99
+				[$new_total, $new_pretax] = $this->recalculateSubTotal($line_item, $total, $pretax);
100
+				break;
101
+
102
+			case EEM_Line_Item::type_line_item:
103
+				[$new_total, $new_pretax] = $this->recalculateLineItem($line_item, $total, $pretax);
104
+				break;
105
+
106
+			case EEM_Line_Item::type_sub_line_item:
107
+				// sub line items operate on the total and update both the total AND the pre-tax total
108
+				$new_total = $new_pretax = $this->recalculateSubLineItem($line_item, $total);
109
+				break;
110
+
111
+			case EEM_Line_Item::type_sub_tax:
112
+				// sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
113
+				$new_total = $this->recalculateSubTax($line_item, $pretax);
114
+				break;
115
+
116
+			case EEM_Line_Item::type_tax_sub_total:
117
+			case EEM_Line_Item::type_tax:
118
+			case EEM_Line_Item::type_cancellation:
119
+				// completely ignore tax totals, tax sub-totals, and cancelled line items
120
+				// when calculating the pre-tax-total
121
+				break;
122
+		}
123
+		return [$new_total, $new_pretax];
124
+	}
125
+
126
+
127
+	/**
128
+	 * @param EE_Line_Item $line_item
129
+	 * @param float        $total
130
+	 * @param float        $pretax
131
+	 * @return array
132
+	 * @throws EE_Error
133
+	 * @throws ReflectionException
134
+	 */
135
+	private function recalculateSubTotal(
136
+		EE_Line_Item $line_item,
137
+		float $total = 0,
138
+		float $pretax = 0
139
+	): array {
140
+		if ($line_item->is_total()) {
141
+			// if this is the grand total line item
142
+			// then first update ALL of the line item quantities (if need be)
143
+			$this->updateLineItemQuantities($line_item);
144
+		}
145
+		// recursively loop through children and recalculate their totals
146
+		$children = $line_item->children($this->default_query_params);
147
+		if (empty($children)) {
148
+			return [$total, $pretax];
149
+		}
150
+		// reset the total and pretax total to zero since we are recalculating them
151
+		$pretax = $total = 0;
152
+		foreach ($children as $child_line_item) {
153
+			[$child_total, $child_pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $pretax);
154
+			$total  += $child_total;
155
+			$pretax += $child_pretax;
156
+		}
157
+		// for the actual pre-tax sub total line item, we want to save the pretax value for everything
158
+		if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
159
+			$this->updateUnitPrice($line_item, $pretax);
160
+			$this->updateTotal($line_item, $pretax, true);
161
+		} elseif ($line_item->is_total()) {
162
+			// only update the unit price for the total line item, because that will need to include taxes
163
+			$this->updateUnitPrice($line_item, $total);
164
+		} else {
165
+			$this->updateUnitPrice($line_item, $total);
166
+			$total = $this->updateTotal($line_item, $total, true);
167
+		}
168
+		$pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
169
+		return [$total, $pretax];
170
+	}
171
+
172
+
173
+	/**
174
+	 * @param EE_Line_Item $line_item
175
+	 * @param float        $total
176
+	 * @param float        $pretax
177
+	 * @return array
178
+	 * @throws EE_Error
179
+	 * @throws ReflectionException
180
+	 */
181
+	private function recalculateLineItem(
182
+		EE_Line_Item $line_item,
183
+		float $total = 0,
184
+		float $pretax = 0
185
+	): array {
186
+		if ($line_item->is_percent()) {
187
+			$total = $this->calculatePercentage($total, $line_item->percent());
188
+			$pretax = $this->calculatePercentage($pretax, $line_item->percent());
189
+		} else {
190
+			// recursively loop through children and recalculate their totals
191
+			$children = $line_item->children($this->default_query_params);
192
+			if (! empty($children)) {
193
+				// reset the total and pretax total to zero since we are recalculating them
194
+				$pretax = $total = 0;
195
+				foreach ($children as $child_line_item) {
196
+					[$child_total, $child_pretax] = $this->recalculateLineItemTotals($child_line_item, $total, $total);
197
+					$total  += $child_total;
198
+					$pretax += $child_pretax;
199
+				}
200
+			} else {
201
+				// no child line items, so recalculate the total from the unit price and quantity
202
+				// and set the pretax total to match since their are obviously no sub-taxes
203
+				$pretax = $total = $this->calculateTotal($line_item);
204
+			}
205
+		}
206
+		$total  = $this->updateTotal($line_item, $total, true);
207
+		$pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
208
+
209
+		if (! $line_item->is_percent()) {
210
+			// need to also adjust unit price too if the pretax total or quantity has been updated
211
+			$this->updateUnitPrice($line_item, $pretax);
212
+		}
213
+		return [$total, $pretax];
214
+	}
215
+
216
+
217
+	/**
218
+	 * @param EE_Line_Item $line_item
219
+	 * @param float|int    $total
220
+	 * @return float
221
+	 * @throws EE_Error
222
+	 * @throws ReflectionException
223
+	 */
224
+	private function recalculateSubLineItem(EE_Line_Item $line_item, float $total = 0): float
225
+	{
226
+		$total = $line_item->is_percent()
227
+			? $this->calculatePercentage($total, $line_item->percent())
228
+			: $this->calculateTotal($line_item);
229
+		return $this->updateTotal($line_item, $total);
230
+	}
231
+
232
+
233
+	/**
234
+	 * @param EE_Line_Item $line_item
235
+	 * @param float|int    $total
236
+	 * @return float
237
+	 * @throws EE_Error
238
+	 * @throws ReflectionException
239
+	 */
240
+	private function recalculateSubTax(EE_Line_Item $line_item, float $total = 0): float
241
+	{
242
+		$total = $this->calculatePercentage($total, $line_item->percent());
243
+		return $this->updateTotal($line_item, $total);
244
+	}
245
+
246
+
247
+	/**
248
+	 * recursively loops through the entire line item tree updating line item quantities accordingly.
249
+	 * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
250
+	 *
251
+	 * @param EE_Line_Item $line_item
252
+	 * @param int          $quantity
253
+	 * @return int
254
+	 * @throws EE_Error
255
+	 * @throws ReflectionException
256
+	 */
257
+	private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
258
+	{
259
+		switch ($line_item->type()) {
260
+			case EEM_Line_Item::type_total:
261
+			case EEM_Line_Item::type_sub_total:
262
+			case EEM_Line_Item::type_tax_sub_total:
263
+				// first, loop through children and set their quantities
264
+				$count = 0;
265
+				$children = $line_item->children($this->default_query_params);
266
+				foreach ($children as $child_line_item) {
267
+					$count += $this->updateLineItemQuantities($child_line_item);
268
+				}
269
+				// totals and subtotals should have a quantity of 1
270
+				// unless their children have all been removed, in which case we can set them to 0
271
+				$quantity = $count > 0 ? 1 : 0;
272
+				$this->updateQuantity($line_item, $quantity);
273
+				return $quantity;
274
+
275
+			case EEM_Line_Item::type_line_item:
276
+				// line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
277
+				// but if this is a percentage based line item, then ensure its quantity is 1
278
+				if ($line_item->is_percent()) {
279
+					$this->updateQuantity($line_item, 1);
280
+				}
281
+				// and we also need to loop through all of the sub items and ensure those quantities match this parent.
282
+				$children = $line_item->children($this->default_query_params);
283
+				$quantity = $line_item->quantity();
284
+				foreach ($children as $child_line_item) {
285
+					$this->updateLineItemQuantities($child_line_item, $quantity);
286
+				}
287
+				// percentage line items should not increment their parent's count, so they return 0
288
+				return ! $line_item->is_percent() ? $quantity : 0;
289
+
290
+			case EEM_Line_Item::type_sub_line_item:
291
+				// percentage based items need their quantity set to 1,
292
+				// all others use the incoming value from the parent line item
293
+				$quantity = $line_item->is_percent() ? 1 : $quantity;
294
+				$this->updateQuantity($line_item, $quantity);
295
+				// percentage line items should not increment their parent's count, so they return 0
296
+				return ! $line_item->is_percent() ? $quantity : 0;
297
+
298
+			case EEM_Line_Item::type_tax:
299
+			case EEM_Line_Item::type_sub_tax:
300
+				// taxes should have a quantity of 1
301
+				$this->updateQuantity($line_item, 1);
302
+				return 1;
303
+
304
+			case EEM_Line_Item::type_cancellation:
305
+				// cancellations will be ignored for all calculations
306
+				// because their parent quantities should have already been adjusted when they were added
307
+				// so assume that things are already set correctly
308
+				return 0;
309
+		}
310
+		return 0;
311
+	}
312
+
313
+
314
+	/**
315
+	 * @param float $total
316
+	 * @param float $percent
317
+	 * @param bool  $round
318
+	 * @return float
319
+	 */
320
+	private function calculatePercentage(float $total, float $percent, bool $round = false): float
321
+	{
322
+		$amount = $total * $percent / 100;
323
+		return $this->decimal_values->roundDecimalValue($amount, $round);
324
+	}
325
+
326
+
327
+	/**
328
+	 * @param EE_Line_Item $line_item
329
+	 * @return float
330
+	 * @throws EE_Error
331
+	 * @throws ReflectionException
332
+	 */
333
+	private function calculateTotal(EE_Line_Item $line_item): float
334
+	{
335
+		$total = $line_item->unit_price() * $line_item->quantity();
336
+		return $this->decimal_values->roundDecimalValue($total);
337
+	}
338
+
339
+
340
+	/**
341
+	 * @param EE_Line_Item $line_item
342
+	 * @param float        $percent
343
+	 * @throws EE_Error
344
+	 * @throws ReflectionException
345
+	 */
346
+	private function updatePercent(EE_Line_Item $line_item, float $percent)
347
+	{
348
+		// update and save new percent only if incoming value does not match existing value
349
+		if ($line_item->percent() !== $percent) {
350
+			$line_item->set_percent($percent);
351
+			$line_item->maybe_save();
352
+		}
353
+	}
354
+
355
+
356
+	/**
357
+	 * @param EE_Line_Item $line_item
358
+	 * @param int          $quantity
359
+	 * @throws EE_Error
360
+	 * @throws ReflectionException
361
+	 */
362
+	private function updateQuantity(EE_Line_Item $line_item, int $quantity)
363
+	{
364
+		// update and save new quantity only if incoming value does not match existing value
365
+		if ($line_item->quantity() !== $quantity) {
366
+			$line_item->set_quantity($quantity);
367
+			$line_item->maybe_save();
368
+		}
369
+	}
370
+
371
+
372
+	/**
373
+	 * @param EE_Line_Item $line_item
374
+	 * @param float        $pretax_total
375
+	 * @param bool         $round
376
+	 * @return float
377
+	 * @throws EE_Error
378
+	 * @throws ReflectionException
379
+	 */
380
+	private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
381
+	{
382
+		$pretax_total = $this->decimal_values->roundDecimalValue($pretax_total, $round);
383
+		// update and save new total only if incoming value does not match existing value
384
+		if ($line_item->preTaxTotal() !== $pretax_total) {
385
+			$line_item->setPreTaxTotal($pretax_total);
386
+			$line_item->maybe_save();
387
+		}
388
+		return $pretax_total;
389
+	}
390
+
391
+
392
+	/**
393
+	 * @param EE_Line_Item $line_item
394
+	 * @param float        $total
395
+	 * @param bool         $round
396
+	 * @return float
397
+	 * @throws EE_Error
398
+	 * @throws ReflectionException
399
+	 */
400
+	private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
401
+	{
402
+		$total = $this->decimal_values->roundDecimalValue($total, $round);
403
+		// update and save new total only if incoming value does not match existing value
404
+		if ($line_item->total() !== $total) {
405
+			$line_item->set_total($total);
406
+			$line_item->maybe_save();
407
+		}
408
+		return $total;
409
+	}
410
+
411
+
412
+	/**
413
+	 * @param EE_Line_Item $line_item
414
+	 * @param float        $total
415
+	 * @param bool         $update_status
416
+	 * @return void
417
+	 * @throws EE_Error
418
+	 * @throws ReflectionException
419
+	 */
420
+	private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
421
+	{
422
+		// only update the related transaction's total
423
+		// if we intend to save this line item and its a grand total
424
+		if ($line_item->allow_persist()) {
425
+			$transaction = $line_item->transaction();
426
+			if ($transaction instanceof EE_Transaction) {
427
+				$transaction->set_total($total);
428
+				if ($update_status) {
429
+					// don't save the TXN because that will be done below
430
+					// and the following method only saves if the status changes
431
+					$transaction->update_status_based_on_total_paid(false);
432
+				}
433
+				if ($transaction->ID()) {
434
+					$transaction->save();
435
+				}
436
+			}
437
+		}
438
+	}
439
+
440
+
441
+	/**
442
+	 * @param EE_Line_Item $line_item
443
+	 * @param float        $total
444
+	 * @return void
445
+	 * @throws EE_Error
446
+	 * @throws ReflectionException
447
+	 */
448
+	private function updateUnitPrice(EE_Line_Item $line_item, float $total)
449
+	{
450
+		$quantity = $line_item->quantity();
451
+		// don't divide by zero else you'll create a singularity and implode the interweb
452
+		if ($quantity === 0) {
453
+			return;
454
+		}
455
+		// $new_unit_price = $quantity !== 0 ? $total / $quantity : 0;
456
+		$new_unit_price = $total / $quantity;
457
+		$new_unit_price = $this->decimal_values->roundDecimalValue($new_unit_price);
458
+		// update and save new total only if incoming value does not match existing value
459
+		if ($line_item->unit_price() !== $new_unit_price) {
460
+			$line_item->set_unit_price($new_unit_price);
461
+			$line_item->maybe_save();
462
+		}
463
+	}
464
+
465
+
466
+	/**
467
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
468
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
469
+	 * and tax sub-total if already in the DB
470
+	 *
471
+	 * @param EE_Line_Item $total_line_item
472
+	 * @return float
473
+	 * @throws EE_Error
474
+	 * @throws ReflectionException
475
+	 */
476
+	public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
477
+	{
478
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
479
+		// calculate the total taxable amount for globally applied taxes
480
+		$taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
481
+		$global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
482
+		$non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
483
+		$all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
484
+		$this->recalculateTaxSubTotal($total_line_item);
485
+		return $all_tax_total;
486
+	}
487
+
488
+
489
+	/**
490
+	 * @param EE_Line_Item $total_line_item
491
+	 * @param float        $taxable_total
492
+	 * @return float
493
+	 * @throws EE_Error
494
+	 * @throws ReflectionException
495
+	 */
496
+	private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
497
+	{
498
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
499
+		$total_tax = 0;
500
+		// loop through all global taxes all taxes
501
+		$global_taxes = $total_line_item->tax_descendants();
502
+		foreach ($global_taxes as $tax) {
503
+			$tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
504
+			$tax_total = $this->updateTotal($tax, $tax_total, true);
505
+			$total_tax += $tax_total;
506
+		}
507
+		return $this->decimal_values->roundDecimalValue($total_tax, true);
508
+	}
509
+
510
+
511
+	/**
512
+	 * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
513
+	 *
514
+	 * @param EE_Line_Item $line_item
515
+	 * @return void
516
+	 * @throws EE_Error
517
+	 * @throws ReflectionException
518
+	 */
519
+	private function recalculateTaxSubTotal(EE_Line_Item $line_item)
520
+	{
521
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
522
+		foreach ($line_item->children() as $maybe_tax_subtotal) {
523
+			if (
524
+				$this->validateLineItemAndType($maybe_tax_subtotal)
525
+				&& $maybe_tax_subtotal->is_tax_sub_total()
526
+			) {
527
+				$total         = 0;
528
+				$total_percent = 0;
529
+				// simply loop through all its children (which should be taxes) and sum their total
530
+				foreach ($maybe_tax_subtotal->children() as $child_tax) {
531
+					if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
532
+						$total         += $child_tax->total();
533
+						$total_percent += $child_tax->percent();
534
+					}
535
+				}
536
+				$this->updateTotal($maybe_tax_subtotal, $total, true);
537
+				$this->updatePercent($maybe_tax_subtotal, $total_percent);
538
+			}
539
+		}
540
+	}
541
+
542
+
543
+	/**
544
+	 * returns an array of tax details like:
545
+	 *  [
546
+	 *      'GST_7' => [
547
+	 *          'name'  => 'GST',
548
+	 *          'rate'  => float(7),
549
+	 *          'total' => float(4.9),
550
+	 *      ]
551
+	 *  ]
552
+	 *
553
+	 * @param EE_Line_Item $total_line_item
554
+	 * @param array        $non_global_taxes
555
+	 * @param float        $line_item_total
556
+	 * @return array
557
+	 * @throws EE_Error
558
+	 * @throws ReflectionException
559
+	 */
560
+	private function calculateNonGlobalTaxes(
561
+		EE_Line_Item $total_line_item,
562
+		array $non_global_taxes = [],
563
+		float $line_item_total = 0
564
+	): array {
565
+		foreach ($total_line_item->children() as $line_item) {
566
+			if ($this->validateLineItemAndType($line_item)) {
567
+				if ($line_item->is_sub_total()) {
568
+					$non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
569
+				} elseif ($line_item->is_line_item()) {
570
+					$non_global_taxes = $this->calculateNonGlobalTaxes(
571
+						$line_item,
572
+						$non_global_taxes,
573
+						$line_item->pretaxTotal()
574
+					);
575
+				} elseif ($line_item->isSubTax()) {
576
+					$tax_ID = $line_item->name() . '_' . $line_item->percent();
577
+					if (! isset($non_global_taxes[ $tax_ID ])) {
578
+						$non_global_taxes[ $tax_ID ] = [
579
+							'name'  => $line_item->name(),
580
+							'rate'  => $line_item->percent(),
581
+							'total' => 0,
582
+							'obj'   => $line_item->OBJ_type(),
583
+							'objID' => $line_item->OBJ_ID(),
584
+						];
585
+					}
586
+					$tax = $this->calculatePercentage($line_item_total, $line_item->percent());
587
+					$non_global_taxes[ $tax_ID ]['total'] += $tax;
588
+				}
589
+			}
590
+		}
591
+		return $non_global_taxes;
592
+	}
593
+
594
+
595
+	/**
596
+	 * @param EE_Line_Item $total_line_item
597
+	 * @param float        $tax_total
598
+	 * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
599
+	 * @return float
600
+	 * @throws EE_Error
601
+	 * @throws ReflectionException
602
+	 */
603
+	private function applyNonGlobalTaxes(
604
+		EE_Line_Item $total_line_item,
605
+		float $tax_total,
606
+		array $non_global_taxes
607
+	): float {
608
+		$global_taxes   = $total_line_item->tax_descendants();
609
+		$taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
610
+		foreach ($non_global_taxes as $non_global_tax) {
611
+			$found = false;
612
+			foreach ($global_taxes as $global_tax) {
613
+				if (
614
+					$this->validateLineItemAndType($global_tax)
615
+					&& $non_global_tax['obj'] === $global_tax->OBJ_type()
616
+					&& $non_global_tax['objID'] === $global_tax->OBJ_ID()
617
+				) {
618
+					$found = true;
619
+					$new_total = $global_tax->total() + $non_global_tax['total'];
620
+					// add non global tax to matching global tax AND the tax total
621
+					$global_tax->set_total($new_total);
622
+					$global_tax->maybe_save();
623
+					$tax_total += $non_global_tax['total'];
624
+				}
625
+			}
626
+			if (! $found) {
627
+				// add a new line item for this non global tax
628
+				$taxes_subtotal->add_child_line_item(
629
+					EE_Line_Item::new_instance(
630
+						[
631
+							'LIN_name'       => $non_global_tax['name'],
632
+							'LIN_percent'    => $non_global_tax['rate'],
633
+							'LIN_is_taxable' => false,
634
+							'LIN_total'      => $non_global_tax['total'],
635
+							'LIN_type'       => EEM_Line_Item::type_tax,
636
+							'OBJ_type'       => $non_global_tax['obj'],
637
+							'OBJ_ID'         => $non_global_tax['objID'],
638
+						]
639
+					)
640
+				);
641
+				$tax_total += $non_global_tax['total'];
642
+			}
643
+		}
644
+		return $this->decimal_values->roundDecimalValue($tax_total, true);
645
+	}
646
+
647
+
648
+	/**
649
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
650
+	 * recalculate_taxes_and_total
651
+	 *
652
+	 * @param EE_Line_Item $line_item
653
+	 * @return float
654
+	 * @throws EE_Error
655
+	 * @throws ReflectionException
656
+	 */
657
+	public function getTotalTax(EE_Line_Item $line_item): float
658
+	{
659
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
660
+		$this->recalculateTaxSubTotal($line_item);
661
+		$total = 0;
662
+		foreach ($line_item->tax_descendants() as $tax_line_item) {
663
+			if ($this->validateLineItemAndType($tax_line_item)) {
664
+				$total += $tax_line_item->total();
665
+			}
666
+		}
667
+		return $this->decimal_values->roundDecimalValue($total, true);
668
+	}
669
+
670
+
671
+	/**
672
+	 * Returns the amount taxable among this line item's children (or if it has no children,
673
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
674
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
675
+	 * but there is a "Taxable" discount), returns 0.
676
+	 *
677
+	 * @param EE_Line_Item|null $line_item
678
+	 * @return float
679
+	 * @throws EE_Error
680
+	 * @throws ReflectionException
681
+	 */
682
+	public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
683
+	{
684
+		$total      = 0;
685
+		$child_line_items = $line_item->children($this->default_query_params);
686
+		foreach ($child_line_items as $child_line_item) {
687
+			$this->validateLineItemAndType($child_line_item);
688
+			if ($child_line_item->is_sub_total()) {
689
+				$total += $this->taxableAmountForGlobalTaxes($child_line_item);
690
+			} elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
691
+				// if it's a percent item, only take into account
692
+				// the percentage that's taxable (the taxable total so far)
693
+				if ($child_line_item->is_percent()) {
694
+					$total += $this->calculatePercentage($total, $child_line_item->percent(), true);
695
+				} else {
696
+					// pretax total will be equal to the total for line items with globally applied taxes
697
+					$pretax_total = $this->calculateTotal($child_line_item);
698
+					$total += $this->updatePreTaxTotal($child_line_item, $pretax_total);
699
+				}
700
+			}
701
+		}
702
+		return max($total, 0);
703
+	}
704
+
705
+
706
+	/**
707
+	 * @param EE_Line_Item|null $line_item
708
+	 * @param string|null       $type
709
+	 * @return bool
710
+	 * @throws EE_Error
711
+	 * @throws ReflectionException
712
+	 */
713
+	private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
714
+	{
715
+		if (! $line_item instanceof EE_Line_Item) {
716
+			throw new DomainException(
717
+				esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
718
+			);
719
+		}
720
+		if ($type && $line_item->type() !== $type) {
721
+			throw new DomainException(
722
+				sprintf(
723
+					esc_html__(
724
+						'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
725
+						'event_espresso'
726
+					),
727
+					$line_item->type(),
728
+					$type
729
+				)
730
+			);
731
+		}
732
+		return true;
733
+	}
734 734
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -189,7 +189,7 @@  discard block
 block discarded – undo
189 189
         } else {
190 190
             // recursively loop through children and recalculate their totals
191 191
             $children = $line_item->children($this->default_query_params);
192
-            if (! empty($children)) {
192
+            if ( ! empty($children)) {
193 193
                 // reset the total and pretax total to zero since we are recalculating them
194 194
                 $pretax = $total = 0;
195 195
                 foreach ($children as $child_line_item) {
@@ -206,7 +206,7 @@  discard block
 block discarded – undo
206 206
         $total  = $this->updateTotal($line_item, $total, true);
207 207
         $pretax = $this->updatePreTaxTotal($line_item, $pretax, true);
208 208
 
209
-        if (! $line_item->is_percent()) {
209
+        if ( ! $line_item->is_percent()) {
210 210
             // need to also adjust unit price too if the pretax total or quantity has been updated
211 211
             $this->updateUnitPrice($line_item, $pretax);
212 212
         }
@@ -480,7 +480,7 @@  discard block
 block discarded – undo
480 480
         $taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
481 481
         $global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
482 482
         $non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
483
-        $all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
483
+        $all_tax_total = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
484 484
         $this->recalculateTaxSubTotal($total_line_item);
485 485
         return $all_tax_total;
486 486
     }
@@ -573,9 +573,9 @@  discard block
 block discarded – undo
573 573
                         $line_item->pretaxTotal()
574 574
                     );
575 575
                 } elseif ($line_item->isSubTax()) {
576
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
577
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
578
-                        $non_global_taxes[ $tax_ID ] = [
576
+                    $tax_ID = $line_item->name().'_'.$line_item->percent();
577
+                    if ( ! isset($non_global_taxes[$tax_ID])) {
578
+                        $non_global_taxes[$tax_ID] = [
579 579
                             'name'  => $line_item->name(),
580 580
                             'rate'  => $line_item->percent(),
581 581
                             'total' => 0,
@@ -584,7 +584,7 @@  discard block
 block discarded – undo
584 584
                         ];
585 585
                     }
586 586
                     $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
587
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
587
+                    $non_global_taxes[$tax_ID]['total'] += $tax;
588 588
                 }
589 589
             }
590 590
         }
@@ -623,7 +623,7 @@  discard block
 block discarded – undo
623 623
                     $tax_total += $non_global_tax['total'];
624 624
                 }
625 625
             }
626
-            if (! $found) {
626
+            if ( ! $found) {
627 627
                 // add a new line item for this non global tax
628 628
                 $taxes_subtotal->add_child_line_item(
629 629
                     EE_Line_Item::new_instance(
@@ -681,7 +681,7 @@  discard block
 block discarded – undo
681 681
      */
682 682
     public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
683 683
     {
684
-        $total      = 0;
684
+        $total = 0;
685 685
         $child_line_items = $line_item->children($this->default_query_params);
686 686
         foreach ($child_line_items as $child_line_item) {
687 687
             $this->validateLineItemAndType($child_line_item);
@@ -712,7 +712,7 @@  discard block
 block discarded – undo
712 712
      */
713 713
     private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
714 714
     {
715
-        if (! $line_item instanceof EE_Line_Item) {
715
+        if ( ! $line_item instanceof EE_Line_Item) {
716 716
             throw new DomainException(
717 717
                 esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
718 718
             );
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 1 patch
Indentation   +2046 added lines, -2046 removed lines patch added patch discarded remove patch
@@ -15,2054 +15,2054 @@
 block discarded – undo
15 15
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
16 16
 {
17 17
 
18
-    /**
19
-     * TicKet Archived:
20
-     * constant used by ticket_status() to indicate that a ticket is archived
21
-     * and no longer available for purchase
22
-     */
23
-    const archived = 'TKA';
24
-
25
-    /**
26
-     * TicKet Expired:
27
-     * constant used by ticket_status() to indicate that a ticket is expired
28
-     * and no longer available for purchase
29
-     */
30
-    const expired = 'TKE';
31
-
32
-    /**
33
-     * TicKet On sale:
34
-     * constant used by ticket_status() to indicate that a ticket is On Sale
35
-     * and IS available for purchase
36
-     */
37
-    const onsale = 'TKO';
38
-
39
-    /**
40
-     * TicKet Pending:
41
-     * constant used by ticket_status() to indicate that a ticket is pending
42
-     * and is NOT YET available for purchase
43
-     */
44
-    const pending = 'TKP';
45
-
46
-    /**
47
-     * TicKet Sold out:
48
-     * constant used by ticket_status() to indicate that a ticket is sold out
49
-     * and no longer available for purchases
50
-     */
51
-    const sold_out = 'TKS';
52
-
53
-    /**
54
-     * extra meta key for tracking ticket reservations
55
-     *
56
-     * @type string
57
-     */
58
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
59
-
60
-    /**
61
-     * override of parent property
62
-     *
63
-     * @var EEM_Ticket
64
-     */
65
-    protected $_model;
66
-
67
-    /**
68
-     * cached result from method of the same name
69
-     *
70
-     * @var float $_ticket_total_with_taxes
71
-     */
72
-    private $_ticket_total_with_taxes;
73
-
74
-    /**
75
-     * @var TicketPriceModifiers
76
-     */
77
-    protected $ticket_price_modifiers;
78
-
79
-
80
-    /**
81
-     * @param array  $props_n_values          incoming values
82
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
83
-     *                                        used.)
84
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
85
-     *                                        date_format and the second value is the time format
86
-     * @return EE_Ticket
87
-     * @throws EE_Error
88
-     * @throws ReflectionException
89
-     */
90
-    public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
91
-    {
92
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
93
-        return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
94
-    }
95
-
96
-
97
-    /**
98
-     * @param array  $props_n_values  incoming values from the database
99
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
100
-     *                                the website will be used.
101
-     * @return EE_Ticket
102
-     * @throws EE_Error
103
-     * @throws ReflectionException
104
-     */
105
-    public static function new_instance_from_db($props_n_values = [], $timezone = null)
106
-    {
107
-        return new self($props_n_values, true, $timezone);
108
-    }
109
-
110
-
111
-    /**
112
-     * @param array  $fieldValues
113
-     * @param false  $bydb
114
-     * @param string $timezone
115
-     * @param array  $date_formats
116
-     * @throws EE_Error
117
-     * @throws ReflectionException
118
-     */
119
-    public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
120
-    {
121
-        parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
122
-        $this->ticket_price_modifiers = new TicketPriceModifiers($this);
123
-    }
124
-
125
-
126
-    /**
127
-     * @return bool
128
-     * @throws EE_Error
129
-     * @throws ReflectionException
130
-     */
131
-    public function parent()
132
-    {
133
-        return $this->get('TKT_parent');
134
-    }
135
-
136
-
137
-    /**
138
-     * return if a ticket has quantities available for purchase
139
-     *
140
-     * @param int $DTT_ID the primary key for a particular datetime
141
-     * @return boolean
142
-     * @throws EE_Error
143
-     * @throws ReflectionException
144
-     */
145
-    public function available($DTT_ID = 0)
146
-    {
147
-        // are we checking availability for a particular datetime ?
148
-        if ($DTT_ID) {
149
-            // get that datetime object
150
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
151
-            // if  ticket sales for this datetime have exceeded the reg limit...
152
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
153
-                return false;
154
-            }
155
-        }
156
-        // datetime is still open for registration, but is this ticket sold out ?
157
-        return $this->qty() < 1 || $this->qty() > $this->sold();
158
-    }
159
-
160
-
161
-    /**
162
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
163
-     *
164
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
165
-     *                               relevant status const
166
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
167
-     *                               further processing
168
-     * @return mixed status int if the display string isn't requested
169
-     * @throws EE_Error
170
-     * @throws ReflectionException
171
-     */
172
-    public function ticket_status($display = false, $remaining = null)
173
-    {
174
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
175
-        if (! $remaining) {
176
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
177
-        }
178
-        if ($this->get('TKT_deleted')) {
179
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
180
-        }
181
-        if ($this->is_expired()) {
182
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
183
-        }
184
-        if ($this->is_pending()) {
185
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
186
-        }
187
-        if ($this->is_on_sale()) {
188
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
189
-        }
190
-        return '';
191
-    }
192
-
193
-
194
-    /**
195
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
196
-     * considering ALL the factors used for figuring that out.
197
-     *
198
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
199
-     * @return boolean         true = tickets remaining, false not.
200
-     * @throws EE_Error
201
-     * @throws ReflectionException
202
-     */
203
-    public function is_remaining($DTT_ID = 0)
204
-    {
205
-        $num_remaining = $this->remaining($DTT_ID);
206
-        if ($num_remaining === 0) {
207
-            return false;
208
-        }
209
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
210
-            return false;
211
-        }
212
-        return true;
213
-    }
214
-
215
-
216
-    /**
217
-     * return the total number of tickets available for purchase
218
-     *
219
-     * @param int $DTT_ID  the primary key for a particular datetime.
220
-     *                     set to 0 for all related datetimes
221
-     * @return int
222
-     * @throws EE_Error
223
-     * @throws ReflectionException
224
-     */
225
-    public function remaining($DTT_ID = 0)
226
-    {
227
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
228
-    }
229
-
230
-
231
-    /**
232
-     * Gets min
233
-     *
234
-     * @return int
235
-     * @throws EE_Error
236
-     * @throws ReflectionException
237
-     */
238
-    public function min()
239
-    {
240
-        return $this->get('TKT_min');
241
-    }
242
-
243
-
244
-    /**
245
-     * return if a ticket is no longer available cause its available dates have expired.
246
-     *
247
-     * @return boolean
248
-     * @throws EE_Error
249
-     * @throws ReflectionException
250
-     */
251
-    public function is_expired()
252
-    {
253
-        return ($this->get_raw('TKT_end_date') < time());
254
-    }
255
-
256
-
257
-    /**
258
-     * Return if a ticket is yet to go on sale or not
259
-     *
260
-     * @return boolean
261
-     * @throws EE_Error
262
-     * @throws ReflectionException
263
-     */
264
-    public function is_pending()
265
-    {
266
-        return ($this->get_raw('TKT_start_date') >= time());
267
-    }
268
-
269
-
270
-    /**
271
-     * Return if a ticket is on sale or not
272
-     *
273
-     * @return boolean
274
-     * @throws EE_Error
275
-     * @throws ReflectionException
276
-     */
277
-    public function is_on_sale()
278
-    {
279
-        return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
280
-    }
281
-
282
-
283
-    /**
284
-     * This returns the chronologically last datetime that this ticket is associated with
285
-     *
286
-     * @param string $date_format
287
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
288
-     *                            the end date ie: Jan 01 "to" Dec 31
289
-     * @return string
290
-     * @throws EE_Error
291
-     * @throws ReflectionException
292
-     */
293
-    public function date_range($date_format = '', $conjunction = ' - ')
294
-    {
295
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
296
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
297
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
298
-            : '';
299
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
300
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
301
-            : '';
302
-
303
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
304
-    }
305
-
306
-
307
-    /**
308
-     * This returns the chronologically first datetime that this ticket is associated with
309
-     *
310
-     * @return EE_Datetime
311
-     * @throws EE_Error
312
-     * @throws ReflectionException
313
-     */
314
-    public function first_datetime()
315
-    {
316
-        $datetimes = $this->datetimes(['limit' => 1]);
317
-        return reset($datetimes);
318
-    }
319
-
320
-
321
-    /**
322
-     * Gets all the datetimes this ticket can be used for attending.
323
-     * Unless otherwise specified, orders datetimes by start date.
324
-     *
325
-     * @param array $query_params
326
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
327
-     * @return EE_Datetime[]|EE_Base_Class[]
328
-     * @throws EE_Error
329
-     * @throws ReflectionException
330
-     */
331
-    public function datetimes($query_params = [])
332
-    {
333
-        if (! isset($query_params['order_by'])) {
334
-            $query_params['order_by']['DTT_order'] = 'ASC';
335
-        }
336
-        return $this->get_many_related('Datetime', $query_params);
337
-    }
338
-
339
-
340
-    /**
341
-     * This returns the chronologically last datetime that this ticket is associated with
342
-     *
343
-     * @return EE_Datetime
344
-     * @throws EE_Error
345
-     * @throws ReflectionException
346
-     */
347
-    public function last_datetime()
348
-    {
349
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
350
-        return end($datetimes);
351
-    }
352
-
353
-
354
-    /**
355
-     * This returns the total tickets sold depending on the given parameters.
356
-     *
357
-     * @param string $what    Can be one of two options: 'ticket', 'datetime'.
358
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
359
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
360
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
361
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
362
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
363
-     * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
364
-     * @return mixed (array|int)          how many tickets have sold
365
-     * @throws EE_Error
366
-     * @throws ReflectionException
367
-     */
368
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
369
-    {
370
-        $total        = 0;
371
-        $tickets_sold = $this->_all_tickets_sold();
372
-        switch ($what) {
373
-            case 'ticket':
374
-                return $tickets_sold['ticket'];
375
-
376
-            case 'datetime':
377
-                if (empty($tickets_sold['datetime'])) {
378
-                    return $total;
379
-                }
380
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
381
-                    EE_Error::add_error(
382
-                        __(
383
-                            '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?',
384
-                            'event_espresso'
385
-                        ),
386
-                        __FILE__,
387
-                        __FUNCTION__,
388
-                        __LINE__
389
-                    );
390
-                    return $total;
391
-                }
392
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
393
-
394
-            default:
395
-                return $total;
396
-        }
397
-    }
398
-
399
-
400
-    /**
401
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
402
-     *
403
-     * @return EE_Ticket[]
404
-     * @throws EE_Error
405
-     * @throws ReflectionException
406
-     */
407
-    protected function _all_tickets_sold()
408
-    {
409
-        $datetimes    = $this->get_many_related('Datetime');
410
-        $tickets_sold = [];
411
-        if (! empty($datetimes)) {
412
-            foreach ($datetimes as $datetime) {
413
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
414
-            }
415
-        }
416
-        // Tickets sold
417
-        $tickets_sold['ticket'] = $this->sold();
418
-        return $tickets_sold;
419
-    }
420
-
421
-
422
-    /**
423
-     * This returns the base price object for the ticket.
424
-     *
425
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
426
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
427
-     * @throws EE_Error
428
-     * @throws ReflectionException
429
-     */
430
-    public function base_price(bool $return_array = false)
431
-    {
432
-        $base_price = $this->ticket_price_modifiers->getBasePrice();
433
-        if (! empty($base_price)) {
434
-            return $return_array ? $base_price : reset($base_price);
435
-        }
436
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
437
-        return $return_array
438
-            ? $this->get_many_related('Price', [$_where])
439
-            : $this->get_first_related('Price', [$_where]);
440
-    }
441
-
442
-
443
-    /**
444
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
445
-     *
446
-     * @return EE_Price[]
447
-     * @throws EE_Error
448
-     * @throws ReflectionException
449
-     */
450
-    public function price_modifiers(): array
451
-    {
452
-        $price_modifiers = $this->usesGlobalTaxes()
453
-            ? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
454
-            : $this->ticket_price_modifiers ->getAllModifiersForTicket();
455
-        if (! empty($price_modifiers)) {
456
-            return $price_modifiers;
457
-        }
458
-        return $this->prices(
459
-            [
460
-                [
461
-                    'Price_Type.PBT_ID' => [
462
-                        'NOT IN',
463
-                        [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
464
-                    ]
465
-                ]
466
-            ]
467
-        );
468
-    }
469
-
470
-
471
-    /**
472
-     * This returns ONLY the TAX price modifiers for the ticket
473
-     *
474
-     * @return EE_Price[]
475
-     * @throws EE_Error
476
-     * @throws ReflectionException
477
-     */
478
-    public function tax_price_modifiers(): array
479
-    {
480
-        $tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
481
-        if (! empty($tax_price_modifiers)) {
482
-            return $tax_price_modifiers;
483
-        }
484
-        return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
485
-    }
486
-
487
-
488
-    /**
489
-     * Gets all the prices that combine to form the final price of this ticket
490
-     *
491
-     * @param array $query_params
492
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
493
-     * @return EE_Price[]|EE_Base_Class[]
494
-     * @throws EE_Error
495
-     * @throws ReflectionException
496
-     */
497
-    public function prices(array $query_params = []): array
498
-    {
499
-        if (! isset($query_params['order_by'])) {
500
-            $query_params['order_by']['PRC_order'] = 'ASC';
501
-        }
502
-        return $this->get_many_related('Price', $query_params);
503
-    }
504
-
505
-
506
-    /**
507
-     * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
508
-     *
509
-     * @param array $query_params
510
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
511
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
512
-     * @throws EE_Error
513
-     * @throws ReflectionException
514
-     */
515
-    public function datetime_tickets($query_params = [])
516
-    {
517
-        return $this->get_many_related('Datetime_Ticket', $query_params);
518
-    }
519
-
520
-
521
-    /**
522
-     * Gets all the datetimes from the db ordered by DTT_order
523
-     *
524
-     * @param boolean $show_expired
525
-     * @param boolean $show_deleted
526
-     * @return EE_Datetime[]
527
-     * @throws EE_Error
528
-     * @throws ReflectionException
529
-     */
530
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
531
-    {
532
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
533
-            $this->ID(),
534
-            $show_expired,
535
-            $show_deleted
536
-        );
537
-    }
538
-
539
-
540
-    /**
541
-     * Gets ID
542
-     *
543
-     * @return int
544
-     * @throws EE_Error
545
-     * @throws ReflectionException
546
-     */
547
-    public function ID()
548
-    {
549
-        return $this->get('TKT_ID');
550
-    }
551
-
552
-
553
-    /**
554
-     * get the author of the ticket.
555
-     *
556
-     * @return int
557
-     * @throws EE_Error
558
-     * @throws ReflectionException
559
-     * @since 4.5.0
560
-     */
561
-    public function wp_user()
562
-    {
563
-        return $this->get('TKT_wp_user');
564
-    }
565
-
566
-
567
-    /**
568
-     * Gets the template for the ticket
569
-     *
570
-     * @return EE_Ticket_Template|EE_Base_Class
571
-     * @throws EE_Error
572
-     * @throws ReflectionException
573
-     */
574
-    public function template()
575
-    {
576
-        return $this->get_first_related('Ticket_Template');
577
-    }
578
-
579
-
580
-    /**
581
-     * Simply returns an array of EE_Price objects that are taxes.
582
-     *
583
-     * @return EE_Price[]
584
-     * @throws EE_Error
585
-     * @throws ReflectionException
586
-     */
587
-    public function get_ticket_taxes_for_admin(): array
588
-    {
589
-        return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
590
-    }
591
-
592
-
593
-    /**
594
-     * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
595
-     * as opposed to having tax price modifiers added directly to each ticket
596
-     *
597
-     * @return bool
598
-     * @throws EE_Error
599
-     * @throws ReflectionException
600
-     * @since   $VID:$
601
-     */
602
-    public function usesGlobalTaxes(): bool
603
-    {
604
-        return $this->taxable();
605
-    }
606
-
607
-
608
-    /**
609
-     * @return float
610
-     * @throws EE_Error
611
-     * @throws ReflectionException
612
-     */
613
-    public function ticket_price()
614
-    {
615
-        return $this->get('TKT_price');
616
-    }
617
-
618
-
619
-    /**
620
-     * @return mixed
621
-     * @throws EE_Error
622
-     * @throws ReflectionException
623
-     */
624
-    public function pretty_price()
625
-    {
626
-        return $this->get_pretty('TKT_price');
627
-    }
628
-
629
-
630
-    /**
631
-     * @return bool
632
-     * @throws EE_Error
633
-     * @throws ReflectionException
634
-     */
635
-    public function is_free()
636
-    {
637
-        return $this->get_ticket_total_with_taxes() === (float) 0;
638
-    }
639
-
640
-
641
-    /**
642
-     * get_ticket_total_with_taxes
643
-     *
644
-     * @param bool $no_cache
645
-     * @return float
646
-     * @throws EE_Error
647
-     * @throws ReflectionException
648
-     */
649
-    public function get_ticket_total_with_taxes($no_cache = false)
650
-    {
651
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
652
-            $this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
653
-                ? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
654
-                : $this->ticket_price();
655
-        }
656
-        return (float) $this->_ticket_total_with_taxes;
657
-    }
658
-
659
-
660
-    /**
661
-     * @throws EE_Error
662
-     * @throws ReflectionException
663
-     */
664
-    public function ensure_TKT_Price_correct()
665
-    {
666
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
667
-        $this->save();
668
-    }
669
-
670
-
671
-    /**
672
-     * @return float
673
-     * @throws EE_Error
674
-     * @throws ReflectionException
675
-     */
676
-    public function get_ticket_subtotal()
677
-    {
678
-        return EE_Taxes::get_subtotal_for_admin($this);
679
-    }
680
-
681
-
682
-    /**
683
-     * Returns the total taxes applied to this ticket
684
-     *
685
-     * @return float
686
-     * @throws EE_Error
687
-     * @throws ReflectionException
688
-     */
689
-    public function get_ticket_taxes_total_for_admin()
690
-    {
691
-        return EE_Taxes::get_total_taxes_for_admin($this);
692
-    }
693
-
694
-
695
-    /**
696
-     * Sets name
697
-     *
698
-     * @param string $name
699
-     * @throws EE_Error
700
-     * @throws ReflectionException
701
-     */
702
-    public function set_name($name)
703
-    {
704
-        $this->set('TKT_name', $name);
705
-    }
706
-
707
-
708
-    /**
709
-     * Gets description
710
-     *
711
-     * @return string
712
-     * @throws EE_Error
713
-     * @throws ReflectionException
714
-     */
715
-    public function description()
716
-    {
717
-        return $this->get('TKT_description');
718
-    }
719
-
720
-
721
-    /**
722
-     * Sets description
723
-     *
724
-     * @param string $description
725
-     * @throws EE_Error
726
-     * @throws ReflectionException
727
-     */
728
-    public function set_description($description)
729
-    {
730
-        $this->set('TKT_description', $description);
731
-    }
732
-
733
-
734
-    /**
735
-     * Gets start_date
736
-     *
737
-     * @param string $date_format
738
-     * @param string $time_format
739
-     * @return string
740
-     * @throws EE_Error
741
-     * @throws ReflectionException
742
-     */
743
-    public function start_date($date_format = '', $time_format = '')
744
-    {
745
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
746
-    }
747
-
748
-
749
-    /**
750
-     * Sets start_date
751
-     *
752
-     * @param string $start_date
753
-     * @return void
754
-     * @throws EE_Error
755
-     * @throws ReflectionException
756
-     */
757
-    public function set_start_date($start_date)
758
-    {
759
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
760
-    }
761
-
762
-
763
-    /**
764
-     * Gets end_date
765
-     *
766
-     * @param string $date_format
767
-     * @param string $time_format
768
-     * @return string
769
-     * @throws EE_Error
770
-     * @throws ReflectionException
771
-     */
772
-    public function end_date($date_format = '', $time_format = '')
773
-    {
774
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
775
-    }
776
-
777
-
778
-    /**
779
-     * Sets end_date
780
-     *
781
-     * @param string $end_date
782
-     * @return void
783
-     * @throws EE_Error
784
-     * @throws ReflectionException
785
-     */
786
-    public function set_end_date($end_date)
787
-    {
788
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
789
-    }
790
-
791
-
792
-    /**
793
-     * Sets sell until time
794
-     *
795
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
796
-     * @throws EE_Error
797
-     * @throws ReflectionException
798
-     * @since 4.5.0
799
-     */
800
-    public function set_end_time($time)
801
-    {
802
-        $this->_set_time_for($time, 'TKT_end_date');
803
-    }
804
-
805
-
806
-    /**
807
-     * Sets min
808
-     *
809
-     * @param int $min
810
-     * @return void
811
-     * @throws EE_Error
812
-     * @throws ReflectionException
813
-     */
814
-    public function set_min($min)
815
-    {
816
-        $this->set('TKT_min', $min);
817
-    }
818
-
819
-
820
-    /**
821
-     * Gets max
822
-     *
823
-     * @return int
824
-     * @throws EE_Error
825
-     * @throws ReflectionException
826
-     */
827
-    public function max()
828
-    {
829
-        return $this->get('TKT_max');
830
-    }
831
-
832
-
833
-    /**
834
-     * Sets max
835
-     *
836
-     * @param int $max
837
-     * @return void
838
-     * @throws EE_Error
839
-     * @throws ReflectionException
840
-     */
841
-    public function set_max($max)
842
-    {
843
-        $this->set('TKT_max', $max);
844
-    }
845
-
846
-
847
-    /**
848
-     * Sets price
849
-     *
850
-     * @param float $price
851
-     * @return void
852
-     * @throws EE_Error
853
-     * @throws ReflectionException
854
-     */
855
-    public function set_price($price)
856
-    {
857
-        $this->set('TKT_price', $price);
858
-    }
859
-
860
-
861
-    /**
862
-     * Gets sold
863
-     *
864
-     * @return int
865
-     * @throws EE_Error
866
-     * @throws ReflectionException
867
-     */
868
-    public function sold()
869
-    {
870
-        return $this->get_raw('TKT_sold');
871
-    }
872
-
873
-
874
-    /**
875
-     * Sets sold
876
-     *
877
-     * @param int $sold
878
-     * @return void
879
-     * @throws EE_Error
880
-     * @throws ReflectionException
881
-     */
882
-    public function set_sold($sold)
883
-    {
884
-        // sold can not go below zero
885
-        $sold = max(0, $sold);
886
-        $this->set('TKT_sold', $sold);
887
-    }
888
-
889
-
890
-    /**
891
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
892
-     * associated datetimes.
893
-     *
894
-     * @param int $qty
895
-     * @return boolean
896
-     * @throws EE_Error
897
-     * @throws InvalidArgumentException
898
-     * @throws InvalidDataTypeException
899
-     * @throws InvalidInterfaceException
900
-     * @throws ReflectionException
901
-     * @since 4.9.80.p
902
-     */
903
-    public function increaseSold($qty = 1)
904
-    {
905
-        $qty = absint($qty);
906
-        // increment sold and decrement reserved datetime quantities simultaneously
907
-        // don't worry about failures, because they must have already had a spot reserved
908
-        $this->increaseSoldForDatetimes($qty);
909
-        // Increment and decrement ticket quantities simultaneously
910
-        $success = $this->adjustNumericFieldsInDb(
911
-            [
912
-                'TKT_reserved' => $qty * -1,
913
-                'TKT_sold'     => $qty,
914
-            ]
915
-        );
916
-        do_action(
917
-            'AHEE__EE_Ticket__increase_sold',
918
-            $this,
919
-            $qty,
920
-            $this->sold(),
921
-            $success
922
-        );
923
-        return $success;
924
-    }
925
-
926
-
927
-    /**
928
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
929
-     *
930
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
931
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
932
-     * @param EE_Datetime[] $datetimes
933
-     * @throws EE_Error
934
-     * @throws InvalidArgumentException
935
-     * @throws InvalidDataTypeException
936
-     * @throws InvalidInterfaceException
937
-     * @throws ReflectionException
938
-     * @since 4.9.80.p
939
-     */
940
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
941
-    {
942
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
943
-        foreach ($datetimes as $datetime) {
944
-            $datetime->increaseSold($qty);
945
-        }
946
-    }
947
-
948
-
949
-    /**
950
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
951
-     * DB and then updates the model objects.
952
-     * Does not affect the reserved counts.
953
-     *
954
-     * @param int $qty
955
-     * @return boolean
956
-     * @throws EE_Error
957
-     * @throws InvalidArgumentException
958
-     * @throws InvalidDataTypeException
959
-     * @throws InvalidInterfaceException
960
-     * @throws ReflectionException
961
-     * @since 4.9.80.p
962
-     */
963
-    public function decreaseSold($qty = 1)
964
-    {
965
-        $qty = absint($qty);
966
-        $this->decreaseSoldForDatetimes($qty);
967
-        $success = $this->adjustNumericFieldsInDb(
968
-            [
969
-                'TKT_sold' => $qty * -1,
970
-            ]
971
-        );
972
-        do_action(
973
-            'AHEE__EE_Ticket__decrease_sold',
974
-            $this,
975
-            $qty,
976
-            $this->sold(),
977
-            $success
978
-        );
979
-        return $success;
980
-    }
981
-
982
-
983
-    /**
984
-     * Decreases sold on related datetimes
985
-     *
986
-     * @param int           $qty
987
-     * @param EE_Datetime[] $datetimes
988
-     * @return void
989
-     * @throws EE_Error
990
-     * @throws InvalidArgumentException
991
-     * @throws InvalidDataTypeException
992
-     * @throws InvalidInterfaceException
993
-     * @throws ReflectionException
994
-     * @since 4.9.80.p
995
-     */
996
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
997
-    {
998
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
999
-        if (is_array($datetimes)) {
1000
-            foreach ($datetimes as $datetime) {
1001
-                if ($datetime instanceof EE_Datetime) {
1002
-                    $datetime->decreaseSold($qty);
1003
-                }
1004
-            }
1005
-        }
1006
-    }
1007
-
1008
-
1009
-    /**
1010
-     * Gets qty of reserved tickets
1011
-     *
1012
-     * @return int
1013
-     * @throws EE_Error
1014
-     * @throws ReflectionException
1015
-     */
1016
-    public function reserved()
1017
-    {
1018
-        return $this->get_raw('TKT_reserved');
1019
-    }
1020
-
1021
-
1022
-    /**
1023
-     * Sets reserved
1024
-     *
1025
-     * @param int $reserved
1026
-     * @return void
1027
-     * @throws EE_Error
1028
-     * @throws ReflectionException
1029
-     */
1030
-    public function set_reserved($reserved)
1031
-    {
1032
-        // reserved can not go below zero
1033
-        $reserved = max(0, (int) $reserved);
1034
-        $this->set('TKT_reserved', $reserved);
1035
-    }
1036
-
1037
-
1038
-    /**
1039
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1040
-     *
1041
-     * @param int    $qty
1042
-     * @param string $source
1043
-     * @return bool whether we successfully reserved the ticket or not.
1044
-     * @throws EE_Error
1045
-     * @throws InvalidArgumentException
1046
-     * @throws ReflectionException
1047
-     * @throws InvalidDataTypeException
1048
-     * @throws InvalidInterfaceException
1049
-     * @since 4.9.80.p
1050
-     */
1051
-    public function increaseReserved($qty = 1, $source = 'unknown')
1052
-    {
1053
-        $qty = absint($qty);
1054
-        do_action(
1055
-            'AHEE__EE_Ticket__increase_reserved__begin',
1056
-            $this,
1057
-            $qty,
1058
-            $source
1059
-        );
1060
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1061
-        $success                         = false;
1062
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1063
-        if ($datetimes_adjusted_successfully) {
1064
-            $success = $this->incrementFieldConditionallyInDb(
1065
-                'TKT_reserved',
1066
-                'TKT_sold',
1067
-                'TKT_qty',
1068
-                $qty
1069
-            );
1070
-            if (! $success) {
1071
-                // The datetimes were successfully bumped, but not the
1072
-                // ticket. So we need to manually rollback the datetimes.
1073
-                $this->decreaseReservedForDatetimes($qty);
1074
-            }
1075
-        }
1076
-        do_action(
1077
-            'AHEE__EE_Ticket__increase_reserved',
1078
-            $this,
1079
-            $qty,
1080
-            $this->reserved(),
1081
-            $success
1082
-        );
1083
-        return $success;
1084
-    }
1085
-
1086
-
1087
-    /**
1088
-     * Increases reserved counts on related datetimes
1089
-     *
1090
-     * @param int           $qty
1091
-     * @param EE_Datetime[] $datetimes
1092
-     * @return boolean indicating success
1093
-     * @throws EE_Error
1094
-     * @throws InvalidArgumentException
1095
-     * @throws InvalidDataTypeException
1096
-     * @throws InvalidInterfaceException
1097
-     * @throws ReflectionException
1098
-     * @since 4.9.80.p
1099
-     */
1100
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1101
-    {
1102
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1103
-        $datetimes_updated = [];
1104
-        $limit_exceeded    = false;
1105
-        if (is_array($datetimes)) {
1106
-            foreach ($datetimes as $datetime) {
1107
-                if ($datetime instanceof EE_Datetime) {
1108
-                    if ($datetime->increaseReserved($qty)) {
1109
-                        $datetimes_updated[] = $datetime;
1110
-                    } else {
1111
-                        $limit_exceeded = true;
1112
-                        break;
1113
-                    }
1114
-                }
1115
-            }
1116
-            // If somewhere along the way we detected a datetime whose
1117
-            // limit was exceeded, do a manual rollback.
1118
-            if ($limit_exceeded) {
1119
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1120
-                return false;
1121
-            }
1122
-        }
1123
-        return true;
1124
-    }
1125
-
1126
-
1127
-    /**
1128
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1129
-     *
1130
-     * @param int    $qty
1131
-     * @param bool   $adjust_datetimes
1132
-     * @param string $source
1133
-     * @return boolean
1134
-     * @throws EE_Error
1135
-     * @throws InvalidArgumentException
1136
-     * @throws ReflectionException
1137
-     * @throws InvalidDataTypeException
1138
-     * @throws InvalidInterfaceException
1139
-     * @since 4.9.80.p
1140
-     */
1141
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1142
-    {
1143
-        $qty = absint($qty);
1144
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1145
-        if ($adjust_datetimes) {
1146
-            $this->decreaseReservedForDatetimes($qty);
1147
-        }
1148
-        $success = $this->adjustNumericFieldsInDb(
1149
-            [
1150
-                'TKT_reserved' => $qty * -1,
1151
-            ]
1152
-        );
1153
-        do_action(
1154
-            'AHEE__EE_Ticket__decrease_reserved',
1155
-            $this,
1156
-            $qty,
1157
-            $this->reserved(),
1158
-            $success
1159
-        );
1160
-        return $success;
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * Decreases the reserved count on the specified datetimes.
1166
-     *
1167
-     * @param int           $qty
1168
-     * @param EE_Datetime[] $datetimes
1169
-     * @throws EE_Error
1170
-     * @throws InvalidArgumentException
1171
-     * @throws ReflectionException
1172
-     * @throws InvalidDataTypeException
1173
-     * @throws InvalidInterfaceException
1174
-     * @since 4.9.80.p
1175
-     */
1176
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1177
-    {
1178
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1179
-        foreach ($datetimes as $datetime) {
1180
-            if ($datetime instanceof EE_Datetime) {
1181
-                $datetime->decreaseReserved($qty);
1182
-            }
1183
-        }
1184
-    }
1185
-
1186
-
1187
-    /**
1188
-     * Gets ticket quantity
1189
-     *
1190
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1191
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1192
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1193
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1194
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1195
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1196
-     * @return int
1197
-     * @throws EE_Error
1198
-     * @throws ReflectionException
1199
-     */
1200
-    public function qty($context = '')
1201
-    {
1202
-        switch ($context) {
1203
-            case 'reg_limit':
1204
-                return $this->real_quantity_on_ticket();
1205
-            case 'saleable':
1206
-                return $this->real_quantity_on_ticket('saleable');
1207
-            default:
1208
-                return $this->get_raw('TKT_qty');
1209
-        }
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * Gets ticket quantity
1215
-     *
1216
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1217
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1218
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1219
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1220
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1221
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1222
-     *                            set to 0 for all related datetimes
1223
-     * @return int
1224
-     * @throws EE_Error
1225
-     * @throws ReflectionException
1226
-     */
1227
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1228
-    {
1229
-        $raw = $this->get_raw('TKT_qty');
1230
-        // return immediately if it's zero
1231
-        if ($raw === 0) {
1232
-            return $raw;
1233
-        }
1234
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1235
-        // ensure qty doesn't exceed raw value for THIS ticket
1236
-        $qty = min(EE_INF, $raw);
1237
-        // echo "\n . qty: " . $qty . '<br />';
1238
-        // calculate this ticket's total sales and reservations
1239
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1240
-        // echo "\n . sold: " . $this->sold() . '<br />';
1241
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1242
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1243
-        // first we need to calculate the maximum number of tickets available for the datetime
1244
-        // do we want data for one datetime or all of them ?
1245
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1246
-        $datetimes    = $this->datetimes($query_params);
1247
-        if (is_array($datetimes) && ! empty($datetimes)) {
1248
-            foreach ($datetimes as $datetime) {
1249
-                if ($datetime instanceof EE_Datetime) {
1250
-                    $datetime->refresh_from_db();
1251
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1252
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1253
-                    // initialize with no restrictions for each datetime
1254
-                    // but adjust datetime qty based on datetime reg limit
1255
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1256
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1257
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1258
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1259
-                    // and reservations for this datetime, that do NOT include sales and reservations
1260
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1261
-                    if ($context === 'saleable') {
1262
-                        $datetime_qty = max(
1263
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1264
-                            0
1265
-                        );
1266
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1267
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1268
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1269
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1270
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1271
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1272
-                    }
1273
-                    $qty = min($datetime_qty, $qty);
1274
-                    // echo "\n . . qty: " . $qty . '<br />';
1275
-                }
1276
-            }
1277
-        }
1278
-        // NOW that we know the  maximum number of tickets available for the datetime
1279
-        // we can finally factor in the details for this specific ticket
1280
-        if ($qty > 0 && $context === 'saleable') {
1281
-            // and subtract the sales for THIS ticket
1282
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1283
-            // echo "\n . qty: " . $qty . '<br />';
1284
-        }
1285
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1286
-        return $qty;
1287
-    }
1288
-
1289
-
1290
-    /**
1291
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1292
-     *
1293
-     * @param int $qty
1294
-     * @return void
1295
-     * @throws EE_Error
1296
-     * @throws ReflectionException
1297
-     */
1298
-    public function set_qty($qty)
1299
-    {
1300
-        $datetimes = $this->datetimes();
1301
-        foreach ($datetimes as $datetime) {
1302
-            if ($datetime instanceof EE_Datetime) {
1303
-                $qty = min($qty, $datetime->reg_limit());
1304
-            }
1305
-        }
1306
-        $this->set('TKT_qty', $qty);
1307
-    }
1308
-
1309
-
1310
-    /**
1311
-     * Gets uses
1312
-     *
1313
-     * @return int
1314
-     * @throws EE_Error
1315
-     * @throws ReflectionException
1316
-     */
1317
-    public function uses()
1318
-    {
1319
-        return $this->get('TKT_uses');
1320
-    }
1321
-
1322
-
1323
-    /**
1324
-     * Sets uses
1325
-     *
1326
-     * @param int $uses
1327
-     * @return void
1328
-     * @throws EE_Error
1329
-     * @throws ReflectionException
1330
-     */
1331
-    public function set_uses($uses)
1332
-    {
1333
-        $this->set('TKT_uses', $uses);
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     * returns whether ticket is required or not.
1339
-     *
1340
-     * @return boolean
1341
-     * @throws EE_Error
1342
-     * @throws ReflectionException
1343
-     */
1344
-    public function required()
1345
-    {
1346
-        return $this->get('TKT_required');
1347
-    }
1348
-
1349
-
1350
-    /**
1351
-     * sets the TKT_required property
1352
-     *
1353
-     * @param boolean $required
1354
-     * @return void
1355
-     * @throws EE_Error
1356
-     * @throws ReflectionException
1357
-     */
1358
-    public function set_required($required)
1359
-    {
1360
-        $this->set('TKT_required', $required);
1361
-    }
1362
-
1363
-
1364
-    /**
1365
-     * Gets taxable
1366
-     *
1367
-     * @return boolean
1368
-     * @throws EE_Error
1369
-     * @throws ReflectionException
1370
-     */
1371
-    public function taxable()
1372
-    {
1373
-        return $this->get('TKT_taxable');
1374
-    }
1375
-
1376
-
1377
-    /**
1378
-     * Sets taxable
1379
-     *
1380
-     * @param boolean $taxable
1381
-     * @return void
1382
-     * @throws EE_Error
1383
-     * @throws ReflectionException
1384
-     */
1385
-    public function set_taxable($taxable)
1386
-    {
1387
-        $this->set('TKT_taxable', $taxable);
1388
-    }
1389
-
1390
-
1391
-    /**
1392
-     * Gets is_default
1393
-     *
1394
-     * @return boolean
1395
-     * @throws EE_Error
1396
-     * @throws ReflectionException
1397
-     */
1398
-    public function is_default()
1399
-    {
1400
-        return $this->get('TKT_is_default');
1401
-    }
1402
-
1403
-
1404
-    /**
1405
-     * Sets is_default
1406
-     *
1407
-     * @param boolean $is_default
1408
-     * @return void
1409
-     * @throws EE_Error
1410
-     * @throws ReflectionException
1411
-     */
1412
-    public function set_is_default($is_default)
1413
-    {
1414
-        $this->set('TKT_is_default', $is_default);
1415
-    }
1416
-
1417
-
1418
-    /**
1419
-     * Gets order
1420
-     *
1421
-     * @return int
1422
-     * @throws EE_Error
1423
-     * @throws ReflectionException
1424
-     */
1425
-    public function order()
1426
-    {
1427
-        return $this->get('TKT_order');
1428
-    }
1429
-
1430
-
1431
-    /**
1432
-     * Sets order
1433
-     *
1434
-     * @param int $order
1435
-     * @return void
1436
-     * @throws EE_Error
1437
-     * @throws ReflectionException
1438
-     */
1439
-    public function set_order($order)
1440
-    {
1441
-        $this->set('TKT_order', $order);
1442
-    }
1443
-
1444
-
1445
-    /**
1446
-     * Gets row
1447
-     *
1448
-     * @return int
1449
-     * @throws EE_Error
1450
-     * @throws ReflectionException
1451
-     */
1452
-    public function row()
1453
-    {
1454
-        return $this->get('TKT_row');
1455
-    }
1456
-
1457
-
1458
-    /**
1459
-     * Sets row
1460
-     *
1461
-     * @param int $row
1462
-     * @return void
1463
-     * @throws EE_Error
1464
-     * @throws ReflectionException
1465
-     */
1466
-    public function set_row($row)
1467
-    {
1468
-        $this->set('TKT_row', $row);
1469
-    }
1470
-
1471
-
1472
-    /**
1473
-     * Gets deleted
1474
-     *
1475
-     * @return boolean
1476
-     * @throws EE_Error
1477
-     * @throws ReflectionException
1478
-     */
1479
-    public function deleted()
1480
-    {
1481
-        return $this->get('TKT_deleted');
1482
-    }
1483
-
1484
-
1485
-    /**
1486
-     * Sets deleted
1487
-     *
1488
-     * @param boolean $deleted
1489
-     * @return void
1490
-     * @throws EE_Error
1491
-     * @throws ReflectionException
1492
-     */
1493
-    public function set_deleted($deleted)
1494
-    {
1495
-        $this->set('TKT_deleted', $deleted);
1496
-    }
1497
-
1498
-
1499
-    /**
1500
-     * Gets parent
1501
-     *
1502
-     * @return int
1503
-     * @throws EE_Error
1504
-     * @throws ReflectionException
1505
-     */
1506
-    public function parent_ID()
1507
-    {
1508
-        return $this->get('TKT_parent');
1509
-    }
1510
-
1511
-
1512
-    /**
1513
-     * Sets parent
1514
-     *
1515
-     * @param int $parent
1516
-     * @return void
1517
-     * @throws EE_Error
1518
-     * @throws ReflectionException
1519
-     */
1520
-    public function set_parent_ID($parent)
1521
-    {
1522
-        $this->set('TKT_parent', $parent);
1523
-    }
1524
-
1525
-
1526
-    /**
1527
-     * @return boolean
1528
-     * @throws EE_Error
1529
-     * @throws InvalidArgumentException
1530
-     * @throws InvalidDataTypeException
1531
-     * @throws InvalidInterfaceException
1532
-     * @throws ReflectionException
1533
-     */
1534
-    public function reverse_calculate()
1535
-    {
1536
-        return $this->get('TKT_reverse_calculate');
1537
-    }
1538
-
1539
-
1540
-    /**
1541
-     * @param boolean $reverse_calculate
1542
-     * @throws EE_Error
1543
-     * @throws InvalidArgumentException
1544
-     * @throws InvalidDataTypeException
1545
-     * @throws InvalidInterfaceException
1546
-     * @throws ReflectionException
1547
-     */
1548
-    public function set_reverse_calculate($reverse_calculate)
1549
-    {
1550
-        $this->set('TKT_reverse_calculate', $reverse_calculate);
1551
-    }
1552
-
1553
-
1554
-    /**
1555
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1556
-     *
1557
-     * @return string
1558
-     * @throws EE_Error
1559
-     * @throws ReflectionException
1560
-     */
1561
-    public function name_and_info()
1562
-    {
1563
-        $times = [];
1564
-        foreach ($this->datetimes() as $datetime) {
1565
-            $times[] = $datetime->start_date_and_time();
1566
-        }
1567
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1568
-    }
1569
-
1570
-
1571
-    /**
1572
-     * Gets name
1573
-     *
1574
-     * @return string
1575
-     * @throws EE_Error
1576
-     * @throws ReflectionException
1577
-     */
1578
-    public function name()
1579
-    {
1580
-        return $this->get('TKT_name');
1581
-    }
1582
-
1583
-
1584
-    /**
1585
-     * Gets price
1586
-     *
1587
-     * @return float
1588
-     * @throws EE_Error
1589
-     * @throws ReflectionException
1590
-     */
1591
-    public function price()
1592
-    {
1593
-        return $this->get('TKT_price');
1594
-    }
1595
-
1596
-
1597
-    /**
1598
-     * Gets all the registrations for this ticket
1599
-     *
1600
-     * @param array $query_params
1601
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1602
-     * @return EE_Registration[]|EE_Base_Class[]
1603
-     * @throws EE_Error
1604
-     * @throws ReflectionException
1605
-     */
1606
-    public function registrations($query_params = [])
1607
-    {
1608
-        return $this->get_many_related('Registration', $query_params);
1609
-    }
1610
-
1611
-
1612
-    /**
1613
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1614
-     *
1615
-     * @return int
1616
-     * @throws EE_Error
1617
-     * @throws ReflectionException
1618
-     */
1619
-    public function update_tickets_sold()
1620
-    {
1621
-        $count_regs_for_this_ticket = $this->count_registrations(
1622
-            [
1623
-                [
1624
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1625
-                    'REG_deleted' => 0,
1626
-                ],
1627
-            ]
1628
-        );
1629
-        $this->set_sold($count_regs_for_this_ticket);
1630
-        $this->save();
1631
-        return $count_regs_for_this_ticket;
1632
-    }
1633
-
1634
-
1635
-    /**
1636
-     * Counts the registrations for this ticket
1637
-     *
1638
-     * @param array $query_params
1639
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1640
-     * @return int
1641
-     * @throws EE_Error
1642
-     * @throws ReflectionException
1643
-     */
1644
-    public function count_registrations($query_params = [])
1645
-    {
1646
-        return $this->count_related('Registration', $query_params);
1647
-    }
1648
-
1649
-
1650
-    /**
1651
-     * Implementation for EEI_Has_Icon interface method.
1652
-     *
1653
-     * @return string
1654
-     * @see EEI_Visual_Representation for comments
1655
-     */
1656
-    public function get_icon()
1657
-    {
1658
-        return '<span class="dashicons dashicons-tickets-alt"/>';
1659
-    }
1660
-
1661
-
1662
-    /**
1663
-     * Implementation of the EEI_Event_Relation interface method
1664
-     *
1665
-     * @return EE_Event
1666
-     * @throws EE_Error
1667
-     * @throws UnexpectedEntityException
1668
-     * @throws ReflectionException
1669
-     * @see EEI_Event_Relation for comments
1670
-     */
1671
-    public function get_related_event()
1672
-    {
1673
-        // get one datetime to use for getting the event
1674
-        $datetime = $this->first_datetime();
1675
-        if (! $datetime instanceof EE_Datetime) {
1676
-            throw new UnexpectedEntityException(
1677
-                $datetime,
1678
-                'EE_Datetime',
1679
-                sprintf(
1680
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1681
-                    $this->name()
1682
-                )
1683
-            );
1684
-        }
1685
-        $event = $datetime->event();
1686
-        if (! $event instanceof EE_Event) {
1687
-            throw new UnexpectedEntityException(
1688
-                $event,
1689
-                'EE_Event',
1690
-                sprintf(
1691
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1692
-                    $this->name()
1693
-                )
1694
-            );
1695
-        }
1696
-        return $event;
1697
-    }
1698
-
1699
-
1700
-    /**
1701
-     * Implementation of the EEI_Event_Relation interface method
1702
-     *
1703
-     * @return string
1704
-     * @throws UnexpectedEntityException
1705
-     * @throws EE_Error
1706
-     * @throws ReflectionException
1707
-     * @see EEI_Event_Relation for comments
1708
-     */
1709
-    public function get_event_name()
1710
-    {
1711
-        $event = $this->get_related_event();
1712
-        return $event instanceof EE_Event ? $event->name() : '';
1713
-    }
1714
-
1715
-
1716
-    /**
1717
-     * Implementation of the EEI_Event_Relation interface method
1718
-     *
1719
-     * @return int
1720
-     * @throws UnexpectedEntityException
1721
-     * @throws EE_Error
1722
-     * @throws ReflectionException
1723
-     * @see EEI_Event_Relation for comments
1724
-     */
1725
-    public function get_event_ID()
1726
-    {
1727
-        $event = $this->get_related_event();
1728
-        return $event instanceof EE_Event ? $event->ID() : 0;
1729
-    }
1730
-
1731
-
1732
-    /**
1733
-     * This simply returns whether a ticket can be permanently deleted or not.
1734
-     * The criteria for determining this is whether the ticket has any related registrations.
1735
-     * If there are none then it can be permanently deleted.
1736
-     *
1737
-     * @return bool
1738
-     * @throws EE_Error
1739
-     * @throws ReflectionException
1740
-     */
1741
-    public function is_permanently_deleteable()
1742
-    {
1743
-        return $this->count_registrations() === 0;
1744
-    }
1745
-
1746
-
1747
-    /**
1748
-     * @return int
1749
-     * @throws EE_Error
1750
-     * @throws ReflectionException
1751
-     * @since   $VID:$
1752
-     */
1753
-    public function visibility(): int
1754
-    {
1755
-        return $this->get('TKT_visibility');
1756
-    }
1757
-
1758
-
1759
-    /**
1760
-     * @return int
1761
-     * @throws EE_Error
1762
-     * @throws ReflectionException
1763
-     * @since   $VID:$
1764
-     */
1765
-    public function isHidden(): int
1766
-    {
1767
-        return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1768
-    }
1769
-
1770
-
1771
-    /**
1772
-     * @return int
1773
-     * @throws EE_Error
1774
-     * @throws ReflectionException
1775
-     * @since   $VID:$
1776
-     */
1777
-    public function isNotHidden(): int
1778
-    {
1779
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1780
-    }
1781
-
1782
-
1783
-    /**
1784
-     * @return int
1785
-     * @throws EE_Error
1786
-     * @throws ReflectionException
1787
-     * @since   $VID:$
1788
-     */
1789
-    public function isPublicOnly(): int
1790
-    {
1791
-        return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1792
-    }
1793
-
1794
-
1795
-    /**
1796
-     * @return int
1797
-     * @throws EE_Error
1798
-     * @throws ReflectionException
1799
-     * @since   $VID:$
1800
-     */
1801
-    public function isMembersOnly(): int
1802
-    {
1803
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1804
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1805
-    }
1806
-
1807
-
1808
-    /**
1809
-     * @return int
1810
-     * @throws EE_Error
1811
-     * @throws ReflectionException
1812
-     * @since   $VID:$
1813
-     */
1814
-    public function isAdminsOnly(): int
1815
-    {
1816
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1817
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1818
-    }
1819
-
1820
-
1821
-    /**
1822
-     * @return int
1823
-     * @throws EE_Error
1824
-     * @throws ReflectionException
1825
-     * @since   $VID:$
1826
-     */
1827
-    public function isAdminUiOnly(): int
1828
-    {
1829
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1830
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1831
-    }
1832
-
1833
-
1834
-    /**
1835
-     * @param int $visibility
1836
-     * @throws EE_Error
1837
-     * @throws ReflectionException
1838
-     * @since   $VID:$
1839
-     */
1840
-    public function set_visibility(int $visibility)
1841
-    {
1842
-
1843
-        $ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1844
-        $ticket_visibility         = -1;
1845
-        foreach ($ticket_visibility_options as $ticket_visibility_option) {
1846
-            if ($visibility === $ticket_visibility_option) {
1847
-                $ticket_visibility = $visibility;
1848
-            }
1849
-        }
1850
-        if ($ticket_visibility === -1) {
1851
-            throw new DomainException(
1852
-                sprintf(
1853
-                    esc_html__(
1854
-                        '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 ',
1855
-                        'event_espresso'
1856
-                    ),
1857
-                    $visibility,
1858
-                    '<br />',
1859
-                    var_export($ticket_visibility_options, true)
1860
-                )
1861
-            );
1862
-        }
1863
-        $this->set('TKT_visibility', $ticket_visibility);
1864
-    }
1865
-
1866
-
1867
-    /*******************************************************************
18
+	/**
19
+	 * TicKet Archived:
20
+	 * constant used by ticket_status() to indicate that a ticket is archived
21
+	 * and no longer available for purchase
22
+	 */
23
+	const archived = 'TKA';
24
+
25
+	/**
26
+	 * TicKet Expired:
27
+	 * constant used by ticket_status() to indicate that a ticket is expired
28
+	 * and no longer available for purchase
29
+	 */
30
+	const expired = 'TKE';
31
+
32
+	/**
33
+	 * TicKet On sale:
34
+	 * constant used by ticket_status() to indicate that a ticket is On Sale
35
+	 * and IS available for purchase
36
+	 */
37
+	const onsale = 'TKO';
38
+
39
+	/**
40
+	 * TicKet Pending:
41
+	 * constant used by ticket_status() to indicate that a ticket is pending
42
+	 * and is NOT YET available for purchase
43
+	 */
44
+	const pending = 'TKP';
45
+
46
+	/**
47
+	 * TicKet Sold out:
48
+	 * constant used by ticket_status() to indicate that a ticket is sold out
49
+	 * and no longer available for purchases
50
+	 */
51
+	const sold_out = 'TKS';
52
+
53
+	/**
54
+	 * extra meta key for tracking ticket reservations
55
+	 *
56
+	 * @type string
57
+	 */
58
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
59
+
60
+	/**
61
+	 * override of parent property
62
+	 *
63
+	 * @var EEM_Ticket
64
+	 */
65
+	protected $_model;
66
+
67
+	/**
68
+	 * cached result from method of the same name
69
+	 *
70
+	 * @var float $_ticket_total_with_taxes
71
+	 */
72
+	private $_ticket_total_with_taxes;
73
+
74
+	/**
75
+	 * @var TicketPriceModifiers
76
+	 */
77
+	protected $ticket_price_modifiers;
78
+
79
+
80
+	/**
81
+	 * @param array  $props_n_values          incoming values
82
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
83
+	 *                                        used.)
84
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
85
+	 *                                        date_format and the second value is the time format
86
+	 * @return EE_Ticket
87
+	 * @throws EE_Error
88
+	 * @throws ReflectionException
89
+	 */
90
+	public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
91
+	{
92
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
93
+		return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
94
+	}
95
+
96
+
97
+	/**
98
+	 * @param array  $props_n_values  incoming values from the database
99
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
100
+	 *                                the website will be used.
101
+	 * @return EE_Ticket
102
+	 * @throws EE_Error
103
+	 * @throws ReflectionException
104
+	 */
105
+	public static function new_instance_from_db($props_n_values = [], $timezone = null)
106
+	{
107
+		return new self($props_n_values, true, $timezone);
108
+	}
109
+
110
+
111
+	/**
112
+	 * @param array  $fieldValues
113
+	 * @param false  $bydb
114
+	 * @param string $timezone
115
+	 * @param array  $date_formats
116
+	 * @throws EE_Error
117
+	 * @throws ReflectionException
118
+	 */
119
+	public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
120
+	{
121
+		parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
122
+		$this->ticket_price_modifiers = new TicketPriceModifiers($this);
123
+	}
124
+
125
+
126
+	/**
127
+	 * @return bool
128
+	 * @throws EE_Error
129
+	 * @throws ReflectionException
130
+	 */
131
+	public function parent()
132
+	{
133
+		return $this->get('TKT_parent');
134
+	}
135
+
136
+
137
+	/**
138
+	 * return if a ticket has quantities available for purchase
139
+	 *
140
+	 * @param int $DTT_ID the primary key for a particular datetime
141
+	 * @return boolean
142
+	 * @throws EE_Error
143
+	 * @throws ReflectionException
144
+	 */
145
+	public function available($DTT_ID = 0)
146
+	{
147
+		// are we checking availability for a particular datetime ?
148
+		if ($DTT_ID) {
149
+			// get that datetime object
150
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
151
+			// if  ticket sales for this datetime have exceeded the reg limit...
152
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
153
+				return false;
154
+			}
155
+		}
156
+		// datetime is still open for registration, but is this ticket sold out ?
157
+		return $this->qty() < 1 || $this->qty() > $this->sold();
158
+	}
159
+
160
+
161
+	/**
162
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
163
+	 *
164
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
165
+	 *                               relevant status const
166
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
167
+	 *                               further processing
168
+	 * @return mixed status int if the display string isn't requested
169
+	 * @throws EE_Error
170
+	 * @throws ReflectionException
171
+	 */
172
+	public function ticket_status($display = false, $remaining = null)
173
+	{
174
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
175
+		if (! $remaining) {
176
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
177
+		}
178
+		if ($this->get('TKT_deleted')) {
179
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
180
+		}
181
+		if ($this->is_expired()) {
182
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
183
+		}
184
+		if ($this->is_pending()) {
185
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
186
+		}
187
+		if ($this->is_on_sale()) {
188
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
189
+		}
190
+		return '';
191
+	}
192
+
193
+
194
+	/**
195
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
196
+	 * considering ALL the factors used for figuring that out.
197
+	 *
198
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
199
+	 * @return boolean         true = tickets remaining, false not.
200
+	 * @throws EE_Error
201
+	 * @throws ReflectionException
202
+	 */
203
+	public function is_remaining($DTT_ID = 0)
204
+	{
205
+		$num_remaining = $this->remaining($DTT_ID);
206
+		if ($num_remaining === 0) {
207
+			return false;
208
+		}
209
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
210
+			return false;
211
+		}
212
+		return true;
213
+	}
214
+
215
+
216
+	/**
217
+	 * return the total number of tickets available for purchase
218
+	 *
219
+	 * @param int $DTT_ID  the primary key for a particular datetime.
220
+	 *                     set to 0 for all related datetimes
221
+	 * @return int
222
+	 * @throws EE_Error
223
+	 * @throws ReflectionException
224
+	 */
225
+	public function remaining($DTT_ID = 0)
226
+	{
227
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
228
+	}
229
+
230
+
231
+	/**
232
+	 * Gets min
233
+	 *
234
+	 * @return int
235
+	 * @throws EE_Error
236
+	 * @throws ReflectionException
237
+	 */
238
+	public function min()
239
+	{
240
+		return $this->get('TKT_min');
241
+	}
242
+
243
+
244
+	/**
245
+	 * return if a ticket is no longer available cause its available dates have expired.
246
+	 *
247
+	 * @return boolean
248
+	 * @throws EE_Error
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function is_expired()
252
+	{
253
+		return ($this->get_raw('TKT_end_date') < time());
254
+	}
255
+
256
+
257
+	/**
258
+	 * Return if a ticket is yet to go on sale or not
259
+	 *
260
+	 * @return boolean
261
+	 * @throws EE_Error
262
+	 * @throws ReflectionException
263
+	 */
264
+	public function is_pending()
265
+	{
266
+		return ($this->get_raw('TKT_start_date') >= time());
267
+	}
268
+
269
+
270
+	/**
271
+	 * Return if a ticket is on sale or not
272
+	 *
273
+	 * @return boolean
274
+	 * @throws EE_Error
275
+	 * @throws ReflectionException
276
+	 */
277
+	public function is_on_sale()
278
+	{
279
+		return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
280
+	}
281
+
282
+
283
+	/**
284
+	 * This returns the chronologically last datetime that this ticket is associated with
285
+	 *
286
+	 * @param string $date_format
287
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
288
+	 *                            the end date ie: Jan 01 "to" Dec 31
289
+	 * @return string
290
+	 * @throws EE_Error
291
+	 * @throws ReflectionException
292
+	 */
293
+	public function date_range($date_format = '', $conjunction = ' - ')
294
+	{
295
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
296
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
297
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
298
+			: '';
299
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
300
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
301
+			: '';
302
+
303
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
304
+	}
305
+
306
+
307
+	/**
308
+	 * This returns the chronologically first datetime that this ticket is associated with
309
+	 *
310
+	 * @return EE_Datetime
311
+	 * @throws EE_Error
312
+	 * @throws ReflectionException
313
+	 */
314
+	public function first_datetime()
315
+	{
316
+		$datetimes = $this->datetimes(['limit' => 1]);
317
+		return reset($datetimes);
318
+	}
319
+
320
+
321
+	/**
322
+	 * Gets all the datetimes this ticket can be used for attending.
323
+	 * Unless otherwise specified, orders datetimes by start date.
324
+	 *
325
+	 * @param array $query_params
326
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
327
+	 * @return EE_Datetime[]|EE_Base_Class[]
328
+	 * @throws EE_Error
329
+	 * @throws ReflectionException
330
+	 */
331
+	public function datetimes($query_params = [])
332
+	{
333
+		if (! isset($query_params['order_by'])) {
334
+			$query_params['order_by']['DTT_order'] = 'ASC';
335
+		}
336
+		return $this->get_many_related('Datetime', $query_params);
337
+	}
338
+
339
+
340
+	/**
341
+	 * This returns the chronologically last datetime that this ticket is associated with
342
+	 *
343
+	 * @return EE_Datetime
344
+	 * @throws EE_Error
345
+	 * @throws ReflectionException
346
+	 */
347
+	public function last_datetime()
348
+	{
349
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
350
+		return end($datetimes);
351
+	}
352
+
353
+
354
+	/**
355
+	 * This returns the total tickets sold depending on the given parameters.
356
+	 *
357
+	 * @param string $what    Can be one of two options: 'ticket', 'datetime'.
358
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
359
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
360
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
361
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
362
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
363
+	 * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
364
+	 * @return mixed (array|int)          how many tickets have sold
365
+	 * @throws EE_Error
366
+	 * @throws ReflectionException
367
+	 */
368
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
369
+	{
370
+		$total        = 0;
371
+		$tickets_sold = $this->_all_tickets_sold();
372
+		switch ($what) {
373
+			case 'ticket':
374
+				return $tickets_sold['ticket'];
375
+
376
+			case 'datetime':
377
+				if (empty($tickets_sold['datetime'])) {
378
+					return $total;
379
+				}
380
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
381
+					EE_Error::add_error(
382
+						__(
383
+							'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?',
384
+							'event_espresso'
385
+						),
386
+						__FILE__,
387
+						__FUNCTION__,
388
+						__LINE__
389
+					);
390
+					return $total;
391
+				}
392
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
393
+
394
+			default:
395
+				return $total;
396
+		}
397
+	}
398
+
399
+
400
+	/**
401
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
402
+	 *
403
+	 * @return EE_Ticket[]
404
+	 * @throws EE_Error
405
+	 * @throws ReflectionException
406
+	 */
407
+	protected function _all_tickets_sold()
408
+	{
409
+		$datetimes    = $this->get_many_related('Datetime');
410
+		$tickets_sold = [];
411
+		if (! empty($datetimes)) {
412
+			foreach ($datetimes as $datetime) {
413
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
414
+			}
415
+		}
416
+		// Tickets sold
417
+		$tickets_sold['ticket'] = $this->sold();
418
+		return $tickets_sold;
419
+	}
420
+
421
+
422
+	/**
423
+	 * This returns the base price object for the ticket.
424
+	 *
425
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
426
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
427
+	 * @throws EE_Error
428
+	 * @throws ReflectionException
429
+	 */
430
+	public function base_price(bool $return_array = false)
431
+	{
432
+		$base_price = $this->ticket_price_modifiers->getBasePrice();
433
+		if (! empty($base_price)) {
434
+			return $return_array ? $base_price : reset($base_price);
435
+		}
436
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
437
+		return $return_array
438
+			? $this->get_many_related('Price', [$_where])
439
+			: $this->get_first_related('Price', [$_where]);
440
+	}
441
+
442
+
443
+	/**
444
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
445
+	 *
446
+	 * @return EE_Price[]
447
+	 * @throws EE_Error
448
+	 * @throws ReflectionException
449
+	 */
450
+	public function price_modifiers(): array
451
+	{
452
+		$price_modifiers = $this->usesGlobalTaxes()
453
+			? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
454
+			: $this->ticket_price_modifiers ->getAllModifiersForTicket();
455
+		if (! empty($price_modifiers)) {
456
+			return $price_modifiers;
457
+		}
458
+		return $this->prices(
459
+			[
460
+				[
461
+					'Price_Type.PBT_ID' => [
462
+						'NOT IN',
463
+						[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
464
+					]
465
+				]
466
+			]
467
+		);
468
+	}
469
+
470
+
471
+	/**
472
+	 * This returns ONLY the TAX price modifiers for the ticket
473
+	 *
474
+	 * @return EE_Price[]
475
+	 * @throws EE_Error
476
+	 * @throws ReflectionException
477
+	 */
478
+	public function tax_price_modifiers(): array
479
+	{
480
+		$tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
481
+		if (! empty($tax_price_modifiers)) {
482
+			return $tax_price_modifiers;
483
+		}
484
+		return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
485
+	}
486
+
487
+
488
+	/**
489
+	 * Gets all the prices that combine to form the final price of this ticket
490
+	 *
491
+	 * @param array $query_params
492
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
493
+	 * @return EE_Price[]|EE_Base_Class[]
494
+	 * @throws EE_Error
495
+	 * @throws ReflectionException
496
+	 */
497
+	public function prices(array $query_params = []): array
498
+	{
499
+		if (! isset($query_params['order_by'])) {
500
+			$query_params['order_by']['PRC_order'] = 'ASC';
501
+		}
502
+		return $this->get_many_related('Price', $query_params);
503
+	}
504
+
505
+
506
+	/**
507
+	 * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
508
+	 *
509
+	 * @param array $query_params
510
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
511
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
512
+	 * @throws EE_Error
513
+	 * @throws ReflectionException
514
+	 */
515
+	public function datetime_tickets($query_params = [])
516
+	{
517
+		return $this->get_many_related('Datetime_Ticket', $query_params);
518
+	}
519
+
520
+
521
+	/**
522
+	 * Gets all the datetimes from the db ordered by DTT_order
523
+	 *
524
+	 * @param boolean $show_expired
525
+	 * @param boolean $show_deleted
526
+	 * @return EE_Datetime[]
527
+	 * @throws EE_Error
528
+	 * @throws ReflectionException
529
+	 */
530
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
531
+	{
532
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
533
+			$this->ID(),
534
+			$show_expired,
535
+			$show_deleted
536
+		);
537
+	}
538
+
539
+
540
+	/**
541
+	 * Gets ID
542
+	 *
543
+	 * @return int
544
+	 * @throws EE_Error
545
+	 * @throws ReflectionException
546
+	 */
547
+	public function ID()
548
+	{
549
+		return $this->get('TKT_ID');
550
+	}
551
+
552
+
553
+	/**
554
+	 * get the author of the ticket.
555
+	 *
556
+	 * @return int
557
+	 * @throws EE_Error
558
+	 * @throws ReflectionException
559
+	 * @since 4.5.0
560
+	 */
561
+	public function wp_user()
562
+	{
563
+		return $this->get('TKT_wp_user');
564
+	}
565
+
566
+
567
+	/**
568
+	 * Gets the template for the ticket
569
+	 *
570
+	 * @return EE_Ticket_Template|EE_Base_Class
571
+	 * @throws EE_Error
572
+	 * @throws ReflectionException
573
+	 */
574
+	public function template()
575
+	{
576
+		return $this->get_first_related('Ticket_Template');
577
+	}
578
+
579
+
580
+	/**
581
+	 * Simply returns an array of EE_Price objects that are taxes.
582
+	 *
583
+	 * @return EE_Price[]
584
+	 * @throws EE_Error
585
+	 * @throws ReflectionException
586
+	 */
587
+	public function get_ticket_taxes_for_admin(): array
588
+	{
589
+		return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
590
+	}
591
+
592
+
593
+	/**
594
+	 * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
595
+	 * as opposed to having tax price modifiers added directly to each ticket
596
+	 *
597
+	 * @return bool
598
+	 * @throws EE_Error
599
+	 * @throws ReflectionException
600
+	 * @since   $VID:$
601
+	 */
602
+	public function usesGlobalTaxes(): bool
603
+	{
604
+		return $this->taxable();
605
+	}
606
+
607
+
608
+	/**
609
+	 * @return float
610
+	 * @throws EE_Error
611
+	 * @throws ReflectionException
612
+	 */
613
+	public function ticket_price()
614
+	{
615
+		return $this->get('TKT_price');
616
+	}
617
+
618
+
619
+	/**
620
+	 * @return mixed
621
+	 * @throws EE_Error
622
+	 * @throws ReflectionException
623
+	 */
624
+	public function pretty_price()
625
+	{
626
+		return $this->get_pretty('TKT_price');
627
+	}
628
+
629
+
630
+	/**
631
+	 * @return bool
632
+	 * @throws EE_Error
633
+	 * @throws ReflectionException
634
+	 */
635
+	public function is_free()
636
+	{
637
+		return $this->get_ticket_total_with_taxes() === (float) 0;
638
+	}
639
+
640
+
641
+	/**
642
+	 * get_ticket_total_with_taxes
643
+	 *
644
+	 * @param bool $no_cache
645
+	 * @return float
646
+	 * @throws EE_Error
647
+	 * @throws ReflectionException
648
+	 */
649
+	public function get_ticket_total_with_taxes($no_cache = false)
650
+	{
651
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
652
+			$this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
653
+				? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
654
+				: $this->ticket_price();
655
+		}
656
+		return (float) $this->_ticket_total_with_taxes;
657
+	}
658
+
659
+
660
+	/**
661
+	 * @throws EE_Error
662
+	 * @throws ReflectionException
663
+	 */
664
+	public function ensure_TKT_Price_correct()
665
+	{
666
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
667
+		$this->save();
668
+	}
669
+
670
+
671
+	/**
672
+	 * @return float
673
+	 * @throws EE_Error
674
+	 * @throws ReflectionException
675
+	 */
676
+	public function get_ticket_subtotal()
677
+	{
678
+		return EE_Taxes::get_subtotal_for_admin($this);
679
+	}
680
+
681
+
682
+	/**
683
+	 * Returns the total taxes applied to this ticket
684
+	 *
685
+	 * @return float
686
+	 * @throws EE_Error
687
+	 * @throws ReflectionException
688
+	 */
689
+	public function get_ticket_taxes_total_for_admin()
690
+	{
691
+		return EE_Taxes::get_total_taxes_for_admin($this);
692
+	}
693
+
694
+
695
+	/**
696
+	 * Sets name
697
+	 *
698
+	 * @param string $name
699
+	 * @throws EE_Error
700
+	 * @throws ReflectionException
701
+	 */
702
+	public function set_name($name)
703
+	{
704
+		$this->set('TKT_name', $name);
705
+	}
706
+
707
+
708
+	/**
709
+	 * Gets description
710
+	 *
711
+	 * @return string
712
+	 * @throws EE_Error
713
+	 * @throws ReflectionException
714
+	 */
715
+	public function description()
716
+	{
717
+		return $this->get('TKT_description');
718
+	}
719
+
720
+
721
+	/**
722
+	 * Sets description
723
+	 *
724
+	 * @param string $description
725
+	 * @throws EE_Error
726
+	 * @throws ReflectionException
727
+	 */
728
+	public function set_description($description)
729
+	{
730
+		$this->set('TKT_description', $description);
731
+	}
732
+
733
+
734
+	/**
735
+	 * Gets start_date
736
+	 *
737
+	 * @param string $date_format
738
+	 * @param string $time_format
739
+	 * @return string
740
+	 * @throws EE_Error
741
+	 * @throws ReflectionException
742
+	 */
743
+	public function start_date($date_format = '', $time_format = '')
744
+	{
745
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
746
+	}
747
+
748
+
749
+	/**
750
+	 * Sets start_date
751
+	 *
752
+	 * @param string $start_date
753
+	 * @return void
754
+	 * @throws EE_Error
755
+	 * @throws ReflectionException
756
+	 */
757
+	public function set_start_date($start_date)
758
+	{
759
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
760
+	}
761
+
762
+
763
+	/**
764
+	 * Gets end_date
765
+	 *
766
+	 * @param string $date_format
767
+	 * @param string $time_format
768
+	 * @return string
769
+	 * @throws EE_Error
770
+	 * @throws ReflectionException
771
+	 */
772
+	public function end_date($date_format = '', $time_format = '')
773
+	{
774
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
775
+	}
776
+
777
+
778
+	/**
779
+	 * Sets end_date
780
+	 *
781
+	 * @param string $end_date
782
+	 * @return void
783
+	 * @throws EE_Error
784
+	 * @throws ReflectionException
785
+	 */
786
+	public function set_end_date($end_date)
787
+	{
788
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
789
+	}
790
+
791
+
792
+	/**
793
+	 * Sets sell until time
794
+	 *
795
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
796
+	 * @throws EE_Error
797
+	 * @throws ReflectionException
798
+	 * @since 4.5.0
799
+	 */
800
+	public function set_end_time($time)
801
+	{
802
+		$this->_set_time_for($time, 'TKT_end_date');
803
+	}
804
+
805
+
806
+	/**
807
+	 * Sets min
808
+	 *
809
+	 * @param int $min
810
+	 * @return void
811
+	 * @throws EE_Error
812
+	 * @throws ReflectionException
813
+	 */
814
+	public function set_min($min)
815
+	{
816
+		$this->set('TKT_min', $min);
817
+	}
818
+
819
+
820
+	/**
821
+	 * Gets max
822
+	 *
823
+	 * @return int
824
+	 * @throws EE_Error
825
+	 * @throws ReflectionException
826
+	 */
827
+	public function max()
828
+	{
829
+		return $this->get('TKT_max');
830
+	}
831
+
832
+
833
+	/**
834
+	 * Sets max
835
+	 *
836
+	 * @param int $max
837
+	 * @return void
838
+	 * @throws EE_Error
839
+	 * @throws ReflectionException
840
+	 */
841
+	public function set_max($max)
842
+	{
843
+		$this->set('TKT_max', $max);
844
+	}
845
+
846
+
847
+	/**
848
+	 * Sets price
849
+	 *
850
+	 * @param float $price
851
+	 * @return void
852
+	 * @throws EE_Error
853
+	 * @throws ReflectionException
854
+	 */
855
+	public function set_price($price)
856
+	{
857
+		$this->set('TKT_price', $price);
858
+	}
859
+
860
+
861
+	/**
862
+	 * Gets sold
863
+	 *
864
+	 * @return int
865
+	 * @throws EE_Error
866
+	 * @throws ReflectionException
867
+	 */
868
+	public function sold()
869
+	{
870
+		return $this->get_raw('TKT_sold');
871
+	}
872
+
873
+
874
+	/**
875
+	 * Sets sold
876
+	 *
877
+	 * @param int $sold
878
+	 * @return void
879
+	 * @throws EE_Error
880
+	 * @throws ReflectionException
881
+	 */
882
+	public function set_sold($sold)
883
+	{
884
+		// sold can not go below zero
885
+		$sold = max(0, $sold);
886
+		$this->set('TKT_sold', $sold);
887
+	}
888
+
889
+
890
+	/**
891
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
892
+	 * associated datetimes.
893
+	 *
894
+	 * @param int $qty
895
+	 * @return boolean
896
+	 * @throws EE_Error
897
+	 * @throws InvalidArgumentException
898
+	 * @throws InvalidDataTypeException
899
+	 * @throws InvalidInterfaceException
900
+	 * @throws ReflectionException
901
+	 * @since 4.9.80.p
902
+	 */
903
+	public function increaseSold($qty = 1)
904
+	{
905
+		$qty = absint($qty);
906
+		// increment sold and decrement reserved datetime quantities simultaneously
907
+		// don't worry about failures, because they must have already had a spot reserved
908
+		$this->increaseSoldForDatetimes($qty);
909
+		// Increment and decrement ticket quantities simultaneously
910
+		$success = $this->adjustNumericFieldsInDb(
911
+			[
912
+				'TKT_reserved' => $qty * -1,
913
+				'TKT_sold'     => $qty,
914
+			]
915
+		);
916
+		do_action(
917
+			'AHEE__EE_Ticket__increase_sold',
918
+			$this,
919
+			$qty,
920
+			$this->sold(),
921
+			$success
922
+		);
923
+		return $success;
924
+	}
925
+
926
+
927
+	/**
928
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
929
+	 *
930
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
931
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
932
+	 * @param EE_Datetime[] $datetimes
933
+	 * @throws EE_Error
934
+	 * @throws InvalidArgumentException
935
+	 * @throws InvalidDataTypeException
936
+	 * @throws InvalidInterfaceException
937
+	 * @throws ReflectionException
938
+	 * @since 4.9.80.p
939
+	 */
940
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
941
+	{
942
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
943
+		foreach ($datetimes as $datetime) {
944
+			$datetime->increaseSold($qty);
945
+		}
946
+	}
947
+
948
+
949
+	/**
950
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
951
+	 * DB and then updates the model objects.
952
+	 * Does not affect the reserved counts.
953
+	 *
954
+	 * @param int $qty
955
+	 * @return boolean
956
+	 * @throws EE_Error
957
+	 * @throws InvalidArgumentException
958
+	 * @throws InvalidDataTypeException
959
+	 * @throws InvalidInterfaceException
960
+	 * @throws ReflectionException
961
+	 * @since 4.9.80.p
962
+	 */
963
+	public function decreaseSold($qty = 1)
964
+	{
965
+		$qty = absint($qty);
966
+		$this->decreaseSoldForDatetimes($qty);
967
+		$success = $this->adjustNumericFieldsInDb(
968
+			[
969
+				'TKT_sold' => $qty * -1,
970
+			]
971
+		);
972
+		do_action(
973
+			'AHEE__EE_Ticket__decrease_sold',
974
+			$this,
975
+			$qty,
976
+			$this->sold(),
977
+			$success
978
+		);
979
+		return $success;
980
+	}
981
+
982
+
983
+	/**
984
+	 * Decreases sold on related datetimes
985
+	 *
986
+	 * @param int           $qty
987
+	 * @param EE_Datetime[] $datetimes
988
+	 * @return void
989
+	 * @throws EE_Error
990
+	 * @throws InvalidArgumentException
991
+	 * @throws InvalidDataTypeException
992
+	 * @throws InvalidInterfaceException
993
+	 * @throws ReflectionException
994
+	 * @since 4.9.80.p
995
+	 */
996
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
997
+	{
998
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
999
+		if (is_array($datetimes)) {
1000
+			foreach ($datetimes as $datetime) {
1001
+				if ($datetime instanceof EE_Datetime) {
1002
+					$datetime->decreaseSold($qty);
1003
+				}
1004
+			}
1005
+		}
1006
+	}
1007
+
1008
+
1009
+	/**
1010
+	 * Gets qty of reserved tickets
1011
+	 *
1012
+	 * @return int
1013
+	 * @throws EE_Error
1014
+	 * @throws ReflectionException
1015
+	 */
1016
+	public function reserved()
1017
+	{
1018
+		return $this->get_raw('TKT_reserved');
1019
+	}
1020
+
1021
+
1022
+	/**
1023
+	 * Sets reserved
1024
+	 *
1025
+	 * @param int $reserved
1026
+	 * @return void
1027
+	 * @throws EE_Error
1028
+	 * @throws ReflectionException
1029
+	 */
1030
+	public function set_reserved($reserved)
1031
+	{
1032
+		// reserved can not go below zero
1033
+		$reserved = max(0, (int) $reserved);
1034
+		$this->set('TKT_reserved', $reserved);
1035
+	}
1036
+
1037
+
1038
+	/**
1039
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1040
+	 *
1041
+	 * @param int    $qty
1042
+	 * @param string $source
1043
+	 * @return bool whether we successfully reserved the ticket or not.
1044
+	 * @throws EE_Error
1045
+	 * @throws InvalidArgumentException
1046
+	 * @throws ReflectionException
1047
+	 * @throws InvalidDataTypeException
1048
+	 * @throws InvalidInterfaceException
1049
+	 * @since 4.9.80.p
1050
+	 */
1051
+	public function increaseReserved($qty = 1, $source = 'unknown')
1052
+	{
1053
+		$qty = absint($qty);
1054
+		do_action(
1055
+			'AHEE__EE_Ticket__increase_reserved__begin',
1056
+			$this,
1057
+			$qty,
1058
+			$source
1059
+		);
1060
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1061
+		$success                         = false;
1062
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1063
+		if ($datetimes_adjusted_successfully) {
1064
+			$success = $this->incrementFieldConditionallyInDb(
1065
+				'TKT_reserved',
1066
+				'TKT_sold',
1067
+				'TKT_qty',
1068
+				$qty
1069
+			);
1070
+			if (! $success) {
1071
+				// The datetimes were successfully bumped, but not the
1072
+				// ticket. So we need to manually rollback the datetimes.
1073
+				$this->decreaseReservedForDatetimes($qty);
1074
+			}
1075
+		}
1076
+		do_action(
1077
+			'AHEE__EE_Ticket__increase_reserved',
1078
+			$this,
1079
+			$qty,
1080
+			$this->reserved(),
1081
+			$success
1082
+		);
1083
+		return $success;
1084
+	}
1085
+
1086
+
1087
+	/**
1088
+	 * Increases reserved counts on related datetimes
1089
+	 *
1090
+	 * @param int           $qty
1091
+	 * @param EE_Datetime[] $datetimes
1092
+	 * @return boolean indicating success
1093
+	 * @throws EE_Error
1094
+	 * @throws InvalidArgumentException
1095
+	 * @throws InvalidDataTypeException
1096
+	 * @throws InvalidInterfaceException
1097
+	 * @throws ReflectionException
1098
+	 * @since 4.9.80.p
1099
+	 */
1100
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1101
+	{
1102
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1103
+		$datetimes_updated = [];
1104
+		$limit_exceeded    = false;
1105
+		if (is_array($datetimes)) {
1106
+			foreach ($datetimes as $datetime) {
1107
+				if ($datetime instanceof EE_Datetime) {
1108
+					if ($datetime->increaseReserved($qty)) {
1109
+						$datetimes_updated[] = $datetime;
1110
+					} else {
1111
+						$limit_exceeded = true;
1112
+						break;
1113
+					}
1114
+				}
1115
+			}
1116
+			// If somewhere along the way we detected a datetime whose
1117
+			// limit was exceeded, do a manual rollback.
1118
+			if ($limit_exceeded) {
1119
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1120
+				return false;
1121
+			}
1122
+		}
1123
+		return true;
1124
+	}
1125
+
1126
+
1127
+	/**
1128
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1129
+	 *
1130
+	 * @param int    $qty
1131
+	 * @param bool   $adjust_datetimes
1132
+	 * @param string $source
1133
+	 * @return boolean
1134
+	 * @throws EE_Error
1135
+	 * @throws InvalidArgumentException
1136
+	 * @throws ReflectionException
1137
+	 * @throws InvalidDataTypeException
1138
+	 * @throws InvalidInterfaceException
1139
+	 * @since 4.9.80.p
1140
+	 */
1141
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1142
+	{
1143
+		$qty = absint($qty);
1144
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1145
+		if ($adjust_datetimes) {
1146
+			$this->decreaseReservedForDatetimes($qty);
1147
+		}
1148
+		$success = $this->adjustNumericFieldsInDb(
1149
+			[
1150
+				'TKT_reserved' => $qty * -1,
1151
+			]
1152
+		);
1153
+		do_action(
1154
+			'AHEE__EE_Ticket__decrease_reserved',
1155
+			$this,
1156
+			$qty,
1157
+			$this->reserved(),
1158
+			$success
1159
+		);
1160
+		return $success;
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * Decreases the reserved count on the specified datetimes.
1166
+	 *
1167
+	 * @param int           $qty
1168
+	 * @param EE_Datetime[] $datetimes
1169
+	 * @throws EE_Error
1170
+	 * @throws InvalidArgumentException
1171
+	 * @throws ReflectionException
1172
+	 * @throws InvalidDataTypeException
1173
+	 * @throws InvalidInterfaceException
1174
+	 * @since 4.9.80.p
1175
+	 */
1176
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1177
+	{
1178
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1179
+		foreach ($datetimes as $datetime) {
1180
+			if ($datetime instanceof EE_Datetime) {
1181
+				$datetime->decreaseReserved($qty);
1182
+			}
1183
+		}
1184
+	}
1185
+
1186
+
1187
+	/**
1188
+	 * Gets ticket quantity
1189
+	 *
1190
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1191
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1192
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1193
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1194
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1195
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1196
+	 * @return int
1197
+	 * @throws EE_Error
1198
+	 * @throws ReflectionException
1199
+	 */
1200
+	public function qty($context = '')
1201
+	{
1202
+		switch ($context) {
1203
+			case 'reg_limit':
1204
+				return $this->real_quantity_on_ticket();
1205
+			case 'saleable':
1206
+				return $this->real_quantity_on_ticket('saleable');
1207
+			default:
1208
+				return $this->get_raw('TKT_qty');
1209
+		}
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * Gets ticket quantity
1215
+	 *
1216
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1217
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1218
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1219
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1220
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1221
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1222
+	 *                            set to 0 for all related datetimes
1223
+	 * @return int
1224
+	 * @throws EE_Error
1225
+	 * @throws ReflectionException
1226
+	 */
1227
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1228
+	{
1229
+		$raw = $this->get_raw('TKT_qty');
1230
+		// return immediately if it's zero
1231
+		if ($raw === 0) {
1232
+			return $raw;
1233
+		}
1234
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1235
+		// ensure qty doesn't exceed raw value for THIS ticket
1236
+		$qty = min(EE_INF, $raw);
1237
+		// echo "\n . qty: " . $qty . '<br />';
1238
+		// calculate this ticket's total sales and reservations
1239
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1240
+		// echo "\n . sold: " . $this->sold() . '<br />';
1241
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1242
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1243
+		// first we need to calculate the maximum number of tickets available for the datetime
1244
+		// do we want data for one datetime or all of them ?
1245
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1246
+		$datetimes    = $this->datetimes($query_params);
1247
+		if (is_array($datetimes) && ! empty($datetimes)) {
1248
+			foreach ($datetimes as $datetime) {
1249
+				if ($datetime instanceof EE_Datetime) {
1250
+					$datetime->refresh_from_db();
1251
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1252
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1253
+					// initialize with no restrictions for each datetime
1254
+					// but adjust datetime qty based on datetime reg limit
1255
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1256
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1257
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1258
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1259
+					// and reservations for this datetime, that do NOT include sales and reservations
1260
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1261
+					if ($context === 'saleable') {
1262
+						$datetime_qty = max(
1263
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1264
+							0
1265
+						);
1266
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1267
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1268
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1269
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1270
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1271
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1272
+					}
1273
+					$qty = min($datetime_qty, $qty);
1274
+					// echo "\n . . qty: " . $qty . '<br />';
1275
+				}
1276
+			}
1277
+		}
1278
+		// NOW that we know the  maximum number of tickets available for the datetime
1279
+		// we can finally factor in the details for this specific ticket
1280
+		if ($qty > 0 && $context === 'saleable') {
1281
+			// and subtract the sales for THIS ticket
1282
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1283
+			// echo "\n . qty: " . $qty . '<br />';
1284
+		}
1285
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1286
+		return $qty;
1287
+	}
1288
+
1289
+
1290
+	/**
1291
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1292
+	 *
1293
+	 * @param int $qty
1294
+	 * @return void
1295
+	 * @throws EE_Error
1296
+	 * @throws ReflectionException
1297
+	 */
1298
+	public function set_qty($qty)
1299
+	{
1300
+		$datetimes = $this->datetimes();
1301
+		foreach ($datetimes as $datetime) {
1302
+			if ($datetime instanceof EE_Datetime) {
1303
+				$qty = min($qty, $datetime->reg_limit());
1304
+			}
1305
+		}
1306
+		$this->set('TKT_qty', $qty);
1307
+	}
1308
+
1309
+
1310
+	/**
1311
+	 * Gets uses
1312
+	 *
1313
+	 * @return int
1314
+	 * @throws EE_Error
1315
+	 * @throws ReflectionException
1316
+	 */
1317
+	public function uses()
1318
+	{
1319
+		return $this->get('TKT_uses');
1320
+	}
1321
+
1322
+
1323
+	/**
1324
+	 * Sets uses
1325
+	 *
1326
+	 * @param int $uses
1327
+	 * @return void
1328
+	 * @throws EE_Error
1329
+	 * @throws ReflectionException
1330
+	 */
1331
+	public function set_uses($uses)
1332
+	{
1333
+		$this->set('TKT_uses', $uses);
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 * returns whether ticket is required or not.
1339
+	 *
1340
+	 * @return boolean
1341
+	 * @throws EE_Error
1342
+	 * @throws ReflectionException
1343
+	 */
1344
+	public function required()
1345
+	{
1346
+		return $this->get('TKT_required');
1347
+	}
1348
+
1349
+
1350
+	/**
1351
+	 * sets the TKT_required property
1352
+	 *
1353
+	 * @param boolean $required
1354
+	 * @return void
1355
+	 * @throws EE_Error
1356
+	 * @throws ReflectionException
1357
+	 */
1358
+	public function set_required($required)
1359
+	{
1360
+		$this->set('TKT_required', $required);
1361
+	}
1362
+
1363
+
1364
+	/**
1365
+	 * Gets taxable
1366
+	 *
1367
+	 * @return boolean
1368
+	 * @throws EE_Error
1369
+	 * @throws ReflectionException
1370
+	 */
1371
+	public function taxable()
1372
+	{
1373
+		return $this->get('TKT_taxable');
1374
+	}
1375
+
1376
+
1377
+	/**
1378
+	 * Sets taxable
1379
+	 *
1380
+	 * @param boolean $taxable
1381
+	 * @return void
1382
+	 * @throws EE_Error
1383
+	 * @throws ReflectionException
1384
+	 */
1385
+	public function set_taxable($taxable)
1386
+	{
1387
+		$this->set('TKT_taxable', $taxable);
1388
+	}
1389
+
1390
+
1391
+	/**
1392
+	 * Gets is_default
1393
+	 *
1394
+	 * @return boolean
1395
+	 * @throws EE_Error
1396
+	 * @throws ReflectionException
1397
+	 */
1398
+	public function is_default()
1399
+	{
1400
+		return $this->get('TKT_is_default');
1401
+	}
1402
+
1403
+
1404
+	/**
1405
+	 * Sets is_default
1406
+	 *
1407
+	 * @param boolean $is_default
1408
+	 * @return void
1409
+	 * @throws EE_Error
1410
+	 * @throws ReflectionException
1411
+	 */
1412
+	public function set_is_default($is_default)
1413
+	{
1414
+		$this->set('TKT_is_default', $is_default);
1415
+	}
1416
+
1417
+
1418
+	/**
1419
+	 * Gets order
1420
+	 *
1421
+	 * @return int
1422
+	 * @throws EE_Error
1423
+	 * @throws ReflectionException
1424
+	 */
1425
+	public function order()
1426
+	{
1427
+		return $this->get('TKT_order');
1428
+	}
1429
+
1430
+
1431
+	/**
1432
+	 * Sets order
1433
+	 *
1434
+	 * @param int $order
1435
+	 * @return void
1436
+	 * @throws EE_Error
1437
+	 * @throws ReflectionException
1438
+	 */
1439
+	public function set_order($order)
1440
+	{
1441
+		$this->set('TKT_order', $order);
1442
+	}
1443
+
1444
+
1445
+	/**
1446
+	 * Gets row
1447
+	 *
1448
+	 * @return int
1449
+	 * @throws EE_Error
1450
+	 * @throws ReflectionException
1451
+	 */
1452
+	public function row()
1453
+	{
1454
+		return $this->get('TKT_row');
1455
+	}
1456
+
1457
+
1458
+	/**
1459
+	 * Sets row
1460
+	 *
1461
+	 * @param int $row
1462
+	 * @return void
1463
+	 * @throws EE_Error
1464
+	 * @throws ReflectionException
1465
+	 */
1466
+	public function set_row($row)
1467
+	{
1468
+		$this->set('TKT_row', $row);
1469
+	}
1470
+
1471
+
1472
+	/**
1473
+	 * Gets deleted
1474
+	 *
1475
+	 * @return boolean
1476
+	 * @throws EE_Error
1477
+	 * @throws ReflectionException
1478
+	 */
1479
+	public function deleted()
1480
+	{
1481
+		return $this->get('TKT_deleted');
1482
+	}
1483
+
1484
+
1485
+	/**
1486
+	 * Sets deleted
1487
+	 *
1488
+	 * @param boolean $deleted
1489
+	 * @return void
1490
+	 * @throws EE_Error
1491
+	 * @throws ReflectionException
1492
+	 */
1493
+	public function set_deleted($deleted)
1494
+	{
1495
+		$this->set('TKT_deleted', $deleted);
1496
+	}
1497
+
1498
+
1499
+	/**
1500
+	 * Gets parent
1501
+	 *
1502
+	 * @return int
1503
+	 * @throws EE_Error
1504
+	 * @throws ReflectionException
1505
+	 */
1506
+	public function parent_ID()
1507
+	{
1508
+		return $this->get('TKT_parent');
1509
+	}
1510
+
1511
+
1512
+	/**
1513
+	 * Sets parent
1514
+	 *
1515
+	 * @param int $parent
1516
+	 * @return void
1517
+	 * @throws EE_Error
1518
+	 * @throws ReflectionException
1519
+	 */
1520
+	public function set_parent_ID($parent)
1521
+	{
1522
+		$this->set('TKT_parent', $parent);
1523
+	}
1524
+
1525
+
1526
+	/**
1527
+	 * @return boolean
1528
+	 * @throws EE_Error
1529
+	 * @throws InvalidArgumentException
1530
+	 * @throws InvalidDataTypeException
1531
+	 * @throws InvalidInterfaceException
1532
+	 * @throws ReflectionException
1533
+	 */
1534
+	public function reverse_calculate()
1535
+	{
1536
+		return $this->get('TKT_reverse_calculate');
1537
+	}
1538
+
1539
+
1540
+	/**
1541
+	 * @param boolean $reverse_calculate
1542
+	 * @throws EE_Error
1543
+	 * @throws InvalidArgumentException
1544
+	 * @throws InvalidDataTypeException
1545
+	 * @throws InvalidInterfaceException
1546
+	 * @throws ReflectionException
1547
+	 */
1548
+	public function set_reverse_calculate($reverse_calculate)
1549
+	{
1550
+		$this->set('TKT_reverse_calculate', $reverse_calculate);
1551
+	}
1552
+
1553
+
1554
+	/**
1555
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1556
+	 *
1557
+	 * @return string
1558
+	 * @throws EE_Error
1559
+	 * @throws ReflectionException
1560
+	 */
1561
+	public function name_and_info()
1562
+	{
1563
+		$times = [];
1564
+		foreach ($this->datetimes() as $datetime) {
1565
+			$times[] = $datetime->start_date_and_time();
1566
+		}
1567
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1568
+	}
1569
+
1570
+
1571
+	/**
1572
+	 * Gets name
1573
+	 *
1574
+	 * @return string
1575
+	 * @throws EE_Error
1576
+	 * @throws ReflectionException
1577
+	 */
1578
+	public function name()
1579
+	{
1580
+		return $this->get('TKT_name');
1581
+	}
1582
+
1583
+
1584
+	/**
1585
+	 * Gets price
1586
+	 *
1587
+	 * @return float
1588
+	 * @throws EE_Error
1589
+	 * @throws ReflectionException
1590
+	 */
1591
+	public function price()
1592
+	{
1593
+		return $this->get('TKT_price');
1594
+	}
1595
+
1596
+
1597
+	/**
1598
+	 * Gets all the registrations for this ticket
1599
+	 *
1600
+	 * @param array $query_params
1601
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1602
+	 * @return EE_Registration[]|EE_Base_Class[]
1603
+	 * @throws EE_Error
1604
+	 * @throws ReflectionException
1605
+	 */
1606
+	public function registrations($query_params = [])
1607
+	{
1608
+		return $this->get_many_related('Registration', $query_params);
1609
+	}
1610
+
1611
+
1612
+	/**
1613
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1614
+	 *
1615
+	 * @return int
1616
+	 * @throws EE_Error
1617
+	 * @throws ReflectionException
1618
+	 */
1619
+	public function update_tickets_sold()
1620
+	{
1621
+		$count_regs_for_this_ticket = $this->count_registrations(
1622
+			[
1623
+				[
1624
+					'STS_ID'      => EEM_Registration::status_id_approved,
1625
+					'REG_deleted' => 0,
1626
+				],
1627
+			]
1628
+		);
1629
+		$this->set_sold($count_regs_for_this_ticket);
1630
+		$this->save();
1631
+		return $count_regs_for_this_ticket;
1632
+	}
1633
+
1634
+
1635
+	/**
1636
+	 * Counts the registrations for this ticket
1637
+	 *
1638
+	 * @param array $query_params
1639
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1640
+	 * @return int
1641
+	 * @throws EE_Error
1642
+	 * @throws ReflectionException
1643
+	 */
1644
+	public function count_registrations($query_params = [])
1645
+	{
1646
+		return $this->count_related('Registration', $query_params);
1647
+	}
1648
+
1649
+
1650
+	/**
1651
+	 * Implementation for EEI_Has_Icon interface method.
1652
+	 *
1653
+	 * @return string
1654
+	 * @see EEI_Visual_Representation for comments
1655
+	 */
1656
+	public function get_icon()
1657
+	{
1658
+		return '<span class="dashicons dashicons-tickets-alt"/>';
1659
+	}
1660
+
1661
+
1662
+	/**
1663
+	 * Implementation of the EEI_Event_Relation interface method
1664
+	 *
1665
+	 * @return EE_Event
1666
+	 * @throws EE_Error
1667
+	 * @throws UnexpectedEntityException
1668
+	 * @throws ReflectionException
1669
+	 * @see EEI_Event_Relation for comments
1670
+	 */
1671
+	public function get_related_event()
1672
+	{
1673
+		// get one datetime to use for getting the event
1674
+		$datetime = $this->first_datetime();
1675
+		if (! $datetime instanceof EE_Datetime) {
1676
+			throw new UnexpectedEntityException(
1677
+				$datetime,
1678
+				'EE_Datetime',
1679
+				sprintf(
1680
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1681
+					$this->name()
1682
+				)
1683
+			);
1684
+		}
1685
+		$event = $datetime->event();
1686
+		if (! $event instanceof EE_Event) {
1687
+			throw new UnexpectedEntityException(
1688
+				$event,
1689
+				'EE_Event',
1690
+				sprintf(
1691
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1692
+					$this->name()
1693
+				)
1694
+			);
1695
+		}
1696
+		return $event;
1697
+	}
1698
+
1699
+
1700
+	/**
1701
+	 * Implementation of the EEI_Event_Relation interface method
1702
+	 *
1703
+	 * @return string
1704
+	 * @throws UnexpectedEntityException
1705
+	 * @throws EE_Error
1706
+	 * @throws ReflectionException
1707
+	 * @see EEI_Event_Relation for comments
1708
+	 */
1709
+	public function get_event_name()
1710
+	{
1711
+		$event = $this->get_related_event();
1712
+		return $event instanceof EE_Event ? $event->name() : '';
1713
+	}
1714
+
1715
+
1716
+	/**
1717
+	 * Implementation of the EEI_Event_Relation interface method
1718
+	 *
1719
+	 * @return int
1720
+	 * @throws UnexpectedEntityException
1721
+	 * @throws EE_Error
1722
+	 * @throws ReflectionException
1723
+	 * @see EEI_Event_Relation for comments
1724
+	 */
1725
+	public function get_event_ID()
1726
+	{
1727
+		$event = $this->get_related_event();
1728
+		return $event instanceof EE_Event ? $event->ID() : 0;
1729
+	}
1730
+
1731
+
1732
+	/**
1733
+	 * This simply returns whether a ticket can be permanently deleted or not.
1734
+	 * The criteria for determining this is whether the ticket has any related registrations.
1735
+	 * If there are none then it can be permanently deleted.
1736
+	 *
1737
+	 * @return bool
1738
+	 * @throws EE_Error
1739
+	 * @throws ReflectionException
1740
+	 */
1741
+	public function is_permanently_deleteable()
1742
+	{
1743
+		return $this->count_registrations() === 0;
1744
+	}
1745
+
1746
+
1747
+	/**
1748
+	 * @return int
1749
+	 * @throws EE_Error
1750
+	 * @throws ReflectionException
1751
+	 * @since   $VID:$
1752
+	 */
1753
+	public function visibility(): int
1754
+	{
1755
+		return $this->get('TKT_visibility');
1756
+	}
1757
+
1758
+
1759
+	/**
1760
+	 * @return int
1761
+	 * @throws EE_Error
1762
+	 * @throws ReflectionException
1763
+	 * @since   $VID:$
1764
+	 */
1765
+	public function isHidden(): int
1766
+	{
1767
+		return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1768
+	}
1769
+
1770
+
1771
+	/**
1772
+	 * @return int
1773
+	 * @throws EE_Error
1774
+	 * @throws ReflectionException
1775
+	 * @since   $VID:$
1776
+	 */
1777
+	public function isNotHidden(): int
1778
+	{
1779
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1780
+	}
1781
+
1782
+
1783
+	/**
1784
+	 * @return int
1785
+	 * @throws EE_Error
1786
+	 * @throws ReflectionException
1787
+	 * @since   $VID:$
1788
+	 */
1789
+	public function isPublicOnly(): int
1790
+	{
1791
+		return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1792
+	}
1793
+
1794
+
1795
+	/**
1796
+	 * @return int
1797
+	 * @throws EE_Error
1798
+	 * @throws ReflectionException
1799
+	 * @since   $VID:$
1800
+	 */
1801
+	public function isMembersOnly(): int
1802
+	{
1803
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1804
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1805
+	}
1806
+
1807
+
1808
+	/**
1809
+	 * @return int
1810
+	 * @throws EE_Error
1811
+	 * @throws ReflectionException
1812
+	 * @since   $VID:$
1813
+	 */
1814
+	public function isAdminsOnly(): int
1815
+	{
1816
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1817
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1818
+	}
1819
+
1820
+
1821
+	/**
1822
+	 * @return int
1823
+	 * @throws EE_Error
1824
+	 * @throws ReflectionException
1825
+	 * @since   $VID:$
1826
+	 */
1827
+	public function isAdminUiOnly(): int
1828
+	{
1829
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1830
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1831
+	}
1832
+
1833
+
1834
+	/**
1835
+	 * @param int $visibility
1836
+	 * @throws EE_Error
1837
+	 * @throws ReflectionException
1838
+	 * @since   $VID:$
1839
+	 */
1840
+	public function set_visibility(int $visibility)
1841
+	{
1842
+
1843
+		$ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1844
+		$ticket_visibility         = -1;
1845
+		foreach ($ticket_visibility_options as $ticket_visibility_option) {
1846
+			if ($visibility === $ticket_visibility_option) {
1847
+				$ticket_visibility = $visibility;
1848
+			}
1849
+		}
1850
+		if ($ticket_visibility === -1) {
1851
+			throw new DomainException(
1852
+				sprintf(
1853
+					esc_html__(
1854
+						'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 ',
1855
+						'event_espresso'
1856
+					),
1857
+					$visibility,
1858
+					'<br />',
1859
+					var_export($ticket_visibility_options, true)
1860
+				)
1861
+			);
1862
+		}
1863
+		$this->set('TKT_visibility', $ticket_visibility);
1864
+	}
1865
+
1866
+
1867
+	/*******************************************************************
1868 1868
      ***********************  DEPRECATED METHODS  **********************
1869 1869
      *******************************************************************/
1870 1870
 
1871 1871
 
1872
-    /**
1873
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1874
-     * associated datetimes.
1875
-     *
1876
-     * @param int $qty
1877
-     * @return void
1878
-     * @throws EE_Error
1879
-     * @throws InvalidArgumentException
1880
-     * @throws InvalidDataTypeException
1881
-     * @throws InvalidInterfaceException
1882
-     * @throws ReflectionException
1883
-     * @deprecated 4.9.80.p
1884
-     */
1885
-    public function increase_sold($qty = 1)
1886
-    {
1887
-        EE_Error::doing_it_wrong(
1888
-            __FUNCTION__,
1889
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1890
-            '4.9.80.p',
1891
-            '5.0.0.p'
1892
-        );
1893
-        $this->increaseSold($qty);
1894
-    }
1895
-
1896
-
1897
-    /**
1898
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1899
-     *
1900
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1901
-     *                 Negative means to decreases old counts (and increase reserved counts).
1902
-     * @throws EE_Error
1903
-     * @throws InvalidArgumentException
1904
-     * @throws InvalidDataTypeException
1905
-     * @throws InvalidInterfaceException
1906
-     * @throws ReflectionException
1907
-     * @deprecated 4.9.80.p
1908
-     */
1909
-    protected function _increase_sold_for_datetimes($qty)
1910
-    {
1911
-        EE_Error::doing_it_wrong(
1912
-            __FUNCTION__,
1913
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1914
-            '4.9.80.p',
1915
-            '5.0.0.p'
1916
-        );
1917
-        $this->increaseSoldForDatetimes($qty);
1918
-    }
1919
-
1920
-
1921
-    /**
1922
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1923
-     * DB and then updates the model objects.
1924
-     * Does not affect the reserved counts.
1925
-     *
1926
-     * @param int $qty
1927
-     * @return void
1928
-     * @throws EE_Error
1929
-     * @throws InvalidArgumentException
1930
-     * @throws InvalidDataTypeException
1931
-     * @throws InvalidInterfaceException
1932
-     * @throws ReflectionException
1933
-     * @deprecated 4.9.80.p
1934
-     */
1935
-    public function decrease_sold($qty = 1)
1936
-    {
1937
-        EE_Error::doing_it_wrong(
1938
-            __FUNCTION__,
1939
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1940
-            '4.9.80.p',
1941
-            '5.0.0.p'
1942
-        );
1943
-        $this->decreaseSold($qty);
1944
-    }
1945
-
1946
-
1947
-    /**
1948
-     * Decreases sold on related datetimes
1949
-     *
1950
-     * @param int $qty
1951
-     * @return void
1952
-     * @throws EE_Error
1953
-     * @throws InvalidArgumentException
1954
-     * @throws InvalidDataTypeException
1955
-     * @throws InvalidInterfaceException
1956
-     * @throws ReflectionException
1957
-     * @deprecated 4.9.80.p
1958
-     */
1959
-    protected function _decrease_sold_for_datetimes($qty = 1)
1960
-    {
1961
-        EE_Error::doing_it_wrong(
1962
-            __FUNCTION__,
1963
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1964
-            '4.9.80.p',
1965
-            '5.0.0.p'
1966
-        );
1967
-        $this->decreaseSoldForDatetimes($qty);
1968
-    }
1969
-
1970
-
1971
-    /**
1972
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1973
-     *
1974
-     * @param int    $qty
1975
-     * @param string $source
1976
-     * @return bool whether we successfully reserved the ticket or not.
1977
-     * @throws EE_Error
1978
-     * @throws InvalidArgumentException
1979
-     * @throws ReflectionException
1980
-     * @throws InvalidDataTypeException
1981
-     * @throws InvalidInterfaceException
1982
-     * @deprecated 4.9.80.p
1983
-     */
1984
-    public function increase_reserved($qty = 1, $source = 'unknown')
1985
-    {
1986
-        EE_Error::doing_it_wrong(
1987
-            __FUNCTION__,
1988
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1989
-            '4.9.80.p',
1990
-            '5.0.0.p'
1991
-        );
1992
-        return $this->increaseReserved($qty);
1993
-    }
1994
-
1995
-
1996
-    /**
1997
-     * Increases sold on related datetimes
1998
-     *
1999
-     * @param int $qty
2000
-     * @return boolean indicating success
2001
-     * @throws EE_Error
2002
-     * @throws InvalidArgumentException
2003
-     * @throws InvalidDataTypeException
2004
-     * @throws InvalidInterfaceException
2005
-     * @throws ReflectionException
2006
-     * @deprecated 4.9.80.p
2007
-     */
2008
-    protected function _increase_reserved_for_datetimes($qty = 1)
2009
-    {
2010
-        EE_Error::doing_it_wrong(
2011
-            __FUNCTION__,
2012
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2013
-            '4.9.80.p',
2014
-            '5.0.0.p'
2015
-        );
2016
-        return $this->increaseReservedForDatetimes($qty);
2017
-    }
2018
-
2019
-
2020
-    /**
2021
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2022
-     *
2023
-     * @param int    $qty
2024
-     * @param bool   $adjust_datetimes
2025
-     * @param string $source
2026
-     * @return void
2027
-     * @throws EE_Error
2028
-     * @throws InvalidArgumentException
2029
-     * @throws ReflectionException
2030
-     * @throws InvalidDataTypeException
2031
-     * @throws InvalidInterfaceException
2032
-     * @deprecated 4.9.80.p
2033
-     */
2034
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2035
-    {
2036
-        EE_Error::doing_it_wrong(
2037
-            __FUNCTION__,
2038
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2039
-            '4.9.80.p',
2040
-            '5.0.0.p'
2041
-        );
2042
-        $this->decreaseReserved($qty);
2043
-    }
2044
-
2045
-
2046
-    /**
2047
-     * Decreases reserved on related datetimes
2048
-     *
2049
-     * @param int $qty
2050
-     * @return void
2051
-     * @throws EE_Error
2052
-     * @throws InvalidArgumentException
2053
-     * @throws ReflectionException
2054
-     * @throws InvalidDataTypeException
2055
-     * @throws InvalidInterfaceException
2056
-     * @deprecated 4.9.80.p
2057
-     */
2058
-    protected function _decrease_reserved_for_datetimes($qty = 1)
2059
-    {
2060
-        EE_Error::doing_it_wrong(
2061
-            __FUNCTION__,
2062
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2063
-            '4.9.80.p',
2064
-            '5.0.0.p'
2065
-        );
2066
-        $this->decreaseReservedForDatetimes($qty);
2067
-    }
1872
+	/**
1873
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1874
+	 * associated datetimes.
1875
+	 *
1876
+	 * @param int $qty
1877
+	 * @return void
1878
+	 * @throws EE_Error
1879
+	 * @throws InvalidArgumentException
1880
+	 * @throws InvalidDataTypeException
1881
+	 * @throws InvalidInterfaceException
1882
+	 * @throws ReflectionException
1883
+	 * @deprecated 4.9.80.p
1884
+	 */
1885
+	public function increase_sold($qty = 1)
1886
+	{
1887
+		EE_Error::doing_it_wrong(
1888
+			__FUNCTION__,
1889
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1890
+			'4.9.80.p',
1891
+			'5.0.0.p'
1892
+		);
1893
+		$this->increaseSold($qty);
1894
+	}
1895
+
1896
+
1897
+	/**
1898
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1899
+	 *
1900
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1901
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1902
+	 * @throws EE_Error
1903
+	 * @throws InvalidArgumentException
1904
+	 * @throws InvalidDataTypeException
1905
+	 * @throws InvalidInterfaceException
1906
+	 * @throws ReflectionException
1907
+	 * @deprecated 4.9.80.p
1908
+	 */
1909
+	protected function _increase_sold_for_datetimes($qty)
1910
+	{
1911
+		EE_Error::doing_it_wrong(
1912
+			__FUNCTION__,
1913
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1914
+			'4.9.80.p',
1915
+			'5.0.0.p'
1916
+		);
1917
+		$this->increaseSoldForDatetimes($qty);
1918
+	}
1919
+
1920
+
1921
+	/**
1922
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1923
+	 * DB and then updates the model objects.
1924
+	 * Does not affect the reserved counts.
1925
+	 *
1926
+	 * @param int $qty
1927
+	 * @return void
1928
+	 * @throws EE_Error
1929
+	 * @throws InvalidArgumentException
1930
+	 * @throws InvalidDataTypeException
1931
+	 * @throws InvalidInterfaceException
1932
+	 * @throws ReflectionException
1933
+	 * @deprecated 4.9.80.p
1934
+	 */
1935
+	public function decrease_sold($qty = 1)
1936
+	{
1937
+		EE_Error::doing_it_wrong(
1938
+			__FUNCTION__,
1939
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1940
+			'4.9.80.p',
1941
+			'5.0.0.p'
1942
+		);
1943
+		$this->decreaseSold($qty);
1944
+	}
1945
+
1946
+
1947
+	/**
1948
+	 * Decreases sold on related datetimes
1949
+	 *
1950
+	 * @param int $qty
1951
+	 * @return void
1952
+	 * @throws EE_Error
1953
+	 * @throws InvalidArgumentException
1954
+	 * @throws InvalidDataTypeException
1955
+	 * @throws InvalidInterfaceException
1956
+	 * @throws ReflectionException
1957
+	 * @deprecated 4.9.80.p
1958
+	 */
1959
+	protected function _decrease_sold_for_datetimes($qty = 1)
1960
+	{
1961
+		EE_Error::doing_it_wrong(
1962
+			__FUNCTION__,
1963
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1964
+			'4.9.80.p',
1965
+			'5.0.0.p'
1966
+		);
1967
+		$this->decreaseSoldForDatetimes($qty);
1968
+	}
1969
+
1970
+
1971
+	/**
1972
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1973
+	 *
1974
+	 * @param int    $qty
1975
+	 * @param string $source
1976
+	 * @return bool whether we successfully reserved the ticket or not.
1977
+	 * @throws EE_Error
1978
+	 * @throws InvalidArgumentException
1979
+	 * @throws ReflectionException
1980
+	 * @throws InvalidDataTypeException
1981
+	 * @throws InvalidInterfaceException
1982
+	 * @deprecated 4.9.80.p
1983
+	 */
1984
+	public function increase_reserved($qty = 1, $source = 'unknown')
1985
+	{
1986
+		EE_Error::doing_it_wrong(
1987
+			__FUNCTION__,
1988
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1989
+			'4.9.80.p',
1990
+			'5.0.0.p'
1991
+		);
1992
+		return $this->increaseReserved($qty);
1993
+	}
1994
+
1995
+
1996
+	/**
1997
+	 * Increases sold on related datetimes
1998
+	 *
1999
+	 * @param int $qty
2000
+	 * @return boolean indicating success
2001
+	 * @throws EE_Error
2002
+	 * @throws InvalidArgumentException
2003
+	 * @throws InvalidDataTypeException
2004
+	 * @throws InvalidInterfaceException
2005
+	 * @throws ReflectionException
2006
+	 * @deprecated 4.9.80.p
2007
+	 */
2008
+	protected function _increase_reserved_for_datetimes($qty = 1)
2009
+	{
2010
+		EE_Error::doing_it_wrong(
2011
+			__FUNCTION__,
2012
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2013
+			'4.9.80.p',
2014
+			'5.0.0.p'
2015
+		);
2016
+		return $this->increaseReservedForDatetimes($qty);
2017
+	}
2018
+
2019
+
2020
+	/**
2021
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2022
+	 *
2023
+	 * @param int    $qty
2024
+	 * @param bool   $adjust_datetimes
2025
+	 * @param string $source
2026
+	 * @return void
2027
+	 * @throws EE_Error
2028
+	 * @throws InvalidArgumentException
2029
+	 * @throws ReflectionException
2030
+	 * @throws InvalidDataTypeException
2031
+	 * @throws InvalidInterfaceException
2032
+	 * @deprecated 4.9.80.p
2033
+	 */
2034
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2035
+	{
2036
+		EE_Error::doing_it_wrong(
2037
+			__FUNCTION__,
2038
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2039
+			'4.9.80.p',
2040
+			'5.0.0.p'
2041
+		);
2042
+		$this->decreaseReserved($qty);
2043
+	}
2044
+
2045
+
2046
+	/**
2047
+	 * Decreases reserved on related datetimes
2048
+	 *
2049
+	 * @param int $qty
2050
+	 * @return void
2051
+	 * @throws EE_Error
2052
+	 * @throws InvalidArgumentException
2053
+	 * @throws ReflectionException
2054
+	 * @throws InvalidDataTypeException
2055
+	 * @throws InvalidInterfaceException
2056
+	 * @deprecated 4.9.80.p
2057
+	 */
2058
+	protected function _decrease_reserved_for_datetimes($qty = 1)
2059
+	{
2060
+		EE_Error::doing_it_wrong(
2061
+			__FUNCTION__,
2062
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2063
+			'4.9.80.p',
2064
+			'5.0.0.p'
2065
+		);
2066
+		$this->decreaseReservedForDatetimes($qty);
2067
+	}
2068 2068
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 2 patches
Indentation   +1701 added lines, -1701 removed lines patch added patch discarded remove patch
@@ -13,1705 +13,1705 @@
 block discarded – undo
13 13
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
14 14
 {
15 15
 
16
-    /**
17
-     * The length of time in seconds that a lock is applied before being considered expired.
18
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
19
-     */
20
-    const LOCK_EXPIRATION = 2;
21
-
22
-    /**
23
-     * txn status upon initial construction.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $_old_txn_status;
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values          incoming values
32
-     * @param string $timezone                incoming timezone
33
-     *                                        (if not set the timezone set for the website will be used.)
34
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
-     *                                        date_format and the second value is the time format
36
-     * @return EE_Transaction
37
-     * @throws EE_Error
38
-     * @throws InvalidArgumentException
39
-     * @throws InvalidDataTypeException
40
-     * @throws InvalidInterfaceException
41
-     * @throws ReflectionException
42
-     */
43
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
44
-    {
45
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
-        $txn = $has_object
47
-            ? $has_object
48
-            : new self($props_n_values, false, $timezone, $date_formats);
49
-        if (! $has_object) {
50
-            $txn->set_old_txn_status($txn->status_ID());
51
-        }
52
-        return $txn;
53
-    }
54
-
55
-
56
-    /**
57
-     * @param array  $props_n_values  incoming values from the database
58
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
-     *                                the website will be used.
60
-     * @return EE_Transaction
61
-     * @throws EE_Error
62
-     * @throws InvalidArgumentException
63
-     * @throws InvalidDataTypeException
64
-     * @throws InvalidInterfaceException
65
-     * @throws ReflectionException
66
-     */
67
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
68
-    {
69
-        $txn = new self($props_n_values, true, $timezone);
70
-        $txn->set_old_txn_status($txn->status_ID());
71
-        return $txn;
72
-    }
73
-
74
-
75
-    /**
76
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
-     * If that also fails, then an exception is thrown.
79
-     *
80
-     * @throws EE_Error
81
-     * @throws InvalidArgumentException
82
-     * @throws InvalidDataTypeException
83
-     * @throws InvalidInterfaceException
84
-     * @throws ReflectionException
85
-     */
86
-    public function lock()
87
-    {
88
-        // attempt to set lock, but if that fails...
89
-        if (! $this->add_extra_meta('lock', time(), true)) {
90
-            // then attempt to remove the lock in case it is expired
91
-            if ($this->_remove_expired_lock()) {
92
-                // if removal was successful, then try setting lock again
93
-                $this->lock();
94
-            } else {
95
-                // but if the lock can not be removed, then throw an exception
96
-                throw new EE_Error(
97
-                    sprintf(
98
-                        __(
99
-                            'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
100
-                            'event_espresso'
101
-                        ),
102
-                        $this->ID()
103
-                    )
104
-                );
105
-            }
106
-        }
107
-    }
108
-
109
-
110
-    /**
111
-     * removes transaction lock applied in EE_Transaction::lock()
112
-     *
113
-     * @return int
114
-     * @throws EE_Error
115
-     * @throws InvalidArgumentException
116
-     * @throws InvalidDataTypeException
117
-     * @throws InvalidInterfaceException
118
-     * @throws ReflectionException
119
-     */
120
-    public function unlock()
121
-    {
122
-        return $this->delete_extra_meta('lock');
123
-    }
124
-
125
-
126
-    /**
127
-     * Decides whether or not now is the right time to update the transaction.
128
-     * This is useful because we don't always know if it is safe to update the transaction
129
-     * and its related data. why?
130
-     * because it's possible that the transaction is being used in another
131
-     * request and could overwrite anything we save.
132
-     * So we want to only update the txn once we know that won't happen.
133
-     * We also check that the lock isn't expired, and remove it if it is
134
-     *
135
-     * @return boolean
136
-     * @throws EE_Error
137
-     * @throws InvalidArgumentException
138
-     * @throws InvalidDataTypeException
139
-     * @throws InvalidInterfaceException
140
-     * @throws ReflectionException
141
-     */
142
-    public function is_locked()
143
-    {
144
-        // if TXN is not locked, then return false immediately
145
-        if (! $this->_get_lock()) {
146
-            return false;
147
-        }
148
-        // if not, then let's try and remove the lock in case it's expired...
149
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
-        // and a positive number if the lock was removed (ie: number of locks deleted),
151
-        // so we need to return the opposite
152
-        return ! $this->_remove_expired_lock() ? true : false;
153
-    }
154
-
155
-
156
-    /**
157
-     * Gets the meta field indicating that this TXN is locked
158
-     *
159
-     * @return int
160
-     * @throws EE_Error
161
-     * @throws InvalidArgumentException
162
-     * @throws InvalidDataTypeException
163
-     * @throws InvalidInterfaceException
164
-     * @throws ReflectionException
165
-     */
166
-    protected function _get_lock()
167
-    {
168
-        return (int) $this->get_extra_meta('lock', true, 0);
169
-    }
170
-
171
-
172
-    /**
173
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
-     *
175
-     * @return int
176
-     * @throws EE_Error
177
-     * @throws InvalidArgumentException
178
-     * @throws InvalidDataTypeException
179
-     * @throws InvalidInterfaceException
180
-     * @throws ReflectionException
181
-     */
182
-    protected function _remove_expired_lock()
183
-    {
184
-        $locked = $this->_get_lock();
185
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
-            return $this->unlock();
187
-        }
188
-        return 0;
189
-    }
190
-
191
-
192
-    /**
193
-     * Set transaction total
194
-     *
195
-     * @param float $total total value of transaction
196
-     * @throws EE_Error
197
-     * @throws InvalidArgumentException
198
-     * @throws InvalidDataTypeException
199
-     * @throws InvalidInterfaceException
200
-     * @throws ReflectionException
201
-     */
202
-    public function set_total($total = 0.00)
203
-    {
204
-        $this->set('TXN_total', (float) $total);
205
-    }
206
-
207
-
208
-    /**
209
-     * Set Total Amount Paid to Date
210
-     *
211
-     * @param float $total_paid total amount paid to date (sum of all payments)
212
-     * @throws EE_Error
213
-     * @throws InvalidArgumentException
214
-     * @throws InvalidDataTypeException
215
-     * @throws InvalidInterfaceException
216
-     * @throws ReflectionException
217
-     */
218
-    public function set_paid($total_paid = 0.00)
219
-    {
220
-        $this->set('TXN_paid', (float) $total_paid);
221
-    }
222
-
223
-
224
-    /**
225
-     * Set transaction status
226
-     *
227
-     * @param string $status        whether the transaction is open, declined, accepted,
228
-     *                              or any number of custom values that can be set
229
-     * @throws EE_Error
230
-     * @throws InvalidArgumentException
231
-     * @throws InvalidDataTypeException
232
-     * @throws InvalidInterfaceException
233
-     * @throws ReflectionException
234
-     */
235
-    public function set_status($status = '')
236
-    {
237
-        $this->set('STS_ID', $status);
238
-    }
239
-
240
-
241
-    /**
242
-     * Set hash salt
243
-     *
244
-     * @param string $hash_salt required for some payment gateways
245
-     * @throws EE_Error
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidDataTypeException
248
-     * @throws InvalidInterfaceException
249
-     * @throws ReflectionException
250
-     */
251
-    public function set_hash_salt($hash_salt = '')
252
-    {
253
-        $this->set('TXN_hash_salt', $hash_salt);
254
-    }
255
-
256
-
257
-    /**
258
-     * Sets TXN_reg_steps array
259
-     *
260
-     * @param array $txn_reg_steps
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public function set_reg_steps(array $txn_reg_steps)
268
-    {
269
-        $this->set('TXN_reg_steps', $txn_reg_steps);
270
-    }
271
-
272
-
273
-    /**
274
-     * Gets TXN_reg_steps
275
-     *
276
-     * @return array
277
-     * @throws EE_Error
278
-     * @throws InvalidArgumentException
279
-     * @throws InvalidDataTypeException
280
-     * @throws InvalidInterfaceException
281
-     * @throws ReflectionException
282
-     */
283
-    public function reg_steps()
284
-    {
285
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
286
-        return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
287
-    }
288
-
289
-
290
-    /**
291
-     * @return string of transaction's total cost, with currency symbol and decimal
292
-     * @throws EE_Error
293
-     * @throws InvalidArgumentException
294
-     * @throws InvalidDataTypeException
295
-     * @throws InvalidInterfaceException
296
-     * @throws ReflectionException
297
-     */
298
-    public function pretty_total()
299
-    {
300
-        return $this->get_pretty('TXN_total');
301
-    }
302
-
303
-
304
-    /**
305
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
306
-     *
307
-     * @return string
308
-     * @throws EE_Error
309
-     * @throws InvalidArgumentException
310
-     * @throws InvalidDataTypeException
311
-     * @throws InvalidInterfaceException
312
-     * @throws ReflectionException
313
-     */
314
-    public function pretty_paid()
315
-    {
316
-        return $this->get_pretty('TXN_paid');
317
-    }
318
-
319
-
320
-    /**
321
-     * calculate the amount remaining for this transaction and return;
322
-     *
323
-     * @return float amount remaining
324
-     * @throws EE_Error
325
-     * @throws InvalidArgumentException
326
-     * @throws InvalidDataTypeException
327
-     * @throws InvalidInterfaceException
328
-     * @throws ReflectionException
329
-     */
330
-    public function remaining()
331
-    {
332
-        return $this->total() - $this->paid();
333
-    }
334
-
335
-
336
-    /**
337
-     * get Transaction Total
338
-     *
339
-     * @return float
340
-     * @throws EE_Error
341
-     * @throws InvalidArgumentException
342
-     * @throws InvalidDataTypeException
343
-     * @throws InvalidInterfaceException
344
-     * @throws ReflectionException
345
-     */
346
-    public function total()
347
-    {
348
-        return (float) $this->get('TXN_total');
349
-    }
350
-
351
-
352
-    /**
353
-     * get Total Amount Paid to Date
354
-     *
355
-     * @return float
356
-     * @throws EE_Error
357
-     * @throws InvalidArgumentException
358
-     * @throws InvalidDataTypeException
359
-     * @throws InvalidInterfaceException
360
-     * @throws ReflectionException
361
-     */
362
-    public function paid()
363
-    {
364
-        return (float) $this->get('TXN_paid');
365
-    }
366
-
367
-
368
-    /**
369
-     * @return mixed|null
370
-     * @throws EE_Error
371
-     * @throws InvalidArgumentException
372
-     * @throws InvalidDataTypeException
373
-     * @throws InvalidInterfaceException
374
-     * @throws ReflectionException
375
-     */
376
-    public function get_cart_session()
377
-    {
378
-        $session_data = (array) $this->get('TXN_session_data');
379
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
380
-            ? $session_data['cart']
381
-            : null;
382
-    }
383
-
384
-
385
-    /**
386
-     * get Transaction session data
387
-     *
388
-     * @return array|mixed
389
-     * @throws EE_Error
390
-     * @throws InvalidArgumentException
391
-     * @throws InvalidDataTypeException
392
-     * @throws InvalidInterfaceException
393
-     * @throws ReflectionException
394
-     */
395
-    public function session_data()
396
-    {
397
-        $session_data = $this->get('TXN_session_data');
398
-        if (empty($session_data)) {
399
-            $session_data = array(
400
-                'id'            => null,
401
-                'user_id'       => null,
402
-                'ip_address'    => null,
403
-                'user_agent'    => null,
404
-                'init_access'   => null,
405
-                'last_access'   => null,
406
-                'pages_visited' => array(),
407
-            );
408
-        }
409
-        return $session_data;
410
-    }
411
-
412
-
413
-    /**
414
-     * Set session data within the TXN object
415
-     *
416
-     * @param EE_Session|array $session_data
417
-     * @throws EE_Error
418
-     * @throws InvalidArgumentException
419
-     * @throws InvalidDataTypeException
420
-     * @throws InvalidInterfaceException
421
-     * @throws ReflectionException
422
-     */
423
-    public function set_txn_session_data($session_data)
424
-    {
425
-        if ($session_data instanceof EE_Session) {
426
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
427
-        } else {
428
-            $this->set('TXN_session_data', $session_data);
429
-        }
430
-    }
431
-
432
-
433
-    /**
434
-     * get Transaction hash salt
435
-     *
436
-     * @return mixed
437
-     * @throws EE_Error
438
-     * @throws InvalidArgumentException
439
-     * @throws InvalidDataTypeException
440
-     * @throws InvalidInterfaceException
441
-     * @throws ReflectionException
442
-     */
443
-    public function hash_salt_()
444
-    {
445
-        return $this->get('TXN_hash_salt');
446
-    }
447
-
448
-
449
-    /**
450
-     * Returns the transaction datetime as either:
451
-     *            - unix timestamp format ($format = false, $gmt = true)
452
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
453
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
454
-     *              set timezone in this class differs from what the timezone is on the blog.
455
-     *            - formatted date string including the UTC (timezone) offset (default).
456
-     *
457
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
458
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
459
-     *                          or no UTC offset applied
460
-     * @return string | int
461
-     * @throws EE_Error
462
-     * @throws InvalidArgumentException
463
-     * @throws InvalidDataTypeException
464
-     * @throws InvalidInterfaceException
465
-     * @throws ReflectionException
466
-     */
467
-    public function datetime($format = false, $gmt = false)
468
-    {
469
-        if ($format) {
470
-            return $this->get_pretty('TXN_timestamp');
471
-        }
472
-        if ($gmt) {
473
-            return $this->get_raw('TXN_timestamp');
474
-        }
475
-        return $this->get('TXN_timestamp');
476
-    }
477
-
478
-
479
-    /**
480
-     * Gets registrations on this transaction
481
-     *
482
-     * @param array   $query_params array of query parameters
483
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
484
-     * @return EE_Base_Class[]|EE_Registration[]
485
-     * @throws EE_Error
486
-     * @throws InvalidArgumentException
487
-     * @throws InvalidDataTypeException
488
-     * @throws InvalidInterfaceException
489
-     * @throws ReflectionException
490
-     */
491
-    public function registrations($query_params = array(), $get_cached = false)
492
-    {
493
-        $query_params = (empty($query_params) || ! is_array($query_params))
494
-            ? array(
495
-                'order_by' => array(
496
-                    'Event.EVT_name'     => 'ASC',
497
-                    'Attendee.ATT_lname' => 'ASC',
498
-                    'Attendee.ATT_fname' => 'ASC',
499
-                ),
500
-            )
501
-            : $query_params;
502
-        $query_params = $get_cached ? array() : $query_params;
503
-        return $this->get_many_related('Registration', $query_params);
504
-    }
505
-
506
-
507
-    /**
508
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
509
-     * function for getting attendees and how many registrations they each have for an event)
510
-     *
511
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
512
-     * @throws EE_Error
513
-     * @throws InvalidArgumentException
514
-     * @throws InvalidDataTypeException
515
-     * @throws InvalidInterfaceException
516
-     * @throws ReflectionException
517
-     */
518
-    public function attendees()
519
-    {
520
-        return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
521
-    }
522
-
523
-
524
-    /**
525
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
526
-     *
527
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
528
-     * @return EE_Base_Class[]|EE_Payment[]
529
-     * @throws EE_Error
530
-     * @throws InvalidArgumentException
531
-     * @throws InvalidDataTypeException
532
-     * @throws InvalidInterfaceException
533
-     * @throws ReflectionException
534
-     */
535
-    public function payments($query_params = array())
536
-    {
537
-        return $this->get_many_related('Payment', $query_params);
538
-    }
539
-
540
-
541
-    /**
542
-     * gets only approved payments for this transaction
543
-     *
544
-     * @return EE_Base_Class[]|EE_Payment[]
545
-     * @throws EE_Error
546
-     * @throws InvalidArgumentException
547
-     * @throws ReflectionException
548
-     * @throws InvalidDataTypeException
549
-     * @throws InvalidInterfaceException
550
-     */
551
-    public function approved_payments()
552
-    {
553
-        EE_Registry::instance()->load_model('Payment');
554
-        return $this->get_many_related(
555
-            'Payment',
556
-            array(
557
-                array('STS_ID' => EEM_Payment::status_id_approved),
558
-                'order_by' => array('PAY_timestamp' => 'DESC'),
559
-            )
560
-        );
561
-    }
562
-
563
-
564
-    /**
565
-     * Gets all payments which have not been approved
566
-     *
567
-     * @return EE_Base_Class[]|EEI_Payment[]
568
-     * @throws EE_Error if a model is misconfigured somehow
569
-     * @throws InvalidArgumentException
570
-     * @throws InvalidDataTypeException
571
-     * @throws InvalidInterfaceException
572
-     * @throws ReflectionException
573
-     */
574
-    public function pending_payments()
575
-    {
576
-        return $this->get_many_related(
577
-            'Payment',
578
-            array(
579
-                array(
580
-                    'STS_ID' => EEM_Payment::status_id_pending,
581
-                ),
582
-                'order_by' => array(
583
-                    'PAY_timestamp' => 'DESC',
584
-                ),
585
-            )
586
-        );
587
-    }
588
-
589
-
590
-    /**
591
-     * echoes $this->pretty_status()
592
-     *
593
-     * @param bool $show_icons
594
-     * @throws EE_Error
595
-     * @throws InvalidArgumentException
596
-     * @throws InvalidDataTypeException
597
-     * @throws InvalidInterfaceException
598
-     * @throws ReflectionException
599
-     */
600
-    public function e_pretty_status($show_icons = false)
601
-    {
602
-        echo $this->pretty_status($show_icons);
603
-    }
604
-
605
-
606
-    /**
607
-     * returns a pretty version of the status, good for displaying to users
608
-     *
609
-     * @param bool $show_icons
610
-     * @return string
611
-     * @throws EE_Error
612
-     * @throws InvalidArgumentException
613
-     * @throws InvalidDataTypeException
614
-     * @throws InvalidInterfaceException
615
-     * @throws ReflectionException
616
-     */
617
-    public function pretty_status($show_icons = false)
618
-    {
619
-        $status = EEM_Status::instance()->localized_status(
620
-            array($this->status_ID() => __('unknown', 'event_espresso')),
621
-            false,
622
-            'sentence'
623
-        );
624
-        $icon = '';
625
-        switch ($this->status_ID()) {
626
-            case EEM_Transaction::complete_status_code:
627
-                $icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
628
-                break;
629
-            case EEM_Transaction::incomplete_status_code:
630
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
631
-                    : '';
632
-                break;
633
-            case EEM_Transaction::abandoned_status_code:
634
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
635
-                break;
636
-            case EEM_Transaction::failed_status_code:
637
-                $icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
638
-                break;
639
-            case EEM_Transaction::overpaid_status_code:
640
-                $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
641
-                break;
642
-        }
643
-        return $icon . $status[ $this->status_ID() ];
644
-    }
645
-
646
-
647
-    /**
648
-     * get Transaction Status
649
-     *
650
-     * @return mixed
651
-     * @throws EE_Error
652
-     * @throws InvalidArgumentException
653
-     * @throws InvalidDataTypeException
654
-     * @throws InvalidInterfaceException
655
-     * @throws ReflectionException
656
-     */
657
-    public function status_ID()
658
-    {
659
-        return $this->get('STS_ID');
660
-    }
661
-
662
-
663
-    /**
664
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
665
-     *
666
-     * @return boolean
667
-     * @throws EE_Error
668
-     * @throws InvalidArgumentException
669
-     * @throws InvalidDataTypeException
670
-     * @throws InvalidInterfaceException
671
-     * @throws ReflectionException
672
-     */
673
-    public function is_free()
674
-    {
675
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
676
-    }
677
-
678
-
679
-    /**
680
-     * Returns whether this transaction is complete
681
-     * Useful in templates and other logic for deciding if we should ask for another payment...
682
-     *
683
-     * @return boolean
684
-     * @throws EE_Error
685
-     * @throws InvalidArgumentException
686
-     * @throws InvalidDataTypeException
687
-     * @throws InvalidInterfaceException
688
-     * @throws ReflectionException
689
-     */
690
-    public function is_completed()
691
-    {
692
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
693
-    }
694
-
695
-
696
-    /**
697
-     * Returns whether this transaction is incomplete
698
-     * Useful in templates and other logic for deciding if we should ask for another payment...
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_incomplete()
708
-    {
709
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
710
-    }
711
-
712
-
713
-    /**
714
-     * Returns whether this transaction is overpaid
715
-     * Useful in templates and other logic for deciding if monies need to be refunded
716
-     *
717
-     * @return boolean
718
-     * @throws EE_Error
719
-     * @throws InvalidArgumentException
720
-     * @throws InvalidDataTypeException
721
-     * @throws InvalidInterfaceException
722
-     * @throws ReflectionException
723
-     */
724
-    public function is_overpaid()
725
-    {
726
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
727
-    }
728
-
729
-
730
-    /**
731
-     * Returns whether this transaction was abandoned
732
-     * meaning that the transaction/registration process was somehow interrupted and never completed
733
-     * but that contact information exists for at least one registrant
734
-     *
735
-     * @return boolean
736
-     * @throws EE_Error
737
-     * @throws InvalidArgumentException
738
-     * @throws InvalidDataTypeException
739
-     * @throws InvalidInterfaceException
740
-     * @throws ReflectionException
741
-     */
742
-    public function is_abandoned()
743
-    {
744
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
745
-    }
746
-
747
-
748
-    /**
749
-     * Returns whether this transaction failed
750
-     * meaning that the transaction/registration process was somehow interrupted and never completed
751
-     * and that NO contact information exists for any registrants
752
-     *
753
-     * @return boolean
754
-     * @throws EE_Error
755
-     * @throws InvalidArgumentException
756
-     * @throws InvalidDataTypeException
757
-     * @throws InvalidInterfaceException
758
-     * @throws ReflectionException
759
-     */
760
-    public function failed()
761
-    {
762
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
763
-    }
764
-
765
-
766
-    /**
767
-     * This returns the url for the invoice of this transaction
768
-     *
769
-     * @param string $type 'html' or 'pdf' (default is pdf)
770
-     * @return string
771
-     * @throws EE_Error
772
-     * @throws InvalidArgumentException
773
-     * @throws InvalidDataTypeException
774
-     * @throws InvalidInterfaceException
775
-     * @throws ReflectionException
776
-     */
777
-    public function invoice_url($type = 'html')
778
-    {
779
-        $REG = $this->primary_registration();
780
-        if (! $REG instanceof EE_Registration) {
781
-            return '';
782
-        }
783
-        return $REG->invoice_url($type);
784
-    }
785
-
786
-
787
-    /**
788
-     * Gets the primary registration only
789
-     *
790
-     * @return EE_Base_Class|EE_Registration
791
-     * @throws EE_Error
792
-     * @throws InvalidArgumentException
793
-     * @throws InvalidDataTypeException
794
-     * @throws InvalidInterfaceException
795
-     * @throws ReflectionException
796
-     */
797
-    public function primary_registration()
798
-    {
799
-        $registrations = (array) $this->get_many_related(
800
-            'Registration',
801
-            array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
802
-        );
803
-        foreach ($registrations as $registration) {
804
-            // valid registration that is NOT cancelled or declined ?
805
-            if (
806
-                $registration instanceof EE_Registration
807
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
808
-            ) {
809
-                return $registration;
810
-            }
811
-        }
812
-        // nothing valid found, so just return first thing from array of results
813
-        return reset($registrations);
814
-    }
815
-
816
-
817
-    /**
818
-     * Gets the URL for viewing the receipt
819
-     *
820
-     * @param string $type 'pdf' or 'html' (default is 'html')
821
-     * @return string
822
-     * @throws EE_Error
823
-     * @throws InvalidArgumentException
824
-     * @throws InvalidDataTypeException
825
-     * @throws InvalidInterfaceException
826
-     * @throws ReflectionException
827
-     */
828
-    public function receipt_url($type = 'html')
829
-    {
830
-        $REG = $this->primary_registration();
831
-        if (! $REG instanceof EE_Registration) {
832
-            return '';
833
-        }
834
-        return $REG->receipt_url($type);
835
-    }
836
-
837
-
838
-    /**
839
-     * Gets the URL of the thank you page with this registration REG_url_link added as
840
-     * a query parameter
841
-     *
842
-     * @return string
843
-     * @throws EE_Error
844
-     * @throws InvalidArgumentException
845
-     * @throws InvalidDataTypeException
846
-     * @throws InvalidInterfaceException
847
-     * @throws ReflectionException
848
-     */
849
-    public function payment_overview_url()
850
-    {
851
-        $primary_registration = $this->primary_registration();
852
-        return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
853
-    }
854
-
855
-
856
-    /**
857
-     * @return string
858
-     * @throws EE_Error
859
-     * @throws InvalidArgumentException
860
-     * @throws InvalidDataTypeException
861
-     * @throws InvalidInterfaceException
862
-     * @throws ReflectionException
863
-     */
864
-    public function gateway_response_on_transaction()
865
-    {
866
-        $payment = $this->get_first_related('Payment');
867
-        return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
868
-    }
869
-
870
-
871
-    /**
872
-     * Get the status object of this object
873
-     *
874
-     * @return EE_Base_Class|EE_Status
875
-     * @throws EE_Error
876
-     * @throws InvalidArgumentException
877
-     * @throws InvalidDataTypeException
878
-     * @throws InvalidInterfaceException
879
-     * @throws ReflectionException
880
-     */
881
-    public function status_obj()
882
-    {
883
-        return $this->get_first_related('Status');
884
-    }
885
-
886
-
887
-    /**
888
-     * Gets all the extra meta info on this payment
889
-     *
890
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
891
-     * @return EE_Base_Class[]|EE_Extra_Meta
892
-     * @throws EE_Error
893
-     * @throws InvalidArgumentException
894
-     * @throws InvalidDataTypeException
895
-     * @throws InvalidInterfaceException
896
-     * @throws ReflectionException
897
-     */
898
-    public function extra_meta($query_params = array())
899
-    {
900
-        return $this->get_many_related('Extra_Meta', $query_params);
901
-    }
902
-
903
-
904
-    /**
905
-     * Wrapper for _add_relation_to
906
-     *
907
-     * @param EE_Registration $registration
908
-     * @return EE_Base_Class the relation was added to
909
-     * @throws EE_Error
910
-     * @throws InvalidArgumentException
911
-     * @throws InvalidDataTypeException
912
-     * @throws InvalidInterfaceException
913
-     * @throws ReflectionException
914
-     */
915
-    public function add_registration(EE_Registration $registration)
916
-    {
917
-        return $this->_add_relation_to($registration, 'Registration');
918
-    }
919
-
920
-
921
-    /**
922
-     * Removes the given registration from being related (even before saving this transaction).
923
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
924
-     *
925
-     * @param int $registration_or_id
926
-     * @return EE_Base_Class that was removed from being related
927
-     * @throws EE_Error
928
-     * @throws InvalidArgumentException
929
-     * @throws InvalidDataTypeException
930
-     * @throws InvalidInterfaceException
931
-     * @throws ReflectionException
932
-     */
933
-    public function remove_registration_with_id($registration_or_id)
934
-    {
935
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
936
-    }
937
-
938
-
939
-    /**
940
-     * Gets all the line items which are for ACTUAL items
941
-     *
942
-     * @return EE_Line_Item[]
943
-     * @throws EE_Error
944
-     * @throws InvalidArgumentException
945
-     * @throws InvalidDataTypeException
946
-     * @throws InvalidInterfaceException
947
-     * @throws ReflectionException
948
-     */
949
-    public function items_purchased()
950
-    {
951
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
952
-    }
953
-
954
-
955
-    /**
956
-     * Wrapper for _add_relation_to
957
-     *
958
-     * @param EE_Line_Item $line_item
959
-     * @return EE_Base_Class the relation was added to
960
-     * @throws EE_Error
961
-     * @throws InvalidArgumentException
962
-     * @throws InvalidDataTypeException
963
-     * @throws InvalidInterfaceException
964
-     * @throws ReflectionException
965
-     */
966
-    public function add_line_item(EE_Line_Item $line_item)
967
-    {
968
-        return $this->_add_relation_to($line_item, 'Line_Item');
969
-    }
970
-
971
-
972
-    /**
973
-     * Gets ALL the line items related to this transaction (unstructured)
974
-     *
975
-     * @param array $query_params
976
-     * @return EE_Base_Class[]|EE_Line_Item[]
977
-     * @throws EE_Error
978
-     * @throws InvalidArgumentException
979
-     * @throws InvalidDataTypeException
980
-     * @throws InvalidInterfaceException
981
-     * @throws ReflectionException
982
-     */
983
-    public function line_items($query_params = array())
984
-    {
985
-        return $this->get_many_related('Line_Item', $query_params);
986
-    }
987
-
988
-
989
-    /**
990
-     * Gets all the line items which are taxes on the total
991
-     *
992
-     * @return EE_Line_Item[]
993
-     * @throws EE_Error
994
-     * @throws InvalidArgumentException
995
-     * @throws InvalidDataTypeException
996
-     * @throws InvalidInterfaceException
997
-     * @throws ReflectionException
998
-     */
999
-    public function tax_items()
1000
-    {
1001
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * Gets the total line item (which is a parent of all other related line items,
1007
-     * meaning it takes them all into account on its total)
1008
-     *
1009
-     * @param bool $create_if_not_found
1010
-     * @return EE_Line_Item|null
1011
-     * @throws EE_Error
1012
-     * @throws InvalidArgumentException
1013
-     * @throws InvalidDataTypeException
1014
-     * @throws InvalidInterfaceException
1015
-     * @throws ReflectionException
1016
-     */
1017
-    public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1018
-    {
1019
-        $item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1020
-        if ($item instanceof EE_Line_Item) {
1021
-            return $item;
1022
-        }
1023
-        return $create_if_not_found ? EEH_Line_Item::create_total_line_item($this) : null;
1024
-    }
1025
-
1026
-
1027
-    /**
1028
-     * Returns the total amount of tax on this transaction
1029
-     * (assumes there's only one tax subtotal line item)
1030
-     *
1031
-     * @return float
1032
-     * @throws EE_Error
1033
-     * @throws InvalidArgumentException
1034
-     * @throws InvalidDataTypeException
1035
-     * @throws InvalidInterfaceException
1036
-     * @throws ReflectionException
1037
-     */
1038
-    public function tax_total()
1039
-    {
1040
-        $tax_line_item = $this->tax_total_line_item();
1041
-        if ($tax_line_item) {
1042
-            return (float) $tax_line_item->total();
1043
-        }
1044
-        return (float) 0;
1045
-    }
1046
-
1047
-
1048
-    /**
1049
-     * Gets the tax subtotal line item (assumes there's only one)
1050
-     *
1051
-     * @return EE_Line_Item
1052
-     * @throws EE_Error
1053
-     * @throws InvalidArgumentException
1054
-     * @throws InvalidDataTypeException
1055
-     * @throws InvalidInterfaceException
1056
-     * @throws ReflectionException
1057
-     */
1058
-    public function tax_total_line_item()
1059
-    {
1060
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1061
-    }
1062
-
1063
-
1064
-    /**
1065
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1066
-     *
1067
-     * @return EE_Form_Section_Proper
1068
-     * @throws EE_Error
1069
-     * @throws InvalidArgumentException
1070
-     * @throws InvalidDataTypeException
1071
-     * @throws InvalidInterfaceException
1072
-     * @throws ReflectionException
1073
-     */
1074
-    public function billing_info()
1075
-    {
1076
-        $payment_method = $this->payment_method();
1077
-        if (! $payment_method) {
1078
-            EE_Error::add_error(
1079
-                __(
1080
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
1081
-                    'event_espresso'
1082
-                ),
1083
-                __FILE__,
1084
-                __FUNCTION__,
1085
-                __LINE__
1086
-            );
1087
-            return null;
1088
-        }
1089
-        $primary_reg = $this->primary_registration();
1090
-        if (! $primary_reg) {
1091
-            EE_Error::add_error(
1092
-                __(
1093
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1094
-                    'event_espresso'
1095
-                ),
1096
-                __FILE__,
1097
-                __FUNCTION__,
1098
-                __LINE__
1099
-            );
1100
-            return null;
1101
-        }
1102
-        $attendee = $primary_reg->attendee();
1103
-        if (! $attendee) {
1104
-            EE_Error::add_error(
1105
-                __(
1106
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1107
-                    'event_espresso'
1108
-                ),
1109
-                __FILE__,
1110
-                __FUNCTION__,
1111
-                __LINE__
1112
-            );
1113
-            return null;
1114
-        }
1115
-        return $attendee->billing_info_for_payment_method($payment_method);
1116
-    }
1117
-
1118
-
1119
-    /**
1120
-     * Gets PMD_ID
1121
-     *
1122
-     * @return int
1123
-     * @throws EE_Error
1124
-     * @throws InvalidArgumentException
1125
-     * @throws InvalidDataTypeException
1126
-     * @throws InvalidInterfaceException
1127
-     * @throws ReflectionException
1128
-     */
1129
-    public function payment_method_ID()
1130
-    {
1131
-        return $this->get('PMD_ID');
1132
-    }
1133
-
1134
-
1135
-    /**
1136
-     * Sets PMD_ID
1137
-     *
1138
-     * @param int $PMD_ID
1139
-     * @throws EE_Error
1140
-     * @throws InvalidArgumentException
1141
-     * @throws InvalidDataTypeException
1142
-     * @throws InvalidInterfaceException
1143
-     * @throws ReflectionException
1144
-     */
1145
-    public function set_payment_method_ID($PMD_ID)
1146
-    {
1147
-        $this->set('PMD_ID', $PMD_ID);
1148
-    }
1149
-
1150
-
1151
-    /**
1152
-     * Gets the last-used payment method on this transaction
1153
-     * (we COULD just use the last-made payment, but some payment methods, namely
1154
-     * offline ones, dont' create payments)
1155
-     *
1156
-     * @return EE_Payment_Method
1157
-     * @throws EE_Error
1158
-     * @throws InvalidArgumentException
1159
-     * @throws InvalidDataTypeException
1160
-     * @throws InvalidInterfaceException
1161
-     * @throws ReflectionException
1162
-     */
1163
-    public function payment_method()
1164
-    {
1165
-        $pm = $this->get_first_related('Payment_Method');
1166
-        if ($pm instanceof EE_Payment_Method) {
1167
-            return $pm;
1168
-        }
1169
-        $last_payment = $this->last_payment();
1170
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1171
-            return $last_payment->payment_method();
1172
-        }
1173
-        return null;
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * Gets the last payment made
1179
-     *
1180
-     * @return EE_Base_Class|EE_Payment
1181
-     * @throws EE_Error
1182
-     * @throws InvalidArgumentException
1183
-     * @throws InvalidDataTypeException
1184
-     * @throws InvalidInterfaceException
1185
-     * @throws ReflectionException
1186
-     */
1187
-    public function last_payment()
1188
-    {
1189
-        return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1190
-    }
1191
-
1192
-
1193
-    /**
1194
-     * Gets all the line items which are unrelated to tickets on this transaction
1195
-     *
1196
-     * @return EE_Line_Item[]
1197
-     * @throws EE_Error
1198
-     * @throws InvalidArgumentException
1199
-     * @throws InvalidDataTypeException
1200
-     * @throws InvalidInterfaceException
1201
-     * @throws ReflectionException
1202
-     */
1203
-    public function non_ticket_line_items()
1204
-    {
1205
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1206
-    }
1207
-
1208
-
1209
-    /**
1210
-     * possibly toggles TXN status
1211
-     *
1212
-     * @param  boolean $update whether to save the TXN
1213
-     * @return bool whether the TXN was saved
1214
-     * @throws EE_Error
1215
-     * @throws InvalidArgumentException
1216
-     * @throws InvalidDataTypeException
1217
-     * @throws InvalidInterfaceException
1218
-     * @throws ReflectionException
1219
-     * @throws RuntimeException
1220
-     */
1221
-    public function update_status_based_on_total_paid($update = true)
1222
-    {
1223
-        // set transaction status based on comparison of TXN_paid vs TXN_total
1224
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1225
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
1226
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1227
-            $new_txn_status = EEM_Transaction::complete_status_code;
1228
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1229
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
1230
-        } else {
1231
-            throw new RuntimeException(
1232
-                __('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1233
-            );
1234
-        }
1235
-        if ($new_txn_status !== $this->status_ID()) {
1236
-            $this->set_status($new_txn_status);
1237
-            if ($update) {
1238
-                return $this->save() ? true : false;
1239
-            }
1240
-        }
1241
-        return false;
1242
-    }
1243
-
1244
-
1245
-    /**
1246
-     * Updates the transaction's status and total_paid based on all the payments
1247
-     * that apply to it
1248
-     *
1249
-     * @deprecated
1250
-     * @return array|bool
1251
-     * @throws EE_Error
1252
-     * @throws InvalidArgumentException
1253
-     * @throws ReflectionException
1254
-     * @throws InvalidDataTypeException
1255
-     * @throws InvalidInterfaceException
1256
-     */
1257
-    public function update_based_on_payments()
1258
-    {
1259
-        EE_Error::doing_it_wrong(
1260
-            __CLASS__ . '::' . __FUNCTION__,
1261
-            sprintf(
1262
-                __('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1263
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1264
-            ),
1265
-            '4.6.0'
1266
-        );
1267
-        /** @type EE_Transaction_Processor $transaction_processor */
1268
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1269
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1270
-    }
1271
-
1272
-
1273
-    /**
1274
-     * @return string
1275
-     */
1276
-    public function old_txn_status()
1277
-    {
1278
-        return $this->_old_txn_status;
1279
-    }
1280
-
1281
-
1282
-    /**
1283
-     * @param string $old_txn_status
1284
-     */
1285
-    public function set_old_txn_status($old_txn_status)
1286
-    {
1287
-        // only set the first time
1288
-        if ($this->_old_txn_status === null) {
1289
-            $this->_old_txn_status = $old_txn_status;
1290
-        }
1291
-    }
1292
-
1293
-
1294
-    /**
1295
-     * reg_status_updated
1296
-     *
1297
-     * @return bool
1298
-     * @throws EE_Error
1299
-     * @throws InvalidArgumentException
1300
-     * @throws InvalidDataTypeException
1301
-     * @throws InvalidInterfaceException
1302
-     * @throws ReflectionException
1303
-     */
1304
-    public function txn_status_updated()
1305
-    {
1306
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1307
-    }
1308
-
1309
-
1310
-    /**
1311
-     * _reg_steps_completed
1312
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1313
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1314
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1315
-     *
1316
-     * @param string $reg_step_slug
1317
-     * @param bool   $check_all
1318
-     * @return bool|int
1319
-     * @throws EE_Error
1320
-     * @throws InvalidArgumentException
1321
-     * @throws InvalidDataTypeException
1322
-     * @throws InvalidInterfaceException
1323
-     * @throws ReflectionException
1324
-     */
1325
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1326
-    {
1327
-        $reg_steps = $this->reg_steps();
1328
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1329
-            return false;
1330
-        }
1331
-        // loop thru reg steps array)
1332
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1333
-            // if NOT checking ALL steps (only checking one step)
1334
-            if (! $check_all) {
1335
-                // and this is the one
1336
-                if ($slug === $reg_step_slug) {
1337
-                    return $reg_step_completed;
1338
-                }
1339
-                // skip to next reg step in loop
1340
-                continue;
1341
-            }
1342
-            // $check_all must be true, else we would never have gotten to this point
1343
-            if ($slug === $reg_step_slug) {
1344
-                // if we reach this point, then we are testing either:
1345
-                // all_reg_steps_completed_except() or
1346
-                // all_reg_steps_completed_except_final_step(),
1347
-                // and since this is the reg step EXCEPTION being tested
1348
-                // we want to return true (yes true) if this reg step is NOT completed
1349
-                // ie: "is everything completed except the final step?"
1350
-                // "that is correct... the final step is not completed, but all others are."
1351
-                return $reg_step_completed !== true;
1352
-            }
1353
-            if ($reg_step_completed !== true) {
1354
-                // if any reg step is NOT completed, then ALL steps are not completed
1355
-                return false;
1356
-            }
1357
-        }
1358
-        return true;
1359
-    }
1360
-
1361
-
1362
-    /**
1363
-     * all_reg_steps_completed
1364
-     * returns:
1365
-     *    true if ALL reg steps have been marked as completed
1366
-     *        or false if any step is not completed
1367
-     *
1368
-     * @return bool
1369
-     * @throws EE_Error
1370
-     * @throws InvalidArgumentException
1371
-     * @throws InvalidDataTypeException
1372
-     * @throws InvalidInterfaceException
1373
-     * @throws ReflectionException
1374
-     */
1375
-    public function all_reg_steps_completed()
1376
-    {
1377
-        return $this->_reg_steps_completed();
1378
-    }
1379
-
1380
-
1381
-    /**
1382
-     * all_reg_steps_completed_except
1383
-     * returns:
1384
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1385
-     *        or false if any other step is not completed
1386
-     *        or false if ALL steps are completed including the exception you are testing !!!
1387
-     *
1388
-     * @param string $exception
1389
-     * @return bool
1390
-     * @throws EE_Error
1391
-     * @throws InvalidArgumentException
1392
-     * @throws InvalidDataTypeException
1393
-     * @throws InvalidInterfaceException
1394
-     * @throws ReflectionException
1395
-     */
1396
-    public function all_reg_steps_completed_except($exception = '')
1397
-    {
1398
-        return $this->_reg_steps_completed($exception);
1399
-    }
1400
-
1401
-
1402
-    /**
1403
-     * all_reg_steps_completed_except
1404
-     * returns:
1405
-     *        true if ALL reg steps, except the final step, have been marked as completed
1406
-     *        or false if any step is not completed
1407
-     *    or false if ALL steps are completed including the final step !!!
1408
-     *
1409
-     * @return bool
1410
-     * @throws EE_Error
1411
-     * @throws InvalidArgumentException
1412
-     * @throws InvalidDataTypeException
1413
-     * @throws InvalidInterfaceException
1414
-     * @throws ReflectionException
1415
-     */
1416
-    public function all_reg_steps_completed_except_final_step()
1417
-    {
1418
-        return $this->_reg_steps_completed('finalize_registration');
1419
-    }
1420
-
1421
-
1422
-    /**
1423
-     * reg_step_completed
1424
-     * returns:
1425
-     *    true if a specific reg step has been marked as completed
1426
-     *    a Unix timestamp if it has been initialized but not yet completed,
1427
-     *    or false if it has not yet been initialized
1428
-     *
1429
-     * @param string $reg_step_slug
1430
-     * @return bool|int
1431
-     * @throws EE_Error
1432
-     * @throws InvalidArgumentException
1433
-     * @throws InvalidDataTypeException
1434
-     * @throws InvalidInterfaceException
1435
-     * @throws ReflectionException
1436
-     */
1437
-    public function reg_step_completed($reg_step_slug)
1438
-    {
1439
-        return $this->_reg_steps_completed($reg_step_slug, false);
1440
-    }
1441
-
1442
-
1443
-    /**
1444
-     * completed_final_reg_step
1445
-     * returns:
1446
-     *    true if the finalize_registration reg step has been marked as completed
1447
-     *    a Unix timestamp if it has been initialized but not yet completed,
1448
-     *    or false if it has not yet been initialized
1449
-     *
1450
-     * @return bool|int
1451
-     * @throws EE_Error
1452
-     * @throws InvalidArgumentException
1453
-     * @throws InvalidDataTypeException
1454
-     * @throws InvalidInterfaceException
1455
-     * @throws ReflectionException
1456
-     */
1457
-    public function final_reg_step_completed()
1458
-    {
1459
-        return $this->_reg_steps_completed('finalize_registration', false);
1460
-    }
1461
-
1462
-
1463
-    /**
1464
-     * set_reg_step_initiated
1465
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1466
-     *
1467
-     * @param string $reg_step_slug
1468
-     * @return boolean
1469
-     * @throws EE_Error
1470
-     * @throws InvalidArgumentException
1471
-     * @throws InvalidDataTypeException
1472
-     * @throws InvalidInterfaceException
1473
-     * @throws ReflectionException
1474
-     */
1475
-    public function set_reg_step_initiated($reg_step_slug)
1476
-    {
1477
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1478
-    }
1479
-
1480
-
1481
-    /**
1482
-     * set_reg_step_completed
1483
-     * given a valid TXN_reg_step, this sets the step as completed
1484
-     *
1485
-     * @param string $reg_step_slug
1486
-     * @return boolean
1487
-     * @throws EE_Error
1488
-     * @throws InvalidArgumentException
1489
-     * @throws InvalidDataTypeException
1490
-     * @throws InvalidInterfaceException
1491
-     * @throws ReflectionException
1492
-     */
1493
-    public function set_reg_step_completed($reg_step_slug)
1494
-    {
1495
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1496
-    }
1497
-
1498
-
1499
-    /**
1500
-     * set_reg_step_completed
1501
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1502
-     *
1503
-     * @param string $reg_step_slug
1504
-     * @return boolean
1505
-     * @throws EE_Error
1506
-     * @throws InvalidArgumentException
1507
-     * @throws InvalidDataTypeException
1508
-     * @throws InvalidInterfaceException
1509
-     * @throws ReflectionException
1510
-     */
1511
-    public function set_reg_step_not_completed($reg_step_slug)
1512
-    {
1513
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1514
-    }
1515
-
1516
-
1517
-    /**
1518
-     * set_reg_step_completed
1519
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1520
-     *
1521
-     * @param  string      $reg_step_slug
1522
-     * @param  boolean|int $status
1523
-     * @return boolean
1524
-     * @throws EE_Error
1525
-     * @throws InvalidArgumentException
1526
-     * @throws InvalidDataTypeException
1527
-     * @throws InvalidInterfaceException
1528
-     * @throws ReflectionException
1529
-     */
1530
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1531
-    {
1532
-        // validate status
1533
-        $status = is_bool($status) || is_int($status) ? $status : false;
1534
-        // get reg steps array
1535
-        $txn_reg_steps = $this->reg_steps();
1536
-        // if reg step does NOT exist
1537
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1538
-            return false;
1539
-        }
1540
-        // if  we're trying to complete a step that is already completed
1541
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1542
-            return true;
1543
-        }
1544
-        // if  we're trying to complete a step that hasn't even started
1545
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1546
-            return false;
1547
-        }
1548
-        // if current status value matches the incoming value (no change)
1549
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1550
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1551
-            // this will happen in cases where multiple AJAX requests occur during the same step
1552
-            return true;
1553
-        }
1554
-        // if we're trying to set a start time, but it has already been set...
1555
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1556
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1557
-            return true;
1558
-        }
1559
-        // update completed status
1560
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1561
-        $this->set_reg_steps($txn_reg_steps);
1562
-        $this->save();
1563
-        return true;
1564
-    }
1565
-
1566
-
1567
-    /**
1568
-     * remove_reg_step
1569
-     * given a valid TXN_reg_step slug, this will remove (unset)
1570
-     * the reg step from the TXN reg step array
1571
-     *
1572
-     * @param string $reg_step_slug
1573
-     * @return void
1574
-     * @throws EE_Error
1575
-     * @throws InvalidArgumentException
1576
-     * @throws InvalidDataTypeException
1577
-     * @throws InvalidInterfaceException
1578
-     * @throws ReflectionException
1579
-     */
1580
-    public function remove_reg_step($reg_step_slug)
1581
-    {
1582
-        // get reg steps array
1583
-        $txn_reg_steps = $this->reg_steps();
1584
-        unset($txn_reg_steps[ $reg_step_slug ]);
1585
-        $this->set_reg_steps($txn_reg_steps);
1586
-    }
1587
-
1588
-
1589
-    /**
1590
-     * toggle_failed_transaction_status
1591
-     * upgrades a TXNs status from failed to abandoned,
1592
-     * meaning that contact information has been captured for at least one registrant
1593
-     *
1594
-     * @param bool $save
1595
-     * @return bool
1596
-     * @throws EE_Error
1597
-     * @throws InvalidArgumentException
1598
-     * @throws InvalidDataTypeException
1599
-     * @throws InvalidInterfaceException
1600
-     * @throws ReflectionException
1601
-     */
1602
-    public function toggle_failed_transaction_status($save = true)
1603
-    {
1604
-        // if TXN status is still set as "failed"...
1605
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1606
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1607
-            if ($save) {
1608
-                $this->save();
1609
-            }
1610
-            return true;
1611
-        }
1612
-        return false;
1613
-    }
1614
-
1615
-
1616
-    /**
1617
-     * toggle_abandoned_transaction_status
1618
-     * upgrades a TXNs status from failed or abandoned to incomplete
1619
-     *
1620
-     * @return bool
1621
-     * @throws EE_Error
1622
-     * @throws InvalidArgumentException
1623
-     * @throws InvalidDataTypeException
1624
-     * @throws InvalidInterfaceException
1625
-     * @throws ReflectionException
1626
-     */
1627
-    public function toggle_abandoned_transaction_status()
1628
-    {
1629
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1630
-        $txn_status = $this->status_ID();
1631
-        if (
1632
-            $txn_status === EEM_Transaction::failed_status_code
1633
-            || $txn_status === EEM_Transaction::abandoned_status_code
1634
-        ) {
1635
-            // if a contact record for the primary registrant has been created
1636
-            if (
1637
-                $this->primary_registration() instanceof EE_Registration
1638
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1639
-            ) {
1640
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1641
-            } else {
1642
-                // no contact record? yer abandoned!
1643
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1644
-            }
1645
-            return true;
1646
-        }
1647
-        return false;
1648
-    }
1649
-
1650
-
1651
-    /**
1652
-     * checks if an Abandoned TXN has any related payments, and if so,
1653
-     * updates the TXN status based on the amount paid
1654
-     *
1655
-     * @throws EE_Error
1656
-     * @throws InvalidDataTypeException
1657
-     * @throws InvalidInterfaceException
1658
-     * @throws InvalidArgumentException
1659
-     * @throws RuntimeException
1660
-     * @throws ReflectionException
1661
-     */
1662
-    public function verify_abandoned_transaction_status()
1663
-    {
1664
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1665
-            return;
1666
-        }
1667
-        $payments = $this->get_many_related('Payment');
1668
-        if (! empty($payments)) {
1669
-            foreach ($payments as $payment) {
1670
-                if ($payment instanceof EE_Payment) {
1671
-                    // kk this TXN should NOT be abandoned
1672
-                    $this->update_status_based_on_total_paid();
1673
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1674
-                        EE_Error::add_attention(
1675
-                            sprintf(
1676
-                                esc_html__(
1677
-                                    'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1678
-                                    'event_espresso'
1679
-                                ),
1680
-                                $this->ID(),
1681
-                                $this->pretty_status()
1682
-                            )
1683
-                        );
1684
-                    }
1685
-                    // get final reg step status
1686
-                    $finalized = $this->final_reg_step_completed();
1687
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1688
-                    // but has not yet been fully completed (TRUE)
1689
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1690
-                        $this->set_reg_step_completed('finalize_registration');
1691
-                        $this->save();
1692
-                    }
1693
-                }
1694
-            }
1695
-        }
1696
-    }
1697
-
1698
-
1699
-    /**
1700
-     * @since 4.10.4.p
1701
-     * @throws EE_Error
1702
-     * @throws InvalidArgumentException
1703
-     * @throws InvalidDataTypeException
1704
-     * @throws InvalidInterfaceException
1705
-     * @throws ReflectionException
1706
-     * @throws RuntimeException
1707
-     */
1708
-    public function recalculateLineItems()
1709
-    {
1710
-        $total_line_item = $this->total_line_item(false);
1711
-        if ($total_line_item instanceof EE_Line_Item) {
1712
-            EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1713
-            return EEH_Line_Item::apply_taxes($total_line_item, true);
1714
-        }
1715
-        return false;
1716
-    }
16
+	/**
17
+	 * The length of time in seconds that a lock is applied before being considered expired.
18
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
19
+	 */
20
+	const LOCK_EXPIRATION = 2;
21
+
22
+	/**
23
+	 * txn status upon initial construction.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $_old_txn_status;
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values          incoming values
32
+	 * @param string $timezone                incoming timezone
33
+	 *                                        (if not set the timezone set for the website will be used.)
34
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
+	 *                                        date_format and the second value is the time format
36
+	 * @return EE_Transaction
37
+	 * @throws EE_Error
38
+	 * @throws InvalidArgumentException
39
+	 * @throws InvalidDataTypeException
40
+	 * @throws InvalidInterfaceException
41
+	 * @throws ReflectionException
42
+	 */
43
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
44
+	{
45
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
+		$txn = $has_object
47
+			? $has_object
48
+			: new self($props_n_values, false, $timezone, $date_formats);
49
+		if (! $has_object) {
50
+			$txn->set_old_txn_status($txn->status_ID());
51
+		}
52
+		return $txn;
53
+	}
54
+
55
+
56
+	/**
57
+	 * @param array  $props_n_values  incoming values from the database
58
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
+	 *                                the website will be used.
60
+	 * @return EE_Transaction
61
+	 * @throws EE_Error
62
+	 * @throws InvalidArgumentException
63
+	 * @throws InvalidDataTypeException
64
+	 * @throws InvalidInterfaceException
65
+	 * @throws ReflectionException
66
+	 */
67
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
68
+	{
69
+		$txn = new self($props_n_values, true, $timezone);
70
+		$txn->set_old_txn_status($txn->status_ID());
71
+		return $txn;
72
+	}
73
+
74
+
75
+	/**
76
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
+	 * If that also fails, then an exception is thrown.
79
+	 *
80
+	 * @throws EE_Error
81
+	 * @throws InvalidArgumentException
82
+	 * @throws InvalidDataTypeException
83
+	 * @throws InvalidInterfaceException
84
+	 * @throws ReflectionException
85
+	 */
86
+	public function lock()
87
+	{
88
+		// attempt to set lock, but if that fails...
89
+		if (! $this->add_extra_meta('lock', time(), true)) {
90
+			// then attempt to remove the lock in case it is expired
91
+			if ($this->_remove_expired_lock()) {
92
+				// if removal was successful, then try setting lock again
93
+				$this->lock();
94
+			} else {
95
+				// but if the lock can not be removed, then throw an exception
96
+				throw new EE_Error(
97
+					sprintf(
98
+						__(
99
+							'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
100
+							'event_espresso'
101
+						),
102
+						$this->ID()
103
+					)
104
+				);
105
+			}
106
+		}
107
+	}
108
+
109
+
110
+	/**
111
+	 * removes transaction lock applied in EE_Transaction::lock()
112
+	 *
113
+	 * @return int
114
+	 * @throws EE_Error
115
+	 * @throws InvalidArgumentException
116
+	 * @throws InvalidDataTypeException
117
+	 * @throws InvalidInterfaceException
118
+	 * @throws ReflectionException
119
+	 */
120
+	public function unlock()
121
+	{
122
+		return $this->delete_extra_meta('lock');
123
+	}
124
+
125
+
126
+	/**
127
+	 * Decides whether or not now is the right time to update the transaction.
128
+	 * This is useful because we don't always know if it is safe to update the transaction
129
+	 * and its related data. why?
130
+	 * because it's possible that the transaction is being used in another
131
+	 * request and could overwrite anything we save.
132
+	 * So we want to only update the txn once we know that won't happen.
133
+	 * We also check that the lock isn't expired, and remove it if it is
134
+	 *
135
+	 * @return boolean
136
+	 * @throws EE_Error
137
+	 * @throws InvalidArgumentException
138
+	 * @throws InvalidDataTypeException
139
+	 * @throws InvalidInterfaceException
140
+	 * @throws ReflectionException
141
+	 */
142
+	public function is_locked()
143
+	{
144
+		// if TXN is not locked, then return false immediately
145
+		if (! $this->_get_lock()) {
146
+			return false;
147
+		}
148
+		// if not, then let's try and remove the lock in case it's expired...
149
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
+		// and a positive number if the lock was removed (ie: number of locks deleted),
151
+		// so we need to return the opposite
152
+		return ! $this->_remove_expired_lock() ? true : false;
153
+	}
154
+
155
+
156
+	/**
157
+	 * Gets the meta field indicating that this TXN is locked
158
+	 *
159
+	 * @return int
160
+	 * @throws EE_Error
161
+	 * @throws InvalidArgumentException
162
+	 * @throws InvalidDataTypeException
163
+	 * @throws InvalidInterfaceException
164
+	 * @throws ReflectionException
165
+	 */
166
+	protected function _get_lock()
167
+	{
168
+		return (int) $this->get_extra_meta('lock', true, 0);
169
+	}
170
+
171
+
172
+	/**
173
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
+	 *
175
+	 * @return int
176
+	 * @throws EE_Error
177
+	 * @throws InvalidArgumentException
178
+	 * @throws InvalidDataTypeException
179
+	 * @throws InvalidInterfaceException
180
+	 * @throws ReflectionException
181
+	 */
182
+	protected function _remove_expired_lock()
183
+	{
184
+		$locked = $this->_get_lock();
185
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
+			return $this->unlock();
187
+		}
188
+		return 0;
189
+	}
190
+
191
+
192
+	/**
193
+	 * Set transaction total
194
+	 *
195
+	 * @param float $total total value of transaction
196
+	 * @throws EE_Error
197
+	 * @throws InvalidArgumentException
198
+	 * @throws InvalidDataTypeException
199
+	 * @throws InvalidInterfaceException
200
+	 * @throws ReflectionException
201
+	 */
202
+	public function set_total($total = 0.00)
203
+	{
204
+		$this->set('TXN_total', (float) $total);
205
+	}
206
+
207
+
208
+	/**
209
+	 * Set Total Amount Paid to Date
210
+	 *
211
+	 * @param float $total_paid total amount paid to date (sum of all payments)
212
+	 * @throws EE_Error
213
+	 * @throws InvalidArgumentException
214
+	 * @throws InvalidDataTypeException
215
+	 * @throws InvalidInterfaceException
216
+	 * @throws ReflectionException
217
+	 */
218
+	public function set_paid($total_paid = 0.00)
219
+	{
220
+		$this->set('TXN_paid', (float) $total_paid);
221
+	}
222
+
223
+
224
+	/**
225
+	 * Set transaction status
226
+	 *
227
+	 * @param string $status        whether the transaction is open, declined, accepted,
228
+	 *                              or any number of custom values that can be set
229
+	 * @throws EE_Error
230
+	 * @throws InvalidArgumentException
231
+	 * @throws InvalidDataTypeException
232
+	 * @throws InvalidInterfaceException
233
+	 * @throws ReflectionException
234
+	 */
235
+	public function set_status($status = '')
236
+	{
237
+		$this->set('STS_ID', $status);
238
+	}
239
+
240
+
241
+	/**
242
+	 * Set hash salt
243
+	 *
244
+	 * @param string $hash_salt required for some payment gateways
245
+	 * @throws EE_Error
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidDataTypeException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function set_hash_salt($hash_salt = '')
252
+	{
253
+		$this->set('TXN_hash_salt', $hash_salt);
254
+	}
255
+
256
+
257
+	/**
258
+	 * Sets TXN_reg_steps array
259
+	 *
260
+	 * @param array $txn_reg_steps
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public function set_reg_steps(array $txn_reg_steps)
268
+	{
269
+		$this->set('TXN_reg_steps', $txn_reg_steps);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Gets TXN_reg_steps
275
+	 *
276
+	 * @return array
277
+	 * @throws EE_Error
278
+	 * @throws InvalidArgumentException
279
+	 * @throws InvalidDataTypeException
280
+	 * @throws InvalidInterfaceException
281
+	 * @throws ReflectionException
282
+	 */
283
+	public function reg_steps()
284
+	{
285
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
286
+		return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
287
+	}
288
+
289
+
290
+	/**
291
+	 * @return string of transaction's total cost, with currency symbol and decimal
292
+	 * @throws EE_Error
293
+	 * @throws InvalidArgumentException
294
+	 * @throws InvalidDataTypeException
295
+	 * @throws InvalidInterfaceException
296
+	 * @throws ReflectionException
297
+	 */
298
+	public function pretty_total()
299
+	{
300
+		return $this->get_pretty('TXN_total');
301
+	}
302
+
303
+
304
+	/**
305
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
306
+	 *
307
+	 * @return string
308
+	 * @throws EE_Error
309
+	 * @throws InvalidArgumentException
310
+	 * @throws InvalidDataTypeException
311
+	 * @throws InvalidInterfaceException
312
+	 * @throws ReflectionException
313
+	 */
314
+	public function pretty_paid()
315
+	{
316
+		return $this->get_pretty('TXN_paid');
317
+	}
318
+
319
+
320
+	/**
321
+	 * calculate the amount remaining for this transaction and return;
322
+	 *
323
+	 * @return float amount remaining
324
+	 * @throws EE_Error
325
+	 * @throws InvalidArgumentException
326
+	 * @throws InvalidDataTypeException
327
+	 * @throws InvalidInterfaceException
328
+	 * @throws ReflectionException
329
+	 */
330
+	public function remaining()
331
+	{
332
+		return $this->total() - $this->paid();
333
+	}
334
+
335
+
336
+	/**
337
+	 * get Transaction Total
338
+	 *
339
+	 * @return float
340
+	 * @throws EE_Error
341
+	 * @throws InvalidArgumentException
342
+	 * @throws InvalidDataTypeException
343
+	 * @throws InvalidInterfaceException
344
+	 * @throws ReflectionException
345
+	 */
346
+	public function total()
347
+	{
348
+		return (float) $this->get('TXN_total');
349
+	}
350
+
351
+
352
+	/**
353
+	 * get Total Amount Paid to Date
354
+	 *
355
+	 * @return float
356
+	 * @throws EE_Error
357
+	 * @throws InvalidArgumentException
358
+	 * @throws InvalidDataTypeException
359
+	 * @throws InvalidInterfaceException
360
+	 * @throws ReflectionException
361
+	 */
362
+	public function paid()
363
+	{
364
+		return (float) $this->get('TXN_paid');
365
+	}
366
+
367
+
368
+	/**
369
+	 * @return mixed|null
370
+	 * @throws EE_Error
371
+	 * @throws InvalidArgumentException
372
+	 * @throws InvalidDataTypeException
373
+	 * @throws InvalidInterfaceException
374
+	 * @throws ReflectionException
375
+	 */
376
+	public function get_cart_session()
377
+	{
378
+		$session_data = (array) $this->get('TXN_session_data');
379
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
380
+			? $session_data['cart']
381
+			: null;
382
+	}
383
+
384
+
385
+	/**
386
+	 * get Transaction session data
387
+	 *
388
+	 * @return array|mixed
389
+	 * @throws EE_Error
390
+	 * @throws InvalidArgumentException
391
+	 * @throws InvalidDataTypeException
392
+	 * @throws InvalidInterfaceException
393
+	 * @throws ReflectionException
394
+	 */
395
+	public function session_data()
396
+	{
397
+		$session_data = $this->get('TXN_session_data');
398
+		if (empty($session_data)) {
399
+			$session_data = array(
400
+				'id'            => null,
401
+				'user_id'       => null,
402
+				'ip_address'    => null,
403
+				'user_agent'    => null,
404
+				'init_access'   => null,
405
+				'last_access'   => null,
406
+				'pages_visited' => array(),
407
+			);
408
+		}
409
+		return $session_data;
410
+	}
411
+
412
+
413
+	/**
414
+	 * Set session data within the TXN object
415
+	 *
416
+	 * @param EE_Session|array $session_data
417
+	 * @throws EE_Error
418
+	 * @throws InvalidArgumentException
419
+	 * @throws InvalidDataTypeException
420
+	 * @throws InvalidInterfaceException
421
+	 * @throws ReflectionException
422
+	 */
423
+	public function set_txn_session_data($session_data)
424
+	{
425
+		if ($session_data instanceof EE_Session) {
426
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
427
+		} else {
428
+			$this->set('TXN_session_data', $session_data);
429
+		}
430
+	}
431
+
432
+
433
+	/**
434
+	 * get Transaction hash salt
435
+	 *
436
+	 * @return mixed
437
+	 * @throws EE_Error
438
+	 * @throws InvalidArgumentException
439
+	 * @throws InvalidDataTypeException
440
+	 * @throws InvalidInterfaceException
441
+	 * @throws ReflectionException
442
+	 */
443
+	public function hash_salt_()
444
+	{
445
+		return $this->get('TXN_hash_salt');
446
+	}
447
+
448
+
449
+	/**
450
+	 * Returns the transaction datetime as either:
451
+	 *            - unix timestamp format ($format = false, $gmt = true)
452
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
453
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
454
+	 *              set timezone in this class differs from what the timezone is on the blog.
455
+	 *            - formatted date string including the UTC (timezone) offset (default).
456
+	 *
457
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
458
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
459
+	 *                          or no UTC offset applied
460
+	 * @return string | int
461
+	 * @throws EE_Error
462
+	 * @throws InvalidArgumentException
463
+	 * @throws InvalidDataTypeException
464
+	 * @throws InvalidInterfaceException
465
+	 * @throws ReflectionException
466
+	 */
467
+	public function datetime($format = false, $gmt = false)
468
+	{
469
+		if ($format) {
470
+			return $this->get_pretty('TXN_timestamp');
471
+		}
472
+		if ($gmt) {
473
+			return $this->get_raw('TXN_timestamp');
474
+		}
475
+		return $this->get('TXN_timestamp');
476
+	}
477
+
478
+
479
+	/**
480
+	 * Gets registrations on this transaction
481
+	 *
482
+	 * @param array   $query_params array of query parameters
483
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
484
+	 * @return EE_Base_Class[]|EE_Registration[]
485
+	 * @throws EE_Error
486
+	 * @throws InvalidArgumentException
487
+	 * @throws InvalidDataTypeException
488
+	 * @throws InvalidInterfaceException
489
+	 * @throws ReflectionException
490
+	 */
491
+	public function registrations($query_params = array(), $get_cached = false)
492
+	{
493
+		$query_params = (empty($query_params) || ! is_array($query_params))
494
+			? array(
495
+				'order_by' => array(
496
+					'Event.EVT_name'     => 'ASC',
497
+					'Attendee.ATT_lname' => 'ASC',
498
+					'Attendee.ATT_fname' => 'ASC',
499
+				),
500
+			)
501
+			: $query_params;
502
+		$query_params = $get_cached ? array() : $query_params;
503
+		return $this->get_many_related('Registration', $query_params);
504
+	}
505
+
506
+
507
+	/**
508
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
509
+	 * function for getting attendees and how many registrations they each have for an event)
510
+	 *
511
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
512
+	 * @throws EE_Error
513
+	 * @throws InvalidArgumentException
514
+	 * @throws InvalidDataTypeException
515
+	 * @throws InvalidInterfaceException
516
+	 * @throws ReflectionException
517
+	 */
518
+	public function attendees()
519
+	{
520
+		return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
521
+	}
522
+
523
+
524
+	/**
525
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
526
+	 *
527
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
528
+	 * @return EE_Base_Class[]|EE_Payment[]
529
+	 * @throws EE_Error
530
+	 * @throws InvalidArgumentException
531
+	 * @throws InvalidDataTypeException
532
+	 * @throws InvalidInterfaceException
533
+	 * @throws ReflectionException
534
+	 */
535
+	public function payments($query_params = array())
536
+	{
537
+		return $this->get_many_related('Payment', $query_params);
538
+	}
539
+
540
+
541
+	/**
542
+	 * gets only approved payments for this transaction
543
+	 *
544
+	 * @return EE_Base_Class[]|EE_Payment[]
545
+	 * @throws EE_Error
546
+	 * @throws InvalidArgumentException
547
+	 * @throws ReflectionException
548
+	 * @throws InvalidDataTypeException
549
+	 * @throws InvalidInterfaceException
550
+	 */
551
+	public function approved_payments()
552
+	{
553
+		EE_Registry::instance()->load_model('Payment');
554
+		return $this->get_many_related(
555
+			'Payment',
556
+			array(
557
+				array('STS_ID' => EEM_Payment::status_id_approved),
558
+				'order_by' => array('PAY_timestamp' => 'DESC'),
559
+			)
560
+		);
561
+	}
562
+
563
+
564
+	/**
565
+	 * Gets all payments which have not been approved
566
+	 *
567
+	 * @return EE_Base_Class[]|EEI_Payment[]
568
+	 * @throws EE_Error if a model is misconfigured somehow
569
+	 * @throws InvalidArgumentException
570
+	 * @throws InvalidDataTypeException
571
+	 * @throws InvalidInterfaceException
572
+	 * @throws ReflectionException
573
+	 */
574
+	public function pending_payments()
575
+	{
576
+		return $this->get_many_related(
577
+			'Payment',
578
+			array(
579
+				array(
580
+					'STS_ID' => EEM_Payment::status_id_pending,
581
+				),
582
+				'order_by' => array(
583
+					'PAY_timestamp' => 'DESC',
584
+				),
585
+			)
586
+		);
587
+	}
588
+
589
+
590
+	/**
591
+	 * echoes $this->pretty_status()
592
+	 *
593
+	 * @param bool $show_icons
594
+	 * @throws EE_Error
595
+	 * @throws InvalidArgumentException
596
+	 * @throws InvalidDataTypeException
597
+	 * @throws InvalidInterfaceException
598
+	 * @throws ReflectionException
599
+	 */
600
+	public function e_pretty_status($show_icons = false)
601
+	{
602
+		echo $this->pretty_status($show_icons);
603
+	}
604
+
605
+
606
+	/**
607
+	 * returns a pretty version of the status, good for displaying to users
608
+	 *
609
+	 * @param bool $show_icons
610
+	 * @return string
611
+	 * @throws EE_Error
612
+	 * @throws InvalidArgumentException
613
+	 * @throws InvalidDataTypeException
614
+	 * @throws InvalidInterfaceException
615
+	 * @throws ReflectionException
616
+	 */
617
+	public function pretty_status($show_icons = false)
618
+	{
619
+		$status = EEM_Status::instance()->localized_status(
620
+			array($this->status_ID() => __('unknown', 'event_espresso')),
621
+			false,
622
+			'sentence'
623
+		);
624
+		$icon = '';
625
+		switch ($this->status_ID()) {
626
+			case EEM_Transaction::complete_status_code:
627
+				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
628
+				break;
629
+			case EEM_Transaction::incomplete_status_code:
630
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
631
+					: '';
632
+				break;
633
+			case EEM_Transaction::abandoned_status_code:
634
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
635
+				break;
636
+			case EEM_Transaction::failed_status_code:
637
+				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
638
+				break;
639
+			case EEM_Transaction::overpaid_status_code:
640
+				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
641
+				break;
642
+		}
643
+		return $icon . $status[ $this->status_ID() ];
644
+	}
645
+
646
+
647
+	/**
648
+	 * get Transaction Status
649
+	 *
650
+	 * @return mixed
651
+	 * @throws EE_Error
652
+	 * @throws InvalidArgumentException
653
+	 * @throws InvalidDataTypeException
654
+	 * @throws InvalidInterfaceException
655
+	 * @throws ReflectionException
656
+	 */
657
+	public function status_ID()
658
+	{
659
+		return $this->get('STS_ID');
660
+	}
661
+
662
+
663
+	/**
664
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
665
+	 *
666
+	 * @return boolean
667
+	 * @throws EE_Error
668
+	 * @throws InvalidArgumentException
669
+	 * @throws InvalidDataTypeException
670
+	 * @throws InvalidInterfaceException
671
+	 * @throws ReflectionException
672
+	 */
673
+	public function is_free()
674
+	{
675
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
676
+	}
677
+
678
+
679
+	/**
680
+	 * Returns whether this transaction is complete
681
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
682
+	 *
683
+	 * @return boolean
684
+	 * @throws EE_Error
685
+	 * @throws InvalidArgumentException
686
+	 * @throws InvalidDataTypeException
687
+	 * @throws InvalidInterfaceException
688
+	 * @throws ReflectionException
689
+	 */
690
+	public function is_completed()
691
+	{
692
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
693
+	}
694
+
695
+
696
+	/**
697
+	 * Returns whether this transaction is incomplete
698
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
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_incomplete()
708
+	{
709
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
710
+	}
711
+
712
+
713
+	/**
714
+	 * Returns whether this transaction is overpaid
715
+	 * Useful in templates and other logic for deciding if monies need to be refunded
716
+	 *
717
+	 * @return boolean
718
+	 * @throws EE_Error
719
+	 * @throws InvalidArgumentException
720
+	 * @throws InvalidDataTypeException
721
+	 * @throws InvalidInterfaceException
722
+	 * @throws ReflectionException
723
+	 */
724
+	public function is_overpaid()
725
+	{
726
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
727
+	}
728
+
729
+
730
+	/**
731
+	 * Returns whether this transaction was abandoned
732
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
733
+	 * but that contact information exists for at least one registrant
734
+	 *
735
+	 * @return boolean
736
+	 * @throws EE_Error
737
+	 * @throws InvalidArgumentException
738
+	 * @throws InvalidDataTypeException
739
+	 * @throws InvalidInterfaceException
740
+	 * @throws ReflectionException
741
+	 */
742
+	public function is_abandoned()
743
+	{
744
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
745
+	}
746
+
747
+
748
+	/**
749
+	 * Returns whether this transaction failed
750
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
751
+	 * and that NO contact information exists for any registrants
752
+	 *
753
+	 * @return boolean
754
+	 * @throws EE_Error
755
+	 * @throws InvalidArgumentException
756
+	 * @throws InvalidDataTypeException
757
+	 * @throws InvalidInterfaceException
758
+	 * @throws ReflectionException
759
+	 */
760
+	public function failed()
761
+	{
762
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
763
+	}
764
+
765
+
766
+	/**
767
+	 * This returns the url for the invoice of this transaction
768
+	 *
769
+	 * @param string $type 'html' or 'pdf' (default is pdf)
770
+	 * @return string
771
+	 * @throws EE_Error
772
+	 * @throws InvalidArgumentException
773
+	 * @throws InvalidDataTypeException
774
+	 * @throws InvalidInterfaceException
775
+	 * @throws ReflectionException
776
+	 */
777
+	public function invoice_url($type = 'html')
778
+	{
779
+		$REG = $this->primary_registration();
780
+		if (! $REG instanceof EE_Registration) {
781
+			return '';
782
+		}
783
+		return $REG->invoice_url($type);
784
+	}
785
+
786
+
787
+	/**
788
+	 * Gets the primary registration only
789
+	 *
790
+	 * @return EE_Base_Class|EE_Registration
791
+	 * @throws EE_Error
792
+	 * @throws InvalidArgumentException
793
+	 * @throws InvalidDataTypeException
794
+	 * @throws InvalidInterfaceException
795
+	 * @throws ReflectionException
796
+	 */
797
+	public function primary_registration()
798
+	{
799
+		$registrations = (array) $this->get_many_related(
800
+			'Registration',
801
+			array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
802
+		);
803
+		foreach ($registrations as $registration) {
804
+			// valid registration that is NOT cancelled or declined ?
805
+			if (
806
+				$registration instanceof EE_Registration
807
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
808
+			) {
809
+				return $registration;
810
+			}
811
+		}
812
+		// nothing valid found, so just return first thing from array of results
813
+		return reset($registrations);
814
+	}
815
+
816
+
817
+	/**
818
+	 * Gets the URL for viewing the receipt
819
+	 *
820
+	 * @param string $type 'pdf' or 'html' (default is 'html')
821
+	 * @return string
822
+	 * @throws EE_Error
823
+	 * @throws InvalidArgumentException
824
+	 * @throws InvalidDataTypeException
825
+	 * @throws InvalidInterfaceException
826
+	 * @throws ReflectionException
827
+	 */
828
+	public function receipt_url($type = 'html')
829
+	{
830
+		$REG = $this->primary_registration();
831
+		if (! $REG instanceof EE_Registration) {
832
+			return '';
833
+		}
834
+		return $REG->receipt_url($type);
835
+	}
836
+
837
+
838
+	/**
839
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
840
+	 * a query parameter
841
+	 *
842
+	 * @return string
843
+	 * @throws EE_Error
844
+	 * @throws InvalidArgumentException
845
+	 * @throws InvalidDataTypeException
846
+	 * @throws InvalidInterfaceException
847
+	 * @throws ReflectionException
848
+	 */
849
+	public function payment_overview_url()
850
+	{
851
+		$primary_registration = $this->primary_registration();
852
+		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
853
+	}
854
+
855
+
856
+	/**
857
+	 * @return string
858
+	 * @throws EE_Error
859
+	 * @throws InvalidArgumentException
860
+	 * @throws InvalidDataTypeException
861
+	 * @throws InvalidInterfaceException
862
+	 * @throws ReflectionException
863
+	 */
864
+	public function gateway_response_on_transaction()
865
+	{
866
+		$payment = $this->get_first_related('Payment');
867
+		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
868
+	}
869
+
870
+
871
+	/**
872
+	 * Get the status object of this object
873
+	 *
874
+	 * @return EE_Base_Class|EE_Status
875
+	 * @throws EE_Error
876
+	 * @throws InvalidArgumentException
877
+	 * @throws InvalidDataTypeException
878
+	 * @throws InvalidInterfaceException
879
+	 * @throws ReflectionException
880
+	 */
881
+	public function status_obj()
882
+	{
883
+		return $this->get_first_related('Status');
884
+	}
885
+
886
+
887
+	/**
888
+	 * Gets all the extra meta info on this payment
889
+	 *
890
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
891
+	 * @return EE_Base_Class[]|EE_Extra_Meta
892
+	 * @throws EE_Error
893
+	 * @throws InvalidArgumentException
894
+	 * @throws InvalidDataTypeException
895
+	 * @throws InvalidInterfaceException
896
+	 * @throws ReflectionException
897
+	 */
898
+	public function extra_meta($query_params = array())
899
+	{
900
+		return $this->get_many_related('Extra_Meta', $query_params);
901
+	}
902
+
903
+
904
+	/**
905
+	 * Wrapper for _add_relation_to
906
+	 *
907
+	 * @param EE_Registration $registration
908
+	 * @return EE_Base_Class the relation was added to
909
+	 * @throws EE_Error
910
+	 * @throws InvalidArgumentException
911
+	 * @throws InvalidDataTypeException
912
+	 * @throws InvalidInterfaceException
913
+	 * @throws ReflectionException
914
+	 */
915
+	public function add_registration(EE_Registration $registration)
916
+	{
917
+		return $this->_add_relation_to($registration, 'Registration');
918
+	}
919
+
920
+
921
+	/**
922
+	 * Removes the given registration from being related (even before saving this transaction).
923
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
924
+	 *
925
+	 * @param int $registration_or_id
926
+	 * @return EE_Base_Class that was removed from being related
927
+	 * @throws EE_Error
928
+	 * @throws InvalidArgumentException
929
+	 * @throws InvalidDataTypeException
930
+	 * @throws InvalidInterfaceException
931
+	 * @throws ReflectionException
932
+	 */
933
+	public function remove_registration_with_id($registration_or_id)
934
+	{
935
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
936
+	}
937
+
938
+
939
+	/**
940
+	 * Gets all the line items which are for ACTUAL items
941
+	 *
942
+	 * @return EE_Line_Item[]
943
+	 * @throws EE_Error
944
+	 * @throws InvalidArgumentException
945
+	 * @throws InvalidDataTypeException
946
+	 * @throws InvalidInterfaceException
947
+	 * @throws ReflectionException
948
+	 */
949
+	public function items_purchased()
950
+	{
951
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
952
+	}
953
+
954
+
955
+	/**
956
+	 * Wrapper for _add_relation_to
957
+	 *
958
+	 * @param EE_Line_Item $line_item
959
+	 * @return EE_Base_Class the relation was added to
960
+	 * @throws EE_Error
961
+	 * @throws InvalidArgumentException
962
+	 * @throws InvalidDataTypeException
963
+	 * @throws InvalidInterfaceException
964
+	 * @throws ReflectionException
965
+	 */
966
+	public function add_line_item(EE_Line_Item $line_item)
967
+	{
968
+		return $this->_add_relation_to($line_item, 'Line_Item');
969
+	}
970
+
971
+
972
+	/**
973
+	 * Gets ALL the line items related to this transaction (unstructured)
974
+	 *
975
+	 * @param array $query_params
976
+	 * @return EE_Base_Class[]|EE_Line_Item[]
977
+	 * @throws EE_Error
978
+	 * @throws InvalidArgumentException
979
+	 * @throws InvalidDataTypeException
980
+	 * @throws InvalidInterfaceException
981
+	 * @throws ReflectionException
982
+	 */
983
+	public function line_items($query_params = array())
984
+	{
985
+		return $this->get_many_related('Line_Item', $query_params);
986
+	}
987
+
988
+
989
+	/**
990
+	 * Gets all the line items which are taxes on the total
991
+	 *
992
+	 * @return EE_Line_Item[]
993
+	 * @throws EE_Error
994
+	 * @throws InvalidArgumentException
995
+	 * @throws InvalidDataTypeException
996
+	 * @throws InvalidInterfaceException
997
+	 * @throws ReflectionException
998
+	 */
999
+	public function tax_items()
1000
+	{
1001
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * Gets the total line item (which is a parent of all other related line items,
1007
+	 * meaning it takes them all into account on its total)
1008
+	 *
1009
+	 * @param bool $create_if_not_found
1010
+	 * @return EE_Line_Item|null
1011
+	 * @throws EE_Error
1012
+	 * @throws InvalidArgumentException
1013
+	 * @throws InvalidDataTypeException
1014
+	 * @throws InvalidInterfaceException
1015
+	 * @throws ReflectionException
1016
+	 */
1017
+	public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1018
+	{
1019
+		$item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1020
+		if ($item instanceof EE_Line_Item) {
1021
+			return $item;
1022
+		}
1023
+		return $create_if_not_found ? EEH_Line_Item::create_total_line_item($this) : null;
1024
+	}
1025
+
1026
+
1027
+	/**
1028
+	 * Returns the total amount of tax on this transaction
1029
+	 * (assumes there's only one tax subtotal line item)
1030
+	 *
1031
+	 * @return float
1032
+	 * @throws EE_Error
1033
+	 * @throws InvalidArgumentException
1034
+	 * @throws InvalidDataTypeException
1035
+	 * @throws InvalidInterfaceException
1036
+	 * @throws ReflectionException
1037
+	 */
1038
+	public function tax_total()
1039
+	{
1040
+		$tax_line_item = $this->tax_total_line_item();
1041
+		if ($tax_line_item) {
1042
+			return (float) $tax_line_item->total();
1043
+		}
1044
+		return (float) 0;
1045
+	}
1046
+
1047
+
1048
+	/**
1049
+	 * Gets the tax subtotal line item (assumes there's only one)
1050
+	 *
1051
+	 * @return EE_Line_Item
1052
+	 * @throws EE_Error
1053
+	 * @throws InvalidArgumentException
1054
+	 * @throws InvalidDataTypeException
1055
+	 * @throws InvalidInterfaceException
1056
+	 * @throws ReflectionException
1057
+	 */
1058
+	public function tax_total_line_item()
1059
+	{
1060
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1061
+	}
1062
+
1063
+
1064
+	/**
1065
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1066
+	 *
1067
+	 * @return EE_Form_Section_Proper
1068
+	 * @throws EE_Error
1069
+	 * @throws InvalidArgumentException
1070
+	 * @throws InvalidDataTypeException
1071
+	 * @throws InvalidInterfaceException
1072
+	 * @throws ReflectionException
1073
+	 */
1074
+	public function billing_info()
1075
+	{
1076
+		$payment_method = $this->payment_method();
1077
+		if (! $payment_method) {
1078
+			EE_Error::add_error(
1079
+				__(
1080
+					'Could not find billing info for transaction because no gateway has been used for it yet',
1081
+					'event_espresso'
1082
+				),
1083
+				__FILE__,
1084
+				__FUNCTION__,
1085
+				__LINE__
1086
+			);
1087
+			return null;
1088
+		}
1089
+		$primary_reg = $this->primary_registration();
1090
+		if (! $primary_reg) {
1091
+			EE_Error::add_error(
1092
+				__(
1093
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1094
+					'event_espresso'
1095
+				),
1096
+				__FILE__,
1097
+				__FUNCTION__,
1098
+				__LINE__
1099
+			);
1100
+			return null;
1101
+		}
1102
+		$attendee = $primary_reg->attendee();
1103
+		if (! $attendee) {
1104
+			EE_Error::add_error(
1105
+				__(
1106
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1107
+					'event_espresso'
1108
+				),
1109
+				__FILE__,
1110
+				__FUNCTION__,
1111
+				__LINE__
1112
+			);
1113
+			return null;
1114
+		}
1115
+		return $attendee->billing_info_for_payment_method($payment_method);
1116
+	}
1117
+
1118
+
1119
+	/**
1120
+	 * Gets PMD_ID
1121
+	 *
1122
+	 * @return int
1123
+	 * @throws EE_Error
1124
+	 * @throws InvalidArgumentException
1125
+	 * @throws InvalidDataTypeException
1126
+	 * @throws InvalidInterfaceException
1127
+	 * @throws ReflectionException
1128
+	 */
1129
+	public function payment_method_ID()
1130
+	{
1131
+		return $this->get('PMD_ID');
1132
+	}
1133
+
1134
+
1135
+	/**
1136
+	 * Sets PMD_ID
1137
+	 *
1138
+	 * @param int $PMD_ID
1139
+	 * @throws EE_Error
1140
+	 * @throws InvalidArgumentException
1141
+	 * @throws InvalidDataTypeException
1142
+	 * @throws InvalidInterfaceException
1143
+	 * @throws ReflectionException
1144
+	 */
1145
+	public function set_payment_method_ID($PMD_ID)
1146
+	{
1147
+		$this->set('PMD_ID', $PMD_ID);
1148
+	}
1149
+
1150
+
1151
+	/**
1152
+	 * Gets the last-used payment method on this transaction
1153
+	 * (we COULD just use the last-made payment, but some payment methods, namely
1154
+	 * offline ones, dont' create payments)
1155
+	 *
1156
+	 * @return EE_Payment_Method
1157
+	 * @throws EE_Error
1158
+	 * @throws InvalidArgumentException
1159
+	 * @throws InvalidDataTypeException
1160
+	 * @throws InvalidInterfaceException
1161
+	 * @throws ReflectionException
1162
+	 */
1163
+	public function payment_method()
1164
+	{
1165
+		$pm = $this->get_first_related('Payment_Method');
1166
+		if ($pm instanceof EE_Payment_Method) {
1167
+			return $pm;
1168
+		}
1169
+		$last_payment = $this->last_payment();
1170
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1171
+			return $last_payment->payment_method();
1172
+		}
1173
+		return null;
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * Gets the last payment made
1179
+	 *
1180
+	 * @return EE_Base_Class|EE_Payment
1181
+	 * @throws EE_Error
1182
+	 * @throws InvalidArgumentException
1183
+	 * @throws InvalidDataTypeException
1184
+	 * @throws InvalidInterfaceException
1185
+	 * @throws ReflectionException
1186
+	 */
1187
+	public function last_payment()
1188
+	{
1189
+		return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1190
+	}
1191
+
1192
+
1193
+	/**
1194
+	 * Gets all the line items which are unrelated to tickets on this transaction
1195
+	 *
1196
+	 * @return EE_Line_Item[]
1197
+	 * @throws EE_Error
1198
+	 * @throws InvalidArgumentException
1199
+	 * @throws InvalidDataTypeException
1200
+	 * @throws InvalidInterfaceException
1201
+	 * @throws ReflectionException
1202
+	 */
1203
+	public function non_ticket_line_items()
1204
+	{
1205
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1206
+	}
1207
+
1208
+
1209
+	/**
1210
+	 * possibly toggles TXN status
1211
+	 *
1212
+	 * @param  boolean $update whether to save the TXN
1213
+	 * @return bool whether the TXN was saved
1214
+	 * @throws EE_Error
1215
+	 * @throws InvalidArgumentException
1216
+	 * @throws InvalidDataTypeException
1217
+	 * @throws InvalidInterfaceException
1218
+	 * @throws ReflectionException
1219
+	 * @throws RuntimeException
1220
+	 */
1221
+	public function update_status_based_on_total_paid($update = true)
1222
+	{
1223
+		// set transaction status based on comparison of TXN_paid vs TXN_total
1224
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1225
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
1226
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1227
+			$new_txn_status = EEM_Transaction::complete_status_code;
1228
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1229
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
1230
+		} else {
1231
+			throw new RuntimeException(
1232
+				__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1233
+			);
1234
+		}
1235
+		if ($new_txn_status !== $this->status_ID()) {
1236
+			$this->set_status($new_txn_status);
1237
+			if ($update) {
1238
+				return $this->save() ? true : false;
1239
+			}
1240
+		}
1241
+		return false;
1242
+	}
1243
+
1244
+
1245
+	/**
1246
+	 * Updates the transaction's status and total_paid based on all the payments
1247
+	 * that apply to it
1248
+	 *
1249
+	 * @deprecated
1250
+	 * @return array|bool
1251
+	 * @throws EE_Error
1252
+	 * @throws InvalidArgumentException
1253
+	 * @throws ReflectionException
1254
+	 * @throws InvalidDataTypeException
1255
+	 * @throws InvalidInterfaceException
1256
+	 */
1257
+	public function update_based_on_payments()
1258
+	{
1259
+		EE_Error::doing_it_wrong(
1260
+			__CLASS__ . '::' . __FUNCTION__,
1261
+			sprintf(
1262
+				__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1263
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1264
+			),
1265
+			'4.6.0'
1266
+		);
1267
+		/** @type EE_Transaction_Processor $transaction_processor */
1268
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1269
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1270
+	}
1271
+
1272
+
1273
+	/**
1274
+	 * @return string
1275
+	 */
1276
+	public function old_txn_status()
1277
+	{
1278
+		return $this->_old_txn_status;
1279
+	}
1280
+
1281
+
1282
+	/**
1283
+	 * @param string $old_txn_status
1284
+	 */
1285
+	public function set_old_txn_status($old_txn_status)
1286
+	{
1287
+		// only set the first time
1288
+		if ($this->_old_txn_status === null) {
1289
+			$this->_old_txn_status = $old_txn_status;
1290
+		}
1291
+	}
1292
+
1293
+
1294
+	/**
1295
+	 * reg_status_updated
1296
+	 *
1297
+	 * @return bool
1298
+	 * @throws EE_Error
1299
+	 * @throws InvalidArgumentException
1300
+	 * @throws InvalidDataTypeException
1301
+	 * @throws InvalidInterfaceException
1302
+	 * @throws ReflectionException
1303
+	 */
1304
+	public function txn_status_updated()
1305
+	{
1306
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1307
+	}
1308
+
1309
+
1310
+	/**
1311
+	 * _reg_steps_completed
1312
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1313
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1314
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1315
+	 *
1316
+	 * @param string $reg_step_slug
1317
+	 * @param bool   $check_all
1318
+	 * @return bool|int
1319
+	 * @throws EE_Error
1320
+	 * @throws InvalidArgumentException
1321
+	 * @throws InvalidDataTypeException
1322
+	 * @throws InvalidInterfaceException
1323
+	 * @throws ReflectionException
1324
+	 */
1325
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1326
+	{
1327
+		$reg_steps = $this->reg_steps();
1328
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1329
+			return false;
1330
+		}
1331
+		// loop thru reg steps array)
1332
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1333
+			// if NOT checking ALL steps (only checking one step)
1334
+			if (! $check_all) {
1335
+				// and this is the one
1336
+				if ($slug === $reg_step_slug) {
1337
+					return $reg_step_completed;
1338
+				}
1339
+				// skip to next reg step in loop
1340
+				continue;
1341
+			}
1342
+			// $check_all must be true, else we would never have gotten to this point
1343
+			if ($slug === $reg_step_slug) {
1344
+				// if we reach this point, then we are testing either:
1345
+				// all_reg_steps_completed_except() or
1346
+				// all_reg_steps_completed_except_final_step(),
1347
+				// and since this is the reg step EXCEPTION being tested
1348
+				// we want to return true (yes true) if this reg step is NOT completed
1349
+				// ie: "is everything completed except the final step?"
1350
+				// "that is correct... the final step is not completed, but all others are."
1351
+				return $reg_step_completed !== true;
1352
+			}
1353
+			if ($reg_step_completed !== true) {
1354
+				// if any reg step is NOT completed, then ALL steps are not completed
1355
+				return false;
1356
+			}
1357
+		}
1358
+		return true;
1359
+	}
1360
+
1361
+
1362
+	/**
1363
+	 * all_reg_steps_completed
1364
+	 * returns:
1365
+	 *    true if ALL reg steps have been marked as completed
1366
+	 *        or false if any step is not completed
1367
+	 *
1368
+	 * @return bool
1369
+	 * @throws EE_Error
1370
+	 * @throws InvalidArgumentException
1371
+	 * @throws InvalidDataTypeException
1372
+	 * @throws InvalidInterfaceException
1373
+	 * @throws ReflectionException
1374
+	 */
1375
+	public function all_reg_steps_completed()
1376
+	{
1377
+		return $this->_reg_steps_completed();
1378
+	}
1379
+
1380
+
1381
+	/**
1382
+	 * all_reg_steps_completed_except
1383
+	 * returns:
1384
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1385
+	 *        or false if any other step is not completed
1386
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1387
+	 *
1388
+	 * @param string $exception
1389
+	 * @return bool
1390
+	 * @throws EE_Error
1391
+	 * @throws InvalidArgumentException
1392
+	 * @throws InvalidDataTypeException
1393
+	 * @throws InvalidInterfaceException
1394
+	 * @throws ReflectionException
1395
+	 */
1396
+	public function all_reg_steps_completed_except($exception = '')
1397
+	{
1398
+		return $this->_reg_steps_completed($exception);
1399
+	}
1400
+
1401
+
1402
+	/**
1403
+	 * all_reg_steps_completed_except
1404
+	 * returns:
1405
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1406
+	 *        or false if any step is not completed
1407
+	 *    or false if ALL steps are completed including the final step !!!
1408
+	 *
1409
+	 * @return bool
1410
+	 * @throws EE_Error
1411
+	 * @throws InvalidArgumentException
1412
+	 * @throws InvalidDataTypeException
1413
+	 * @throws InvalidInterfaceException
1414
+	 * @throws ReflectionException
1415
+	 */
1416
+	public function all_reg_steps_completed_except_final_step()
1417
+	{
1418
+		return $this->_reg_steps_completed('finalize_registration');
1419
+	}
1420
+
1421
+
1422
+	/**
1423
+	 * reg_step_completed
1424
+	 * returns:
1425
+	 *    true if a specific reg step has been marked as completed
1426
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1427
+	 *    or false if it has not yet been initialized
1428
+	 *
1429
+	 * @param string $reg_step_slug
1430
+	 * @return bool|int
1431
+	 * @throws EE_Error
1432
+	 * @throws InvalidArgumentException
1433
+	 * @throws InvalidDataTypeException
1434
+	 * @throws InvalidInterfaceException
1435
+	 * @throws ReflectionException
1436
+	 */
1437
+	public function reg_step_completed($reg_step_slug)
1438
+	{
1439
+		return $this->_reg_steps_completed($reg_step_slug, false);
1440
+	}
1441
+
1442
+
1443
+	/**
1444
+	 * completed_final_reg_step
1445
+	 * returns:
1446
+	 *    true if the finalize_registration reg step has been marked as completed
1447
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1448
+	 *    or false if it has not yet been initialized
1449
+	 *
1450
+	 * @return bool|int
1451
+	 * @throws EE_Error
1452
+	 * @throws InvalidArgumentException
1453
+	 * @throws InvalidDataTypeException
1454
+	 * @throws InvalidInterfaceException
1455
+	 * @throws ReflectionException
1456
+	 */
1457
+	public function final_reg_step_completed()
1458
+	{
1459
+		return $this->_reg_steps_completed('finalize_registration', false);
1460
+	}
1461
+
1462
+
1463
+	/**
1464
+	 * set_reg_step_initiated
1465
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1466
+	 *
1467
+	 * @param string $reg_step_slug
1468
+	 * @return boolean
1469
+	 * @throws EE_Error
1470
+	 * @throws InvalidArgumentException
1471
+	 * @throws InvalidDataTypeException
1472
+	 * @throws InvalidInterfaceException
1473
+	 * @throws ReflectionException
1474
+	 */
1475
+	public function set_reg_step_initiated($reg_step_slug)
1476
+	{
1477
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1478
+	}
1479
+
1480
+
1481
+	/**
1482
+	 * set_reg_step_completed
1483
+	 * given a valid TXN_reg_step, this sets the step as completed
1484
+	 *
1485
+	 * @param string $reg_step_slug
1486
+	 * @return boolean
1487
+	 * @throws EE_Error
1488
+	 * @throws InvalidArgumentException
1489
+	 * @throws InvalidDataTypeException
1490
+	 * @throws InvalidInterfaceException
1491
+	 * @throws ReflectionException
1492
+	 */
1493
+	public function set_reg_step_completed($reg_step_slug)
1494
+	{
1495
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1496
+	}
1497
+
1498
+
1499
+	/**
1500
+	 * set_reg_step_completed
1501
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1502
+	 *
1503
+	 * @param string $reg_step_slug
1504
+	 * @return boolean
1505
+	 * @throws EE_Error
1506
+	 * @throws InvalidArgumentException
1507
+	 * @throws InvalidDataTypeException
1508
+	 * @throws InvalidInterfaceException
1509
+	 * @throws ReflectionException
1510
+	 */
1511
+	public function set_reg_step_not_completed($reg_step_slug)
1512
+	{
1513
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1514
+	}
1515
+
1516
+
1517
+	/**
1518
+	 * set_reg_step_completed
1519
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1520
+	 *
1521
+	 * @param  string      $reg_step_slug
1522
+	 * @param  boolean|int $status
1523
+	 * @return boolean
1524
+	 * @throws EE_Error
1525
+	 * @throws InvalidArgumentException
1526
+	 * @throws InvalidDataTypeException
1527
+	 * @throws InvalidInterfaceException
1528
+	 * @throws ReflectionException
1529
+	 */
1530
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1531
+	{
1532
+		// validate status
1533
+		$status = is_bool($status) || is_int($status) ? $status : false;
1534
+		// get reg steps array
1535
+		$txn_reg_steps = $this->reg_steps();
1536
+		// if reg step does NOT exist
1537
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1538
+			return false;
1539
+		}
1540
+		// if  we're trying to complete a step that is already completed
1541
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1542
+			return true;
1543
+		}
1544
+		// if  we're trying to complete a step that hasn't even started
1545
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1546
+			return false;
1547
+		}
1548
+		// if current status value matches the incoming value (no change)
1549
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1550
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1551
+			// this will happen in cases where multiple AJAX requests occur during the same step
1552
+			return true;
1553
+		}
1554
+		// if we're trying to set a start time, but it has already been set...
1555
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1556
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1557
+			return true;
1558
+		}
1559
+		// update completed status
1560
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1561
+		$this->set_reg_steps($txn_reg_steps);
1562
+		$this->save();
1563
+		return true;
1564
+	}
1565
+
1566
+
1567
+	/**
1568
+	 * remove_reg_step
1569
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1570
+	 * the reg step from the TXN reg step array
1571
+	 *
1572
+	 * @param string $reg_step_slug
1573
+	 * @return void
1574
+	 * @throws EE_Error
1575
+	 * @throws InvalidArgumentException
1576
+	 * @throws InvalidDataTypeException
1577
+	 * @throws InvalidInterfaceException
1578
+	 * @throws ReflectionException
1579
+	 */
1580
+	public function remove_reg_step($reg_step_slug)
1581
+	{
1582
+		// get reg steps array
1583
+		$txn_reg_steps = $this->reg_steps();
1584
+		unset($txn_reg_steps[ $reg_step_slug ]);
1585
+		$this->set_reg_steps($txn_reg_steps);
1586
+	}
1587
+
1588
+
1589
+	/**
1590
+	 * toggle_failed_transaction_status
1591
+	 * upgrades a TXNs status from failed to abandoned,
1592
+	 * meaning that contact information has been captured for at least one registrant
1593
+	 *
1594
+	 * @param bool $save
1595
+	 * @return bool
1596
+	 * @throws EE_Error
1597
+	 * @throws InvalidArgumentException
1598
+	 * @throws InvalidDataTypeException
1599
+	 * @throws InvalidInterfaceException
1600
+	 * @throws ReflectionException
1601
+	 */
1602
+	public function toggle_failed_transaction_status($save = true)
1603
+	{
1604
+		// if TXN status is still set as "failed"...
1605
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1606
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1607
+			if ($save) {
1608
+				$this->save();
1609
+			}
1610
+			return true;
1611
+		}
1612
+		return false;
1613
+	}
1614
+
1615
+
1616
+	/**
1617
+	 * toggle_abandoned_transaction_status
1618
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1619
+	 *
1620
+	 * @return bool
1621
+	 * @throws EE_Error
1622
+	 * @throws InvalidArgumentException
1623
+	 * @throws InvalidDataTypeException
1624
+	 * @throws InvalidInterfaceException
1625
+	 * @throws ReflectionException
1626
+	 */
1627
+	public function toggle_abandoned_transaction_status()
1628
+	{
1629
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1630
+		$txn_status = $this->status_ID();
1631
+		if (
1632
+			$txn_status === EEM_Transaction::failed_status_code
1633
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1634
+		) {
1635
+			// if a contact record for the primary registrant has been created
1636
+			if (
1637
+				$this->primary_registration() instanceof EE_Registration
1638
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1639
+			) {
1640
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1641
+			} else {
1642
+				// no contact record? yer abandoned!
1643
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1644
+			}
1645
+			return true;
1646
+		}
1647
+		return false;
1648
+	}
1649
+
1650
+
1651
+	/**
1652
+	 * checks if an Abandoned TXN has any related payments, and if so,
1653
+	 * updates the TXN status based on the amount paid
1654
+	 *
1655
+	 * @throws EE_Error
1656
+	 * @throws InvalidDataTypeException
1657
+	 * @throws InvalidInterfaceException
1658
+	 * @throws InvalidArgumentException
1659
+	 * @throws RuntimeException
1660
+	 * @throws ReflectionException
1661
+	 */
1662
+	public function verify_abandoned_transaction_status()
1663
+	{
1664
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1665
+			return;
1666
+		}
1667
+		$payments = $this->get_many_related('Payment');
1668
+		if (! empty($payments)) {
1669
+			foreach ($payments as $payment) {
1670
+				if ($payment instanceof EE_Payment) {
1671
+					// kk this TXN should NOT be abandoned
1672
+					$this->update_status_based_on_total_paid();
1673
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1674
+						EE_Error::add_attention(
1675
+							sprintf(
1676
+								esc_html__(
1677
+									'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1678
+									'event_espresso'
1679
+								),
1680
+								$this->ID(),
1681
+								$this->pretty_status()
1682
+							)
1683
+						);
1684
+					}
1685
+					// get final reg step status
1686
+					$finalized = $this->final_reg_step_completed();
1687
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1688
+					// but has not yet been fully completed (TRUE)
1689
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1690
+						$this->set_reg_step_completed('finalize_registration');
1691
+						$this->save();
1692
+					}
1693
+				}
1694
+			}
1695
+		}
1696
+	}
1697
+
1698
+
1699
+	/**
1700
+	 * @since 4.10.4.p
1701
+	 * @throws EE_Error
1702
+	 * @throws InvalidArgumentException
1703
+	 * @throws InvalidDataTypeException
1704
+	 * @throws InvalidInterfaceException
1705
+	 * @throws ReflectionException
1706
+	 * @throws RuntimeException
1707
+	 */
1708
+	public function recalculateLineItems()
1709
+	{
1710
+		$total_line_item = $this->total_line_item(false);
1711
+		if ($total_line_item instanceof EE_Line_Item) {
1712
+			EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1713
+			return EEH_Line_Item::apply_taxes($total_line_item, true);
1714
+		}
1715
+		return false;
1716
+	}
1717 1717
 }
Please login to merge, or discard this patch.
Spacing   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@  discard block
 block discarded – undo
46 46
         $txn = $has_object
47 47
             ? $has_object
48 48
             : new self($props_n_values, false, $timezone, $date_formats);
49
-        if (! $has_object) {
49
+        if ( ! $has_object) {
50 50
             $txn->set_old_txn_status($txn->status_ID());
51 51
         }
52 52
         return $txn;
@@ -86,7 +86,7 @@  discard block
 block discarded – undo
86 86
     public function lock()
87 87
     {
88 88
         // attempt to set lock, but if that fails...
89
-        if (! $this->add_extra_meta('lock', time(), true)) {
89
+        if ( ! $this->add_extra_meta('lock', time(), true)) {
90 90
             // then attempt to remove the lock in case it is expired
91 91
             if ($this->_remove_expired_lock()) {
92 92
                 // if removal was successful, then try setting lock again
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
     public function is_locked()
143 143
     {
144 144
         // if TXN is not locked, then return false immediately
145
-        if (! $this->_get_lock()) {
145
+        if ( ! $this->_get_lock()) {
146 146
             return false;
147 147
         }
148 148
         // if not, then let's try and remove the lock in case it's expired...
@@ -640,7 +640,7 @@  discard block
 block discarded – undo
640 640
                 $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
641 641
                 break;
642 642
         }
643
-        return $icon . $status[ $this->status_ID() ];
643
+        return $icon.$status[$this->status_ID()];
644 644
     }
645 645
 
646 646
 
@@ -777,7 +777,7 @@  discard block
 block discarded – undo
777 777
     public function invoice_url($type = 'html')
778 778
     {
779 779
         $REG = $this->primary_registration();
780
-        if (! $REG instanceof EE_Registration) {
780
+        if ( ! $REG instanceof EE_Registration) {
781 781
             return '';
782 782
         }
783 783
         return $REG->invoice_url($type);
@@ -828,7 +828,7 @@  discard block
 block discarded – undo
828 828
     public function receipt_url($type = 'html')
829 829
     {
830 830
         $REG = $this->primary_registration();
831
-        if (! $REG instanceof EE_Registration) {
831
+        if ( ! $REG instanceof EE_Registration) {
832 832
             return '';
833 833
         }
834 834
         return $REG->receipt_url($type);
@@ -1074,7 +1074,7 @@  discard block
 block discarded – undo
1074 1074
     public function billing_info()
1075 1075
     {
1076 1076
         $payment_method = $this->payment_method();
1077
-        if (! $payment_method) {
1077
+        if ( ! $payment_method) {
1078 1078
             EE_Error::add_error(
1079 1079
                 __(
1080 1080
                     'Could not find billing info for transaction because no gateway has been used for it yet',
@@ -1087,7 +1087,7 @@  discard block
 block discarded – undo
1087 1087
             return null;
1088 1088
         }
1089 1089
         $primary_reg = $this->primary_registration();
1090
-        if (! $primary_reg) {
1090
+        if ( ! $primary_reg) {
1091 1091
             EE_Error::add_error(
1092 1092
                 __(
1093 1093
                     'Cannot get billing info for gateway %s on transaction because no primary registration exists',
@@ -1100,7 +1100,7 @@  discard block
 block discarded – undo
1100 1100
             return null;
1101 1101
         }
1102 1102
         $attendee = $primary_reg->attendee();
1103
-        if (! $attendee) {
1103
+        if ( ! $attendee) {
1104 1104
             EE_Error::add_error(
1105 1105
                 __(
1106 1106
                     'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
@@ -1257,7 +1257,7 @@  discard block
 block discarded – undo
1257 1257
     public function update_based_on_payments()
1258 1258
     {
1259 1259
         EE_Error::doing_it_wrong(
1260
-            __CLASS__ . '::' . __FUNCTION__,
1260
+            __CLASS__.'::'.__FUNCTION__,
1261 1261
             sprintf(
1262 1262
                 __('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1263 1263
                 'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
@@ -1325,13 +1325,13 @@  discard block
 block discarded – undo
1325 1325
     private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1326 1326
     {
1327 1327
         $reg_steps = $this->reg_steps();
1328
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1328
+        if ( ! is_array($reg_steps) || empty($reg_steps)) {
1329 1329
             return false;
1330 1330
         }
1331 1331
         // loop thru reg steps array)
1332 1332
         foreach ($reg_steps as $slug => $reg_step_completed) {
1333 1333
             // if NOT checking ALL steps (only checking one step)
1334
-            if (! $check_all) {
1334
+            if ( ! $check_all) {
1335 1335
                 // and this is the one
1336 1336
                 if ($slug === $reg_step_slug) {
1337 1337
                     return $reg_step_completed;
@@ -1534,30 +1534,30 @@  discard block
 block discarded – undo
1534 1534
         // get reg steps array
1535 1535
         $txn_reg_steps = $this->reg_steps();
1536 1536
         // if reg step does NOT exist
1537
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1537
+        if ( ! isset($txn_reg_steps[$reg_step_slug])) {
1538 1538
             return false;
1539 1539
         }
1540 1540
         // if  we're trying to complete a step that is already completed
1541
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1541
+        if ($txn_reg_steps[$reg_step_slug] === true) {
1542 1542
             return true;
1543 1543
         }
1544 1544
         // if  we're trying to complete a step that hasn't even started
1545
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1545
+        if ($status === true && $txn_reg_steps[$reg_step_slug] === false) {
1546 1546
             return false;
1547 1547
         }
1548 1548
         // if current status value matches the incoming value (no change)
1549 1549
         // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1550
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1550
+        if ((int) $txn_reg_steps[$reg_step_slug] === (int) $status) {
1551 1551
             // this will happen in cases where multiple AJAX requests occur during the same step
1552 1552
             return true;
1553 1553
         }
1554 1554
         // if we're trying to set a start time, but it has already been set...
1555
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1555
+        if (is_numeric($status) && is_numeric($txn_reg_steps[$reg_step_slug])) {
1556 1556
             // skip the update below, but don't return FALSE so that errors won't be displayed
1557 1557
             return true;
1558 1558
         }
1559 1559
         // update completed status
1560
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1560
+        $txn_reg_steps[$reg_step_slug] = $status;
1561 1561
         $this->set_reg_steps($txn_reg_steps);
1562 1562
         $this->save();
1563 1563
         return true;
@@ -1581,7 +1581,7 @@  discard block
 block discarded – undo
1581 1581
     {
1582 1582
         // get reg steps array
1583 1583
         $txn_reg_steps = $this->reg_steps();
1584
-        unset($txn_reg_steps[ $reg_step_slug ]);
1584
+        unset($txn_reg_steps[$reg_step_slug]);
1585 1585
         $this->set_reg_steps($txn_reg_steps);
1586 1586
     }
1587 1587
 
@@ -1665,12 +1665,12 @@  discard block
 block discarded – undo
1665 1665
             return;
1666 1666
         }
1667 1667
         $payments = $this->get_many_related('Payment');
1668
-        if (! empty($payments)) {
1668
+        if ( ! empty($payments)) {
1669 1669
             foreach ($payments as $payment) {
1670 1670
                 if ($payment instanceof EE_Payment) {
1671 1671
                     // kk this TXN should NOT be abandoned
1672 1672
                     $this->update_status_based_on_total_paid();
1673
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1673
+                    if ( ! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1674 1674
                         EE_Error::add_attention(
1675 1675
                             sprintf(
1676 1676
                                 esc_html__(
Please login to merge, or discard this patch.
core/db_classes/EE_Line_Item.class.php 2 patches
Indentation   +1588 added lines, -1588 removed lines patch added patch discarded remove patch
@@ -16,1592 +16,1592 @@
 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_Line_Item[]
642
-     * @throws EE_Error
643
-     * @throws InvalidArgumentException
644
-     * @throws InvalidDataTypeException
645
-     * @throws InvalidInterfaceException
646
-     * @throws ReflectionException
647
-     */
648
-    public function children(array $query_params = []): array
649
-    {
650
-        if ($this->ID()) {
651
-            // ensure where params are an array
652
-            $query_params[0] = $query_params[0] ?? [];
653
-            // add defaults for line item parent and orderby
654
-            $query_params[0] += ['LIN_parent' => $this->ID()];
655
-            $query_params += ['order_by' => ['LIN_order' => 'ASC']];
656
-            return $this->get_model()->get_all($query_params);
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
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1511
-    }
1512
-
1513
-
1514
-    /**
1515
-     * If this item has an ID, then this saves it again to update the db
1516
-     *
1517
-     * @return int count of items saved
1518
-     * @throws EE_Error
1519
-     * @throws InvalidArgumentException
1520
-     * @throws InvalidDataTypeException
1521
-     * @throws InvalidInterfaceException
1522
-     * @throws ReflectionException
1523
-     */
1524
-    public function maybe_save()
1525
-    {
1526
-        if ($this->ID()) {
1527
-            return $this->save();
1528
-        }
1529
-        return false;
1530
-    }
1531
-
1532
-
1533
-    /**
1534
-     * clears the cached children and parent from the line item
1535
-     *
1536
-     * @return void
1537
-     */
1538
-    public function clear_related_line_item_cache()
1539
-    {
1540
-        $this->_children = array();
1541
-        $this->_parent = null;
1542
-    }
1543
-
1544
-
1545
-    /**
1546
-     * @param bool $raw
1547
-     * @return int
1548
-     * @throws EE_Error
1549
-     * @throws InvalidArgumentException
1550
-     * @throws InvalidDataTypeException
1551
-     * @throws InvalidInterfaceException
1552
-     * @throws ReflectionException
1553
-     */
1554
-    public function timestamp($raw = false)
1555
-    {
1556
-        return $raw
1557
-            ? $this->get_raw('LIN_timestamp')
1558
-            : $this->get('LIN_timestamp');
1559
-    }
1560
-
1561
-
1562
-
1563
-
1564
-    /************************* DEPRECATED *************************/
1565
-    /**
1566
-     * @deprecated 4.6.0
1567
-     * @param string $type one of the constants on EEM_Line_Item
1568
-     * @return EE_Line_Item[]
1569
-     * @throws EE_Error
1570
-     */
1571
-    protected function _get_descendants_of_type($type)
1572
-    {
1573
-        EE_Error::doing_it_wrong(
1574
-            'EE_Line_Item::_get_descendants_of_type()',
1575
-            sprintf(
1576
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1577
-                'EEH_Line_Item::get_descendants_of_type()'
1578
-            ),
1579
-            '4.6.0'
1580
-        );
1581
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1582
-    }
1583
-
1584
-
1585
-    /**
1586
-     * @deprecated 4.6.0
1587
-     * @param string $type like one of the EEM_Line_Item::type_*
1588
-     * @return EE_Line_Item
1589
-     * @throws EE_Error
1590
-     * @throws InvalidArgumentException
1591
-     * @throws InvalidDataTypeException
1592
-     * @throws InvalidInterfaceException
1593
-     * @throws ReflectionException
1594
-     */
1595
-    public function get_nearest_descendant_of_type($type)
1596
-    {
1597
-        EE_Error::doing_it_wrong(
1598
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1599
-            sprintf(
1600
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1601
-                'EEH_Line_Item::get_nearest_descendant_of_type()'
1602
-            ),
1603
-            '4.6.0'
1604
-        );
1605
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1606
-    }
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_Line_Item[]
642
+	 * @throws EE_Error
643
+	 * @throws InvalidArgumentException
644
+	 * @throws InvalidDataTypeException
645
+	 * @throws InvalidInterfaceException
646
+	 * @throws ReflectionException
647
+	 */
648
+	public function children(array $query_params = []): array
649
+	{
650
+		if ($this->ID()) {
651
+			// ensure where params are an array
652
+			$query_params[0] = $query_params[0] ?? [];
653
+			// add defaults for line item parent and orderby
654
+			$query_params[0] += ['LIN_parent' => $this->ID()];
655
+			$query_params += ['order_by' => ['LIN_order' => 'ASC']];
656
+			return $this->get_model()->get_all($query_params);
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
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1511
+	}
1512
+
1513
+
1514
+	/**
1515
+	 * If this item has an ID, then this saves it again to update the db
1516
+	 *
1517
+	 * @return int count of items saved
1518
+	 * @throws EE_Error
1519
+	 * @throws InvalidArgumentException
1520
+	 * @throws InvalidDataTypeException
1521
+	 * @throws InvalidInterfaceException
1522
+	 * @throws ReflectionException
1523
+	 */
1524
+	public function maybe_save()
1525
+	{
1526
+		if ($this->ID()) {
1527
+			return $this->save();
1528
+		}
1529
+		return false;
1530
+	}
1531
+
1532
+
1533
+	/**
1534
+	 * clears the cached children and parent from the line item
1535
+	 *
1536
+	 * @return void
1537
+	 */
1538
+	public function clear_related_line_item_cache()
1539
+	{
1540
+		$this->_children = array();
1541
+		$this->_parent = null;
1542
+	}
1543
+
1544
+
1545
+	/**
1546
+	 * @param bool $raw
1547
+	 * @return int
1548
+	 * @throws EE_Error
1549
+	 * @throws InvalidArgumentException
1550
+	 * @throws InvalidDataTypeException
1551
+	 * @throws InvalidInterfaceException
1552
+	 * @throws ReflectionException
1553
+	 */
1554
+	public function timestamp($raw = false)
1555
+	{
1556
+		return $raw
1557
+			? $this->get_raw('LIN_timestamp')
1558
+			: $this->get('LIN_timestamp');
1559
+	}
1560
+
1561
+
1562
+
1563
+
1564
+	/************************* DEPRECATED *************************/
1565
+	/**
1566
+	 * @deprecated 4.6.0
1567
+	 * @param string $type one of the constants on EEM_Line_Item
1568
+	 * @return EE_Line_Item[]
1569
+	 * @throws EE_Error
1570
+	 */
1571
+	protected function _get_descendants_of_type($type)
1572
+	{
1573
+		EE_Error::doing_it_wrong(
1574
+			'EE_Line_Item::_get_descendants_of_type()',
1575
+			sprintf(
1576
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1577
+				'EEH_Line_Item::get_descendants_of_type()'
1578
+			),
1579
+			'4.6.0'
1580
+		);
1581
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1582
+	}
1583
+
1584
+
1585
+	/**
1586
+	 * @deprecated 4.6.0
1587
+	 * @param string $type like one of the EEM_Line_Item::type_*
1588
+	 * @return EE_Line_Item
1589
+	 * @throws EE_Error
1590
+	 * @throws InvalidArgumentException
1591
+	 * @throws InvalidDataTypeException
1592
+	 * @throws InvalidInterfaceException
1593
+	 * @throws ReflectionException
1594
+	 */
1595
+	public function get_nearest_descendant_of_type($type)
1596
+	{
1597
+		EE_Error::doing_it_wrong(
1598
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1599
+			sprintf(
1600
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1601
+				'EEH_Line_Item::get_nearest_descendant_of_type()'
1602
+			),
1603
+			'4.6.0'
1604
+		);
1605
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1606
+	}
1607 1607
 }
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
             $query_params += ['order_by' => ['LIN_order' => 'ASC']];
656 656
             return $this->get_model()->get_all($query_params);
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/EE_Dependency_Map.core.php 1 patch
Indentation   +1063 added lines, -1063 removed lines patch added patch discarded remove patch
@@ -22,1067 +22,1067 @@
 block discarded – undo
22 22
 class EE_Dependency_Map
23 23
 {
24 24
 
25
-    /**
26
-     * This means that the requested class dependency is not present in the dependency map
27
-     */
28
-    const not_registered = 0;
29
-
30
-    /**
31
-     * This instructs class loaders to ALWAYS return a newly instantiated object for the requested class.
32
-     */
33
-    const load_new_object = 1;
34
-
35
-    /**
36
-     * This instructs class loaders to return a previously instantiated and cached object for the requested class.
37
-     * IF a previously instantiated object does not exist, a new one will be created and added to the cache.
38
-     */
39
-    const load_from_cache = 2;
40
-
41
-    /**
42
-     * When registering a dependency,
43
-     * this indicates to keep any existing dependencies that already exist,
44
-     * and simply discard any new dependencies declared in the incoming data
45
-     */
46
-    const KEEP_EXISTING_DEPENDENCIES = 0;
47
-
48
-    /**
49
-     * When registering a dependency,
50
-     * this indicates to overwrite any existing dependencies that already exist using the incoming data
51
-     */
52
-    const OVERWRITE_DEPENDENCIES = 1;
53
-
54
-    /**
55
-     * @type EE_Dependency_Map $_instance
56
-     */
57
-    protected static $_instance;
58
-
59
-    /**
60
-     * @var ClassInterfaceCache $class_cache
61
-     */
62
-    private $class_cache;
63
-
64
-    /**
65
-     * @type RequestInterface $request
66
-     */
67
-    protected $request;
68
-
69
-    /**
70
-     * @type LegacyRequestInterface $legacy_request
71
-     */
72
-    protected $legacy_request;
73
-
74
-    /**
75
-     * @type ResponseInterface $response
76
-     */
77
-    protected $response;
78
-
79
-    /**
80
-     * @type LoaderInterface $loader
81
-     */
82
-    protected $loader;
83
-
84
-    /**
85
-     * @type array $_dependency_map
86
-     */
87
-    protected $_dependency_map = [];
88
-
89
-    /**
90
-     * @type array $_class_loaders
91
-     */
92
-    protected $_class_loaders = [];
93
-
94
-
95
-    /**
96
-     * EE_Dependency_Map constructor.
97
-     *
98
-     * @param ClassInterfaceCache $class_cache
99
-     */
100
-    protected function __construct(ClassInterfaceCache $class_cache)
101
-    {
102
-        $this->class_cache = $class_cache;
103
-        do_action('EE_Dependency_Map____construct', $this);
104
-    }
105
-
106
-
107
-    /**
108
-     * @return void
109
-     * @throws InvalidAliasException
110
-     */
111
-    public function initialize()
112
-    {
113
-        $this->_register_core_dependencies();
114
-        $this->_register_core_class_loaders();
115
-        $this->_register_core_aliases();
116
-    }
117
-
118
-
119
-    /**
120
-     * @singleton method used to instantiate class object
121
-     * @param ClassInterfaceCache|null $class_cache
122
-     * @return EE_Dependency_Map
123
-     */
124
-    public static function instance(ClassInterfaceCache $class_cache = null)
125
-    {
126
-        // check if class object is instantiated, and instantiated properly
127
-        if (
128
-            ! EE_Dependency_Map::$_instance instanceof EE_Dependency_Map
129
-            && $class_cache instanceof ClassInterfaceCache
130
-        ) {
131
-            EE_Dependency_Map::$_instance = new EE_Dependency_Map($class_cache);
132
-        }
133
-        return EE_Dependency_Map::$_instance;
134
-    }
135
-
136
-
137
-    /**
138
-     * @param RequestInterface $request
139
-     */
140
-    public function setRequest(RequestInterface $request)
141
-    {
142
-        $this->request = $request;
143
-    }
144
-
145
-
146
-    /**
147
-     * @param LegacyRequestInterface $legacy_request
148
-     */
149
-    public function setLegacyRequest(LegacyRequestInterface $legacy_request)
150
-    {
151
-        $this->legacy_request = $legacy_request;
152
-    }
153
-
154
-
155
-    /**
156
-     * @param ResponseInterface $response
157
-     */
158
-    public function setResponse(ResponseInterface $response)
159
-    {
160
-        $this->response = $response;
161
-    }
162
-
163
-
164
-    /**
165
-     * @param LoaderInterface $loader
166
-     */
167
-    public function setLoader(LoaderInterface $loader)
168
-    {
169
-        $this->loader = $loader;
170
-    }
171
-
172
-
173
-    /**
174
-     * @param string $class
175
-     * @param array  $dependencies
176
-     * @param int    $overwrite
177
-     * @return bool
178
-     */
179
-    public static function register_dependencies(
180
-        $class,
181
-        array $dependencies,
182
-        $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
183
-    ) {
184
-        return EE_Dependency_Map::$_instance->registerDependencies($class, $dependencies, $overwrite);
185
-    }
186
-
187
-
188
-    /**
189
-     * Assigns an array of class names and corresponding load sources (new or cached)
190
-     * to the class specified by the first parameter.
191
-     * IMPORTANT !!!
192
-     * The order of elements in the incoming $dependencies array MUST match
193
-     * the order of the constructor parameters for the class in question.
194
-     * This is especially important when overriding any existing dependencies that are registered.
195
-     * the third parameter controls whether any duplicate dependencies are overwritten or not.
196
-     *
197
-     * @param string $class
198
-     * @param array  $dependencies
199
-     * @param int    $overwrite
200
-     * @return bool
201
-     */
202
-    public function registerDependencies(
203
-        $class,
204
-        array $dependencies,
205
-        $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
206
-    ) {
207
-        $class      = trim($class, '\\');
208
-        $registered = false;
209
-        if (empty(EE_Dependency_Map::$_instance->_dependency_map[ $class ])) {
210
-            EE_Dependency_Map::$_instance->_dependency_map[ $class ] = [];
211
-        }
212
-        // we need to make sure that any aliases used when registering a dependency
213
-        // get resolved to the correct class name
214
-        foreach ($dependencies as $dependency => $load_source) {
215
-            $alias = EE_Dependency_Map::$_instance->getFqnForAlias($dependency);
216
-            if (
217
-                $overwrite === EE_Dependency_Map::OVERWRITE_DEPENDENCIES
218
-                || ! isset(EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $alias ])
219
-            ) {
220
-                unset($dependencies[ $dependency ]);
221
-                $dependencies[ $alias ] = $load_source;
222
-                $registered             = true;
223
-            }
224
-        }
225
-        // now add our two lists of dependencies together.
226
-        // using Union (+=) favours the arrays in precedence from left to right,
227
-        // so $dependencies is NOT overwritten because it is listed first
228
-        // ie: with A = B + C, entries in B take precedence over duplicate entries in C
229
-        // Union is way faster than array_merge() but should be used with caution...
230
-        // especially with numerically indexed arrays
231
-        $dependencies += EE_Dependency_Map::$_instance->_dependency_map[ $class ];
232
-        // now we need to ensure that the resulting dependencies
233
-        // array only has the entries that are required for the class
234
-        // so first count how many dependencies were originally registered for the class
235
-        $dependency_count = count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]);
236
-        // if that count is non-zero (meaning dependencies were already registered)
237
-        EE_Dependency_Map::$_instance->_dependency_map[ $class ] = $dependency_count
238
-            // then truncate the  final array to match that count
239
-            ? array_slice($dependencies, 0, $dependency_count)
240
-            // otherwise just take the incoming array because nothing previously existed
241
-            : $dependencies;
242
-        return $registered;
243
-    }
244
-
245
-
246
-    /**
247
-     * @param string $class_name
248
-     * @param string $loader
249
-     * @return bool
250
-     * @throws DomainException
251
-     */
252
-    public static function register_class_loader($class_name, $loader = 'load_core')
253
-    {
254
-        return EE_Dependency_Map::$_instance->registerClassLoader($class_name, $loader);
255
-    }
256
-
257
-
258
-    /**
259
-     * @param string $class_name
260
-     * @param string $loader
261
-     * @return bool
262
-     * @throws DomainException
263
-     */
264
-    public function registerClassLoader($class_name, $loader = 'load_core')
265
-    {
266
-        if (! $loader instanceof Closure && strpos($class_name, '\\') !== false) {
267
-            throw new DomainException(
268
-                esc_html__('Don\'t use class loaders for FQCNs.', 'event_espresso')
269
-            );
270
-        }
271
-        // check that loader is callable or method starts with "load_" and exists in EE_Registry
272
-        if (
273
-            ! is_callable($loader)
274
-            && (
275
-                strpos($loader, 'load_') !== 0
276
-                || ! method_exists('EE_Registry', $loader)
277
-            )
278
-        ) {
279
-            throw new DomainException(
280
-                sprintf(
281
-                    esc_html__(
282
-                        '"%1$s" is not a valid loader method on EE_Registry.',
283
-                        'event_espresso'
284
-                    ),
285
-                    $loader
286
-                )
287
-            );
288
-        }
289
-        $class_name = EE_Dependency_Map::$_instance->getFqnForAlias($class_name);
290
-        if (! isset(EE_Dependency_Map::$_instance->_class_loaders[ $class_name ])) {
291
-            EE_Dependency_Map::$_instance->_class_loaders[ $class_name ] = $loader;
292
-            return true;
293
-        }
294
-        return false;
295
-    }
296
-
297
-
298
-    /**
299
-     * @return array
300
-     */
301
-    public function dependency_map()
302
-    {
303
-        return $this->_dependency_map;
304
-    }
305
-
306
-
307
-    /**
308
-     * returns TRUE if dependency map contains a listing for the provided class name
309
-     *
310
-     * @param string $class_name
311
-     * @return boolean
312
-     */
313
-    public function has($class_name = '')
314
-    {
315
-        // all legacy models have the same dependencies
316
-        if (strpos($class_name, 'EEM_') === 0) {
317
-            $class_name = 'LEGACY_MODELS';
318
-        }
319
-        return isset($this->_dependency_map[ $class_name ]);
320
-    }
321
-
322
-
323
-    /**
324
-     * returns TRUE if dependency map contains a listing for the provided class name AND dependency
325
-     *
326
-     * @param string $class_name
327
-     * @param string $dependency
328
-     * @return bool
329
-     */
330
-    public function has_dependency_for_class($class_name = '', $dependency = '')
331
-    {
332
-        // all legacy models have the same dependencies
333
-        if (strpos($class_name, 'EEM_') === 0) {
334
-            $class_name = 'LEGACY_MODELS';
335
-        }
336
-        $dependency = $this->getFqnForAlias($dependency, $class_name);
337
-        return isset($this->_dependency_map[ $class_name ][ $dependency ]);
338
-    }
339
-
340
-
341
-    /**
342
-     * returns loading strategy for whether a previously cached dependency should be loaded or a new instance returned
343
-     *
344
-     * @param string $class_name
345
-     * @param string $dependency
346
-     * @return int
347
-     */
348
-    public function loading_strategy_for_class_dependency($class_name = '', $dependency = '')
349
-    {
350
-        // all legacy models have the same dependencies
351
-        if (strpos($class_name, 'EEM_') === 0) {
352
-            $class_name = 'LEGACY_MODELS';
353
-        }
354
-        $dependency = $this->getFqnForAlias($dependency);
355
-        return $this->has_dependency_for_class($class_name, $dependency)
356
-            ? $this->_dependency_map[ $class_name ][ $dependency ]
357
-            : EE_Dependency_Map::not_registered;
358
-    }
359
-
360
-
361
-    /**
362
-     * @param string $class_name
363
-     * @return string | Closure
364
-     */
365
-    public function class_loader($class_name)
366
-    {
367
-        // all legacy models use load_model()
368
-        if (strpos($class_name, 'EEM_') === 0) {
369
-            return 'load_model';
370
-        }
371
-        // EE_CPT_*_Strategy classes like EE_CPT_Event_Strategy, EE_CPT_Venue_Strategy, etc
372
-        // perform strpos() first to avoid loading regex every time we load a class
373
-        if (
374
-            strpos($class_name, 'EE_CPT_') === 0
375
-            && preg_match('/^EE_CPT_([a-zA-Z]+)_Strategy$/', $class_name)
376
-        ) {
377
-            return 'load_core';
378
-        }
379
-        $class_name = $this->getFqnForAlias($class_name);
380
-        return isset($this->_class_loaders[ $class_name ]) ? $this->_class_loaders[ $class_name ] : '';
381
-    }
382
-
383
-
384
-    /**
385
-     * @return array
386
-     */
387
-    public function class_loaders()
388
-    {
389
-        return $this->_class_loaders;
390
-    }
391
-
392
-
393
-    /**
394
-     * adds an alias for a classname
395
-     *
396
-     * @param string $fqcn      the class name that should be used (concrete class to replace interface)
397
-     * @param string $alias     the class name that would be type hinted for (abstract parent or interface)
398
-     * @param string $for_class the class that has the dependency (is type hinting for the interface)
399
-     * @throws InvalidAliasException
400
-     */
401
-    public function add_alias($fqcn, $alias, $for_class = '')
402
-    {
403
-        $this->class_cache->addAlias($fqcn, $alias, $for_class);
404
-    }
405
-
406
-
407
-    /**
408
-     * Returns TRUE if the provided fully qualified name IS an alias
409
-     * WHY?
410
-     * Because if a class is type hinting for a concretion,
411
-     * then why would we need to find another class to supply it?
412
-     * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
413
-     * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
414
-     * Don't go looking for some substitute.
415
-     * Whereas if a class is type hinting for an interface...
416
-     * then we need to find an actual class to use.
417
-     * So the interface IS the alias for some other FQN,
418
-     * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
419
-     * represents some other class.
420
-     *
421
-     * @param string $fqn
422
-     * @param string $for_class
423
-     * @return bool
424
-     */
425
-    public function isAlias($fqn = '', $for_class = '')
426
-    {
427
-        return $this->class_cache->isAlias($fqn, $for_class);
428
-    }
429
-
430
-
431
-    /**
432
-     * Returns a FQN for provided alias if one exists, otherwise returns the original $alias
433
-     * functions recursively, so that multiple aliases can be used to drill down to a FQN
434
-     *  for example:
435
-     *      if the following two entries were added to the _aliases array:
436
-     *          array(
437
-     *              'interface_alias'           => 'some\namespace\interface'
438
-     *              'some\namespace\interface'  => 'some\namespace\classname'
439
-     *          )
440
-     *      then one could use EE_Registry::instance()->create( 'interface_alias' )
441
-     *      to load an instance of 'some\namespace\classname'
442
-     *
443
-     * @param string $alias
444
-     * @param string $for_class
445
-     * @return string
446
-     */
447
-    public function getFqnForAlias($alias = '', $for_class = '')
448
-    {
449
-        return (string) $this->class_cache->getFqnForAlias($alias, $for_class);
450
-    }
451
-
452
-
453
-    /**
454
-     * Registers the core dependencies and whether a previously instantiated object should be loaded from the cache,
455
-     * if one exists, or whether a new object should be generated every time the requested class is loaded.
456
-     * This is done by using the following class constants:
457
-     *        EE_Dependency_Map::load_from_cache - loads previously instantiated object
458
-     *        EE_Dependency_Map::load_new_object - generates a new object every time
459
-     */
460
-    protected function _register_core_dependencies()
461
-    {
462
-        $this->_dependency_map = [
463
-            'EE_Request_Handler'                                                                                          => [
464
-                'EE_Request' => EE_Dependency_Map::load_from_cache,
465
-            ],
466
-            'EE_System'                                                                                                   => [
467
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
468
-                'EE_Maintenance_Mode'                         => EE_Dependency_Map::load_from_cache,
469
-                'EE_Registry'                                 => EE_Dependency_Map::load_from_cache,
470
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
471
-                'EventEspresso\core\services\routing\Router'  => EE_Dependency_Map::load_from_cache,
472
-            ],
473
-            'EE_Admin'                                                                                                    => [
474
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
475
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
476
-            ],
477
-            'EE_Cart'                                                                                                     => [
478
-                'EE_Session' => EE_Dependency_Map::load_from_cache,
479
-            ],
480
-            'EE_Messenger_Collection_Loader'                                                                              => [
481
-                'EE_Messenger_Collection' => EE_Dependency_Map::load_new_object,
482
-            ],
483
-            'EE_Message_Type_Collection_Loader'                                                                           => [
484
-                'EE_Message_Type_Collection' => EE_Dependency_Map::load_new_object,
485
-            ],
486
-            'EE_Message_Resource_Manager'                                                                                 => [
487
-                'EE_Messenger_Collection_Loader'    => EE_Dependency_Map::load_new_object,
488
-                'EE_Message_Type_Collection_Loader' => EE_Dependency_Map::load_new_object,
489
-                'EEM_Message_Template_Group'        => EE_Dependency_Map::load_from_cache,
490
-            ],
491
-            'EE_Message_Factory'                                                                                          => [
492
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
493
-            ],
494
-            'EE_messages'                                                                                                 => [
495
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
496
-            ],
497
-            'EE_Messages_Generator'                                                                                       => [
498
-                'EE_Messages_Queue'                    => EE_Dependency_Map::load_new_object,
499
-                'EE_Messages_Data_Handler_Collection'  => EE_Dependency_Map::load_new_object,
500
-                'EE_Message_Template_Group_Collection' => EE_Dependency_Map::load_new_object,
501
-                'EEH_Parse_Shortcodes'                 => EE_Dependency_Map::load_from_cache,
502
-            ],
503
-            'EE_Messages_Processor'                                                                                       => [
504
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
505
-            ],
506
-            'EE_Messages_Queue'                                                                                           => [
507
-                'EE_Message_Repository' => EE_Dependency_Map::load_new_object,
508
-            ],
509
-            'EE_Messages_Template_Defaults'                                                                               => [
510
-                'EEM_Message_Template_Group' => EE_Dependency_Map::load_from_cache,
511
-                'EEM_Message_Template'       => EE_Dependency_Map::load_from_cache,
512
-            ],
513
-            'EE_Message_To_Generate_From_Request'                                                                         => [
514
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
515
-                'EE_Request_Handler'          => EE_Dependency_Map::load_from_cache,
516
-            ],
517
-            'EventEspresso\core\services\commands\CommandBus'                                                             => [
518
-                'EventEspresso\core\services\commands\CommandHandlerManager' => EE_Dependency_Map::load_from_cache,
519
-            ],
520
-            'EventEspresso\services\commands\CommandHandler'                                                              => [
521
-                'EE_Registry'         => EE_Dependency_Map::load_from_cache,
522
-                'CommandBusInterface' => EE_Dependency_Map::load_from_cache,
523
-            ],
524
-            'EventEspresso\core\services\commands\CommandHandlerManager'                                                  => [
525
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
526
-            ],
527
-            'EventEspresso\core\services\commands\CompositeCommandHandler'                                                => [
528
-                'EventEspresso\core\services\commands\CommandBus'     => EE_Dependency_Map::load_from_cache,
529
-                'EventEspresso\core\services\commands\CommandFactory' => EE_Dependency_Map::load_from_cache,
530
-            ],
531
-            'EventEspresso\core\services\commands\CommandFactory'                                                         => [
532
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
533
-            ],
534
-            'EventEspresso\core\services\commands\middleware\CapChecker'                                                  => [
535
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
536
-            ],
537
-            'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker'                                         => [
538
-                'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
539
-            ],
540
-            'EventEspresso\core\domain\services\capabilities\RegistrationsCapChecker'                                     => [
541
-                'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
542
-            ],
543
-            'EventEspresso\core\services\commands\registration\CreateRegistrationCommandHandler'                          => [
544
-                'EventEspresso\core\domain\services\registration\CreateRegistrationService' => EE_Dependency_Map::load_from_cache,
545
-            ],
546
-            'EventEspresso\core\services\commands\registration\CopyRegistrationDetailsCommandHandler'                     => [
547
-                'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
548
-            ],
549
-            'EventEspresso\core\services\commands\registration\CopyRegistrationPaymentsCommandHandler'                    => [
550
-                'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
551
-            ],
552
-            'EventEspresso\core\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler'         => [
553
-                'EventEspresso\core\domain\services\registration\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
554
-            ],
555
-            'EventEspresso\core\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler' => [
556
-                'EventEspresso\core\domain\services\registration\UpdateRegistrationService' => EE_Dependency_Map::load_from_cache,
557
-            ],
558
-            'EventEspresso\core\services\commands\ticket\CreateTicketLineItemCommandHandler'                              => [
559
-                'EventEspresso\core\domain\services\ticket\CreateTicketLineItemService' => EE_Dependency_Map::load_from_cache,
560
-            ],
561
-            'EventEspresso\core\services\commands\ticket\CancelTicketLineItemCommandHandler'                              => [
562
-                'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
563
-            ],
564
-            'EventEspresso\core\domain\services\registration\CancelRegistrationService'                                   => [
565
-                'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
566
-            ],
567
-            'EventEspresso\core\services\commands\attendee\CreateAttendeeCommandHandler'                                  => [
568
-                'EEM_Attendee' => EE_Dependency_Map::load_from_cache,
569
-            ],
570
-            'EventEspresso\core\services\database\TableManager'                                                           => [
571
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
572
-            ],
573
-            'EE_Data_Migration_Class_Base'                                                                                => [
574
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
575
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
576
-            ],
577
-            'EE_DMS_Core_4_1_0'                                                                                           => [
578
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
579
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
580
-            ],
581
-            'EE_DMS_Core_4_2_0'                                                                                           => [
582
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
583
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
584
-            ],
585
-            'EE_DMS_Core_4_3_0'                                                                                           => [
586
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
587
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
588
-            ],
589
-            'EE_DMS_Core_4_4_0'                                                                                           => [
590
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
591
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
592
-            ],
593
-            'EE_DMS_Core_4_5_0'                                                                                           => [
594
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
595
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
596
-            ],
597
-            'EE_DMS_Core_4_6_0'                                                                                           => [
598
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
599
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
600
-            ],
601
-            'EE_DMS_Core_4_7_0'                                                                                           => [
602
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
603
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
604
-            ],
605
-            'EE_DMS_Core_4_8_0'                                                                                           => [
606
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
607
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
608
-            ],
609
-            'EE_DMS_Core_4_9_0'                                                                                           => [
610
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
611
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
612
-            ],
613
-            'EE_DMS_Core_4_10_0'                                                                                          => [
614
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
615
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
616
-                'EE_DMS_Core_4_9_0'                                  => EE_Dependency_Map::load_from_cache,
617
-            ],
618
-            'EE_DMS_Core_4_11_0'                                                                                          => [
619
-                'EE_DMS_Core_4_10_0'                                 => EE_Dependency_Map::load_from_cache,
620
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
621
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
622
-            ],
623
-            'EE_DMS_Core_4_12_0' => [
624
-                'EE_DMS_Core_4_11_0'                                 => EE_Dependency_Map::load_from_cache,
625
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
626
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
627
-            ],
628
-            'EventEspresso\core\services\assets\Registry'                                                                 => [
629
-                'EventEspresso\core\services\assets\AssetCollection' => EE_Dependency_Map::load_new_object,
630
-                'EventEspresso\core\services\assets\AssetManifest'   => EE_Dependency_Map::load_from_cache,
631
-            ],
632
-            'EventEspresso\core\services\cache\BasicCacheManager'                                                         => [
633
-                'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
634
-            ],
635
-            'EventEspresso\core\services\cache\PostRelatedCacheManager'                                                   => [
636
-                'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
637
-            ],
638
-            'EventEspresso\core\domain\services\validation\email\EmailValidationService'                                  => [
639
-                'EE_Registration_Config'                     => EE_Dependency_Map::load_from_cache,
640
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
641
-            ],
642
-            'EventEspresso\core\domain\values\EmailAddress'                                                               => [
643
-                null,
644
-                'EventEspresso\core\domain\services\validation\email\EmailValidationService' => EE_Dependency_Map::load_from_cache,
645
-            ],
646
-            'EventEspresso\core\services\orm\ModelFieldFactory'                                                           => [
647
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
648
-            ],
649
-            'LEGACY_MODELS'                                                                                               => [
650
-                null,
651
-                'EventEspresso\core\services\database\ModelFieldFactory' => EE_Dependency_Map::load_from_cache,
652
-            ],
653
-            'EE_Module_Request_Router'                                                                                    => [
654
-                'EE_Request' => EE_Dependency_Map::load_from_cache,
655
-            ],
656
-            'EE_Registration_Processor'                                                                                   => [
657
-                'EE_Request' => EE_Dependency_Map::load_from_cache,
658
-            ],
659
-            'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'                                      => [
660
-                null,
661
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
662
-                'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
663
-            ],
664
-            'EventEspresso\caffeinated\modules\recaptcha_invisible\InvisibleRecaptcha'                                    => [
665
-                'EE_Registration_Config' => EE_Dependency_Map::load_from_cache,
666
-                'EE_Session'             => EE_Dependency_Map::load_from_cache,
667
-            ],
668
-            'EventEspresso\modules\ticket_selector\DisplayTicketSelector'                                                 => [
669
-                'EventEspresso\core\domain\entities\users\CurrentUser' => EE_Dependency_Map::load_from_cache,
670
-            ],
671
-            'EventEspresso\modules\ticket_selector\ProcessTicketSelector'                                                 => [
672
-                'EE_Core_Config'                                                          => EE_Dependency_Map::load_from_cache,
673
-                'EventEspresso\core\services\request\Request'                             => EE_Dependency_Map::load_from_cache,
674
-                'EE_Session'                                                              => EE_Dependency_Map::load_from_cache,
675
-                'EEM_Ticket'                                                              => EE_Dependency_Map::load_from_cache,
676
-                'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker' => EE_Dependency_Map::load_from_cache,
677
-            ],
678
-            'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker'                                     => [
679
-                'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
680
-            ],
681
-            'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'                              => [
682
-                'EE_Core_Config'                             => EE_Dependency_Map::load_from_cache,
683
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
684
-            ],
685
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'                                => [
686
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
687
-            ],
688
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'                               => [
689
-                'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
690
-            ],
691
-            'EE_CPT_Strategy'                                                                                             => [
692
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
693
-                'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
694
-            ],
695
-            'EventEspresso\core\services\loaders\ObjectIdentifier'                                                        => [
696
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
697
-            ],
698
-            'EventEspresso\core\CPTs\CptQueryModifier'                                                                    => [
699
-                null,
700
-                null,
701
-                null,
702
-                'EE_Request_Handler'                          => EE_Dependency_Map::load_from_cache,
703
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
704
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
705
-            ],
706
-            'EventEspresso\core\services\dependencies\DependencyResolver'                                                 => [
707
-                'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
708
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
709
-                'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
710
-            ],
711
-            'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver'                               => [
712
-                'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
713
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
714
-                'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
715
-            ],
716
-            'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'                                          => [
717
-                'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver' => EE_Dependency_Map::load_from_cache,
718
-                'EventEspresso\core\services\loaders\Loader'                                    => EE_Dependency_Map::load_from_cache,
719
-            ],
720
-            'EventEspresso\core\services\routing\RouteMatchSpecificationManager'                                          => [
721
-                'EventEspresso\core\services\routing\RouteMatchSpecificationCollection' => EE_Dependency_Map::load_from_cache,
722
-                'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'    => EE_Dependency_Map::load_from_cache,
723
-            ],
724
-            'EE_URL_Validation_Strategy'                                                                                  => [
725
-                null,
726
-                null,
727
-                'EventEspresso\core\services\validators\URLValidator' => EE_Dependency_Map::load_from_cache,
728
-            ],
729
-            'EventEspresso\core\services\request\files\FilesDataHandler'                                                  => [
730
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
731
-            ],
732
-            'EventEspressoBatchRequest\BatchRequestProcessor'                                                             => [
733
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
734
-            ],
735
-            'EventEspresso\core\domain\services\converters\RestApiSpoofer'                                                => [
736
-                'WP_REST_Server'                                               => EE_Dependency_Map::load_from_cache,
737
-                'EED_Core_Rest_Api'                                            => EE_Dependency_Map::load_from_cache,
738
-                'EventEspresso\core\libraries\rest_api\controllers\model\Read' => EE_Dependency_Map::load_from_cache,
739
-                null,
740
-            ],
741
-            'EventEspresso\core\services\routing\RouteHandler'                                                            => [
742
-                'EventEspresso\core\services\json\JsonDataNodeHandler' => EE_Dependency_Map::load_from_cache,
743
-                'EventEspresso\core\services\loaders\Loader'           => EE_Dependency_Map::load_from_cache,
744
-                'EventEspresso\core\services\request\Request'          => EE_Dependency_Map::load_from_cache,
745
-                'EventEspresso\core\services\routing\RouteCollection'  => EE_Dependency_Map::load_from_cache,
746
-            ],
747
-            'EventEspresso\core\services\json\JsonDataNodeHandler'                                                        => [
748
-                'EventEspresso\core\services\json\JsonDataNodeValidator' => EE_Dependency_Map::load_from_cache,
749
-            ],
750
-            'EventEspresso\core\services\routing\Router'                                                                  => [
751
-                'EE_Dependency_Map'                                => EE_Dependency_Map::load_from_cache,
752
-                'EventEspresso\core\services\loaders\Loader'       => EE_Dependency_Map::load_from_cache,
753
-                'EventEspresso\core\services\routing\RouteHandler' => EE_Dependency_Map::load_from_cache,
754
-            ],
755
-            'EventEspresso\core\services\assets\AssetManifest'                                                            => [
756
-                'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
757
-            ],
758
-            'EventEspresso\core\services\assets\AssetManifestFactory'                                                     => [
759
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
760
-            ],
761
-            'EventEspresso\core\services\assets\BaristaFactory'                                                           => [
762
-                'EventEspresso\core\services\assets\AssetManifestFactory' => EE_Dependency_Map::load_from_cache,
763
-                'EventEspresso\core\services\loaders\Loader'              => EE_Dependency_Map::load_from_cache,
764
-            ],
765
-            'EventEspresso\core\domain\services\capabilities\FeatureFlags'                                                => [
766
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
767
-            ],
768
-            'EventEspresso\core\services\addon\AddonManager' => [
769
-                'EventEspresso\core\services\addon\AddonCollection'              => EE_Dependency_Map::load_from_cache,
770
-                'EventEspresso\core\Psr4Autoloader'                              => EE_Dependency_Map::load_from_cache,
771
-                'EventEspresso\core\services\addon\api\v1\RegisterAddon'         => EE_Dependency_Map::load_from_cache,
772
-                'EventEspresso\core\services\addon\api\IncompatibleAddonHandler' => EE_Dependency_Map::load_from_cache,
773
-                'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'  => EE_Dependency_Map::load_from_cache,
774
-            ],
775
-            'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler' => [
776
-                'EventEspresso\core\services\request\Request'  => EE_Dependency_Map::load_from_cache,
777
-            ],
778
-            'EventEspressoBatchRequest\JobHandlers\ExecuteBatchDeletion' => [
779
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache
780
-            ],
781
-            'EventEspressoBatchRequest\JobHandlers\PreviewEventDeletion' => [
782
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache
783
-            ],
784
-            'EventEspresso\core\domain\services\admin\events\data\PreviewDeletion' => [
785
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
786
-                'EEM_Event' => EE_Dependency_Map::load_from_cache,
787
-                'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
788
-                'EEM_Registration' => EE_Dependency_Map::load_from_cache
789
-            ],
790
-            'EventEspresso\core\domain\services\admin\events\data\ConfirmDeletion' => [
791
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
792
-            ],
793
-            'EventEspresso\core\domain\entities\users\CurrentUser' => [
794
-                'EventEspresso\core\domain\entities\users\EventManagers' => EE_Dependency_Map::load_from_cache,
795
-            ],
796
-            'EventEspresso\core\services\form\meta\InputTypes' => [
797
-                'EventEspresso\core\services\form\meta\inputs\Block'   => EE_Dependency_Map::load_from_cache,
798
-                'EventEspresso\core\services\form\meta\inputs\Button'   => EE_Dependency_Map::load_from_cache,
799
-                'EventEspresso\core\services\form\meta\inputs\DateTime' => EE_Dependency_Map::load_from_cache,
800
-                'EventEspresso\core\services\form\meta\inputs\Input'    => EE_Dependency_Map::load_from_cache,
801
-                'EventEspresso\core\services\form\meta\inputs\Number'   => EE_Dependency_Map::load_from_cache,
802
-                'EventEspresso\core\services\form\meta\inputs\Phone'    => EE_Dependency_Map::load_from_cache,
803
-                'EventEspresso\core\services\form\meta\inputs\Select'   => EE_Dependency_Map::load_from_cache,
804
-                'EventEspresso\core\services\form\meta\inputs\Text'     => EE_Dependency_Map::load_from_cache,
805
-            ],
806
-            'EventEspresso\core\domain\services\registration\form\v1\RegFormDependencyHandler' => [
807
-                'EE_Dependency_Map' => EE_Dependency_Map::load_from_cache,
808
-            ],
809
-            'EventEspresso\core\services\calculators\LineItemCalculator' => [
810
-                'EventEspresso\core\services\helpers\DecimalValues' => EE_Dependency_Map::load_from_cache,
811
-            ],
812
-            'EventEspresso\core\services\helpers\DecimalValues'          => [
813
-                'EE_Currency_Config' => EE_Dependency_Map::load_from_cache,
814
-            ],
815
-        ];
816
-    }
817
-
818
-
819
-    /**
820
-     * Registers how core classes are loaded.
821
-     * This can either be done by simply providing the name of one of the EE_Registry loader methods such as:
822
-     *        'EE_Request_Handler' => 'load_core'
823
-     *        'EE_Messages_Queue'  => 'load_lib'
824
-     *        'EEH_Debug_Tools'    => 'load_helper'
825
-     * or, if greater control is required, by providing a custom closure. For example:
826
-     *        'Some_Class' => function () {
827
-     *            return new Some_Class();
828
-     *        },
829
-     * This is required for instantiating dependencies
830
-     * where an interface has been type hinted in a class constructor. For example:
831
-     *        'Required_Interface' => function () {
832
-     *            return new A_Class_That_Implements_Required_Interface();
833
-     *        },
834
-     */
835
-    protected function _register_core_class_loaders()
836
-    {
837
-        $this->_class_loaders = [
838
-            // load_core
839
-            'EE_Dependency_Map'                            => function () {
840
-                return $this;
841
-            },
842
-            'EE_Capabilities'                              => 'load_core',
843
-            'EE_Encryption'                                => 'load_core',
844
-            'EE_Front_Controller'                          => 'load_core',
845
-            'EE_Module_Request_Router'                     => 'load_core',
846
-            'EE_Registry'                                  => 'load_core',
847
-            'EE_Request'                                   => function () {
848
-                return $this->legacy_request;
849
-            },
850
-            'EventEspresso\core\services\request\Request'  => function () {
851
-                return $this->request;
852
-            },
853
-            'EventEspresso\core\services\request\Response' => function () {
854
-                return $this->response;
855
-            },
856
-            'EE_Base'                                      => 'load_core',
857
-            'EE_Request_Handler'                           => 'load_core',
858
-            'EE_Session'                                   => 'load_core',
859
-            'EE_Cron_Tasks'                                => 'load_core',
860
-            'EE_System'                                    => 'load_core',
861
-            'EE_Maintenance_Mode'                          => 'load_core',
862
-            'EE_Register_CPTs'                             => 'load_core',
863
-            'EE_Admin'                                     => 'load_core',
864
-            'EE_CPT_Strategy'                              => 'load_core',
865
-            // load_class
866
-            'EE_Registration_Processor'                    => 'load_class',
867
-            // load_lib
868
-            'EE_Message_Resource_Manager'                  => 'load_lib',
869
-            'EE_Message_Type_Collection'                   => 'load_lib',
870
-            'EE_Message_Type_Collection_Loader'            => 'load_lib',
871
-            'EE_Messenger_Collection'                      => 'load_lib',
872
-            'EE_Messenger_Collection_Loader'               => 'load_lib',
873
-            'EE_Messages_Processor'                        => 'load_lib',
874
-            'EE_Message_Repository'                        => 'load_lib',
875
-            'EE_Messages_Queue'                            => 'load_lib',
876
-            'EE_Messages_Data_Handler_Collection'          => 'load_lib',
877
-            'EE_Message_Template_Group_Collection'         => 'load_lib',
878
-            'EE_Payment_Method_Manager'                    => 'load_lib',
879
-            'EE_DMS_Core_4_1_0'                            => 'load_dms',
880
-            'EE_DMS_Core_4_2_0'                            => 'load_dms',
881
-            'EE_DMS_Core_4_3_0'                            => 'load_dms',
882
-            'EE_DMS_Core_4_5_0'                            => 'load_dms',
883
-            'EE_DMS_Core_4_6_0'                            => 'load_dms',
884
-            'EE_DMS_Core_4_7_0'                            => 'load_dms',
885
-            'EE_DMS_Core_4_8_0'                            => 'load_dms',
886
-            'EE_DMS_Core_4_9_0'                            => 'load_dms',
887
-            'EE_DMS_Core_4_10_0'                           => 'load_dms',
888
-            'EE_DMS_Core_4_11_0'                           => 'load_dms',
889
-            'EE_DMS_Core_4_12_0'                           => 'load_dms',
890
-            'EE_Messages_Generator'                        => static function () {
891
-                return EE_Registry::instance()->load_lib(
892
-                    'Messages_Generator',
893
-                    [],
894
-                    false,
895
-                    false
896
-                );
897
-            },
898
-            'EE_Messages_Template_Defaults'                => static function ($arguments = []) {
899
-                return EE_Registry::instance()->load_lib(
900
-                    'Messages_Template_Defaults',
901
-                    $arguments,
902
-                    false,
903
-                    false
904
-                );
905
-            },
906
-            // load_helper
907
-            'EEH_Parse_Shortcodes'                         => static function () {
908
-                if (EE_Registry::instance()->load_helper('Parse_Shortcodes')) {
909
-                    return new EEH_Parse_Shortcodes();
910
-                }
911
-                return null;
912
-            },
913
-            'EE_Template_Config'                           => static function () {
914
-                return EE_Config::instance()->template_settings;
915
-            },
916
-            'EE_Currency_Config'                           => static function () {
917
-                return EE_Config::instance()->currency;
918
-            },
919
-            'EE_Registration_Config'                       => static function () {
920
-                return EE_Config::instance()->registration;
921
-            },
922
-            'EE_Core_Config'                               => static function () {
923
-                return EE_Config::instance()->core;
924
-            },
925
-            'EventEspresso\core\services\loaders\Loader'   => static function () {
926
-                return LoaderFactory::getLoader();
927
-            },
928
-            'EE_Network_Config'                            => static function () {
929
-                return EE_Network_Config::instance();
930
-            },
931
-            'EE_Config'                                    => static function () {
932
-                return EE_Config::instance();
933
-            },
934
-            'EventEspresso\core\domain\Domain'             => static function () {
935
-                return DomainFactory::getEventEspressoCoreDomain();
936
-            },
937
-            'EE_Admin_Config'                              => static function () {
938
-                return EE_Config::instance()->admin;
939
-            },
940
-            'EE_Organization_Config'                       => static function () {
941
-                return EE_Config::instance()->organization;
942
-            },
943
-            'EE_Network_Core_Config'                       => static function () {
944
-                return EE_Network_Config::instance()->core;
945
-            },
946
-            'EE_Environment_Config'                        => static function () {
947
-                return EE_Config::instance()->environment;
948
-            },
949
-            'EED_Core_Rest_Api'                            => static function () {
950
-                return EED_Core_Rest_Api::instance();
951
-            },
952
-            'WP_REST_Server'                               => static function () {
953
-                return rest_get_server();
954
-            },
955
-            'EventEspresso\core\Psr4Autoloader'            => static function () {
956
-                return EE_Psr4AutoloaderInit::psr4_loader();
957
-            },
958
-        ];
959
-    }
960
-
961
-
962
-    /**
963
-     * can be used for supplying alternate names for classes,
964
-     * or for connecting interface names to instantiable classes
965
-     *
966
-     * @throws InvalidAliasException
967
-     */
968
-    protected function _register_core_aliases()
969
-    {
970
-        $aliases = [
971
-            'CommandBusInterface'                                                          => 'EventEspresso\core\services\commands\CommandBusInterface',
972
-            'EventEspresso\core\services\commands\CommandBusInterface'                     => 'EventEspresso\core\services\commands\CommandBus',
973
-            'CommandHandlerManagerInterface'                                               => 'EventEspresso\core\services\commands\CommandHandlerManagerInterface',
974
-            'EventEspresso\core\services\commands\CommandHandlerManagerInterface'          => 'EventEspresso\core\services\commands\CommandHandlerManager',
975
-            'CapChecker'                                                                   => 'EventEspresso\core\services\commands\middleware\CapChecker',
976
-            'AddActionHook'                                                                => 'EventEspresso\core\services\commands\middleware\AddActionHook',
977
-            'CapabilitiesChecker'                                                          => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
978
-            'CapabilitiesCheckerInterface'                                                 => 'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface',
979
-            'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface' => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
980
-            'CreateRegistrationService'                                                    => 'EventEspresso\core\domain\services\registration\CreateRegistrationService',
981
-            'CreateRegistrationCommandHandler'                                             => 'EventEspresso\core\services\commands\registration\CreateRegistrationCommand',
982
-            'CopyRegistrationDetailsCommandHandler'                                        => 'EventEspresso\core\services\commands\registration\CopyRegistrationDetailsCommand',
983
-            'CopyRegistrationPaymentsCommandHandler'                                       => 'EventEspresso\core\services\commands\registration\CopyRegistrationPaymentsCommand',
984
-            'CancelRegistrationAndTicketLineItemCommandHandler'                            => 'EventEspresso\core\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler',
985
-            'UpdateRegistrationAndTransactionAfterChangeCommandHandler'                    => 'EventEspresso\core\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler',
986
-            'CreateTicketLineItemCommandHandler'                                           => 'EventEspresso\core\services\commands\ticket\CreateTicketLineItemCommand',
987
-            'CreateTransactionCommandHandler'                                              => 'EventEspresso\core\services\commands\transaction\CreateTransactionCommandHandler',
988
-            'CreateAttendeeCommandHandler'                                                 => 'EventEspresso\core\services\commands\attendee\CreateAttendeeCommandHandler',
989
-            'TableManager'                                                                 => 'EventEspresso\core\services\database\TableManager',
990
-            'TableAnalysis'                                                                => 'EventEspresso\core\services\database\TableAnalysis',
991
-            'EspressoShortcode'                                                            => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
992
-            'ShortcodeInterface'                                                           => 'EventEspresso\core\services\shortcodes\ShortcodeInterface',
993
-            'EventEspresso\core\services\shortcodes\ShortcodeInterface'                    => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
994
-            'EventEspresso\core\services\cache\CacheStorageInterface'                      => 'EventEspresso\core\services\cache\TransientCacheStorage',
995
-            'LoaderInterface'                                                              => 'EventEspresso\core\services\loaders\LoaderInterface',
996
-            'EventEspresso\core\services\loaders\LoaderInterface'                          => 'EventEspresso\core\services\loaders\Loader',
997
-            'CommandFactoryInterface'                                                      => 'EventEspresso\core\services\commands\CommandFactoryInterface',
998
-            'EventEspresso\core\services\commands\CommandFactoryInterface'                 => 'EventEspresso\core\services\commands\CommandFactory',
999
-            'EmailValidatorInterface'                                                      => 'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface',
1000
-            'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface'  => 'EventEspresso\core\domain\services\validation\email\EmailValidationService',
1001
-            'NoticeConverterInterface'                                                     => 'EventEspresso\core\services\notices\NoticeConverterInterface',
1002
-            'EventEspresso\core\services\notices\NoticeConverterInterface'                 => 'EventEspresso\core\services\notices\ConvertNoticesToEeErrors',
1003
-            'NoticesContainerInterface'                                                    => 'EventEspresso\core\services\notices\NoticesContainerInterface',
1004
-            'EventEspresso\core\services\notices\NoticesContainerInterface'                => 'EventEspresso\core\services\notices\NoticesContainer',
1005
-            'EventEspresso\core\services\request\RequestInterface'                         => 'EventEspresso\core\services\request\Request',
1006
-            'EventEspresso\core\services\request\ResponseInterface'                        => 'EventEspresso\core\services\request\Response',
1007
-            'EventEspresso\core\domain\DomainInterface'                                    => 'EventEspresso\core\domain\Domain',
1008
-            'Registration_Processor'                                                       => 'EE_Registration_Processor',
1009
-            'EventEspresso\core\services\assets\AssetManifestInterface'                    => 'EventEspresso\core\services\assets\AssetManifest',
1010
-        ];
1011
-        foreach ($aliases as $alias => $fqn) {
1012
-            if (is_array($fqn)) {
1013
-                foreach ($fqn as $class => $for_class) {
1014
-                    $this->class_cache->addAlias($class, $alias, $for_class);
1015
-                }
1016
-                continue;
1017
-            }
1018
-            $this->class_cache->addAlias($fqn, $alias);
1019
-        }
1020
-        if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1021
-            $this->class_cache->addAlias(
1022
-                'EventEspresso\core\services\notices\ConvertNoticesToAdminNotices',
1023
-                'EventEspresso\core\services\notices\NoticeConverterInterface'
1024
-            );
1025
-        }
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * This is used to reset the internal map and class_loaders to their original default state at the beginning of the
1031
-     * request Primarily used by unit tests.
1032
-     */
1033
-    public function reset()
1034
-    {
1035
-        $this->_register_core_class_loaders();
1036
-        $this->_register_core_dependencies();
1037
-    }
1038
-
1039
-
1040
-    /**
1041
-     * PLZ NOTE: a better name for this method would be is_alias()
1042
-     * because it returns TRUE if the provided fully qualified name IS an alias
1043
-     * WHY?
1044
-     * Because if a class is type hinting for a concretion,
1045
-     * then why would we need to find another class to supply it?
1046
-     * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
1047
-     * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
1048
-     * Don't go looking for some substitute.
1049
-     * Whereas if a class is type hinting for an interface...
1050
-     * then we need to find an actual class to use.
1051
-     * So the interface IS the alias for some other FQN,
1052
-     * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
1053
-     * represents some other class.
1054
-     *
1055
-     * @param string $fqn
1056
-     * @param string $for_class
1057
-     * @return bool
1058
-     * @deprecated 4.9.62.p
1059
-     */
1060
-    public function has_alias($fqn = '', $for_class = '')
1061
-    {
1062
-        return $this->isAlias($fqn, $for_class);
1063
-    }
1064
-
1065
-
1066
-    /**
1067
-     * PLZ NOTE: a better name for this method would be get_fqn_for_alias()
1068
-     * because it returns a FQN for provided alias if one exists, otherwise returns the original $alias
1069
-     * functions recursively, so that multiple aliases can be used to drill down to a FQN
1070
-     *  for example:
1071
-     *      if the following two entries were added to the _aliases array:
1072
-     *          array(
1073
-     *              'interface_alias'           => 'some\namespace\interface'
1074
-     *              'some\namespace\interface'  => 'some\namespace\classname'
1075
-     *          )
1076
-     *      then one could use EE_Registry::instance()->create( 'interface_alias' )
1077
-     *      to load an instance of 'some\namespace\classname'
1078
-     *
1079
-     * @param string $alias
1080
-     * @param string $for_class
1081
-     * @return string
1082
-     * @deprecated 4.9.62.p
1083
-     */
1084
-    public function get_alias($alias = '', $for_class = '')
1085
-    {
1086
-        return $this->getFqnForAlias($alias, $for_class);
1087
-    }
25
+	/**
26
+	 * This means that the requested class dependency is not present in the dependency map
27
+	 */
28
+	const not_registered = 0;
29
+
30
+	/**
31
+	 * This instructs class loaders to ALWAYS return a newly instantiated object for the requested class.
32
+	 */
33
+	const load_new_object = 1;
34
+
35
+	/**
36
+	 * This instructs class loaders to return a previously instantiated and cached object for the requested class.
37
+	 * IF a previously instantiated object does not exist, a new one will be created and added to the cache.
38
+	 */
39
+	const load_from_cache = 2;
40
+
41
+	/**
42
+	 * When registering a dependency,
43
+	 * this indicates to keep any existing dependencies that already exist,
44
+	 * and simply discard any new dependencies declared in the incoming data
45
+	 */
46
+	const KEEP_EXISTING_DEPENDENCIES = 0;
47
+
48
+	/**
49
+	 * When registering a dependency,
50
+	 * this indicates to overwrite any existing dependencies that already exist using the incoming data
51
+	 */
52
+	const OVERWRITE_DEPENDENCIES = 1;
53
+
54
+	/**
55
+	 * @type EE_Dependency_Map $_instance
56
+	 */
57
+	protected static $_instance;
58
+
59
+	/**
60
+	 * @var ClassInterfaceCache $class_cache
61
+	 */
62
+	private $class_cache;
63
+
64
+	/**
65
+	 * @type RequestInterface $request
66
+	 */
67
+	protected $request;
68
+
69
+	/**
70
+	 * @type LegacyRequestInterface $legacy_request
71
+	 */
72
+	protected $legacy_request;
73
+
74
+	/**
75
+	 * @type ResponseInterface $response
76
+	 */
77
+	protected $response;
78
+
79
+	/**
80
+	 * @type LoaderInterface $loader
81
+	 */
82
+	protected $loader;
83
+
84
+	/**
85
+	 * @type array $_dependency_map
86
+	 */
87
+	protected $_dependency_map = [];
88
+
89
+	/**
90
+	 * @type array $_class_loaders
91
+	 */
92
+	protected $_class_loaders = [];
93
+
94
+
95
+	/**
96
+	 * EE_Dependency_Map constructor.
97
+	 *
98
+	 * @param ClassInterfaceCache $class_cache
99
+	 */
100
+	protected function __construct(ClassInterfaceCache $class_cache)
101
+	{
102
+		$this->class_cache = $class_cache;
103
+		do_action('EE_Dependency_Map____construct', $this);
104
+	}
105
+
106
+
107
+	/**
108
+	 * @return void
109
+	 * @throws InvalidAliasException
110
+	 */
111
+	public function initialize()
112
+	{
113
+		$this->_register_core_dependencies();
114
+		$this->_register_core_class_loaders();
115
+		$this->_register_core_aliases();
116
+	}
117
+
118
+
119
+	/**
120
+	 * @singleton method used to instantiate class object
121
+	 * @param ClassInterfaceCache|null $class_cache
122
+	 * @return EE_Dependency_Map
123
+	 */
124
+	public static function instance(ClassInterfaceCache $class_cache = null)
125
+	{
126
+		// check if class object is instantiated, and instantiated properly
127
+		if (
128
+			! EE_Dependency_Map::$_instance instanceof EE_Dependency_Map
129
+			&& $class_cache instanceof ClassInterfaceCache
130
+		) {
131
+			EE_Dependency_Map::$_instance = new EE_Dependency_Map($class_cache);
132
+		}
133
+		return EE_Dependency_Map::$_instance;
134
+	}
135
+
136
+
137
+	/**
138
+	 * @param RequestInterface $request
139
+	 */
140
+	public function setRequest(RequestInterface $request)
141
+	{
142
+		$this->request = $request;
143
+	}
144
+
145
+
146
+	/**
147
+	 * @param LegacyRequestInterface $legacy_request
148
+	 */
149
+	public function setLegacyRequest(LegacyRequestInterface $legacy_request)
150
+	{
151
+		$this->legacy_request = $legacy_request;
152
+	}
153
+
154
+
155
+	/**
156
+	 * @param ResponseInterface $response
157
+	 */
158
+	public function setResponse(ResponseInterface $response)
159
+	{
160
+		$this->response = $response;
161
+	}
162
+
163
+
164
+	/**
165
+	 * @param LoaderInterface $loader
166
+	 */
167
+	public function setLoader(LoaderInterface $loader)
168
+	{
169
+		$this->loader = $loader;
170
+	}
171
+
172
+
173
+	/**
174
+	 * @param string $class
175
+	 * @param array  $dependencies
176
+	 * @param int    $overwrite
177
+	 * @return bool
178
+	 */
179
+	public static function register_dependencies(
180
+		$class,
181
+		array $dependencies,
182
+		$overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
183
+	) {
184
+		return EE_Dependency_Map::$_instance->registerDependencies($class, $dependencies, $overwrite);
185
+	}
186
+
187
+
188
+	/**
189
+	 * Assigns an array of class names and corresponding load sources (new or cached)
190
+	 * to the class specified by the first parameter.
191
+	 * IMPORTANT !!!
192
+	 * The order of elements in the incoming $dependencies array MUST match
193
+	 * the order of the constructor parameters for the class in question.
194
+	 * This is especially important when overriding any existing dependencies that are registered.
195
+	 * the third parameter controls whether any duplicate dependencies are overwritten or not.
196
+	 *
197
+	 * @param string $class
198
+	 * @param array  $dependencies
199
+	 * @param int    $overwrite
200
+	 * @return bool
201
+	 */
202
+	public function registerDependencies(
203
+		$class,
204
+		array $dependencies,
205
+		$overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
206
+	) {
207
+		$class      = trim($class, '\\');
208
+		$registered = false;
209
+		if (empty(EE_Dependency_Map::$_instance->_dependency_map[ $class ])) {
210
+			EE_Dependency_Map::$_instance->_dependency_map[ $class ] = [];
211
+		}
212
+		// we need to make sure that any aliases used when registering a dependency
213
+		// get resolved to the correct class name
214
+		foreach ($dependencies as $dependency => $load_source) {
215
+			$alias = EE_Dependency_Map::$_instance->getFqnForAlias($dependency);
216
+			if (
217
+				$overwrite === EE_Dependency_Map::OVERWRITE_DEPENDENCIES
218
+				|| ! isset(EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $alias ])
219
+			) {
220
+				unset($dependencies[ $dependency ]);
221
+				$dependencies[ $alias ] = $load_source;
222
+				$registered             = true;
223
+			}
224
+		}
225
+		// now add our two lists of dependencies together.
226
+		// using Union (+=) favours the arrays in precedence from left to right,
227
+		// so $dependencies is NOT overwritten because it is listed first
228
+		// ie: with A = B + C, entries in B take precedence over duplicate entries in C
229
+		// Union is way faster than array_merge() but should be used with caution...
230
+		// especially with numerically indexed arrays
231
+		$dependencies += EE_Dependency_Map::$_instance->_dependency_map[ $class ];
232
+		// now we need to ensure that the resulting dependencies
233
+		// array only has the entries that are required for the class
234
+		// so first count how many dependencies were originally registered for the class
235
+		$dependency_count = count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]);
236
+		// if that count is non-zero (meaning dependencies were already registered)
237
+		EE_Dependency_Map::$_instance->_dependency_map[ $class ] = $dependency_count
238
+			// then truncate the  final array to match that count
239
+			? array_slice($dependencies, 0, $dependency_count)
240
+			// otherwise just take the incoming array because nothing previously existed
241
+			: $dependencies;
242
+		return $registered;
243
+	}
244
+
245
+
246
+	/**
247
+	 * @param string $class_name
248
+	 * @param string $loader
249
+	 * @return bool
250
+	 * @throws DomainException
251
+	 */
252
+	public static function register_class_loader($class_name, $loader = 'load_core')
253
+	{
254
+		return EE_Dependency_Map::$_instance->registerClassLoader($class_name, $loader);
255
+	}
256
+
257
+
258
+	/**
259
+	 * @param string $class_name
260
+	 * @param string $loader
261
+	 * @return bool
262
+	 * @throws DomainException
263
+	 */
264
+	public function registerClassLoader($class_name, $loader = 'load_core')
265
+	{
266
+		if (! $loader instanceof Closure && strpos($class_name, '\\') !== false) {
267
+			throw new DomainException(
268
+				esc_html__('Don\'t use class loaders for FQCNs.', 'event_espresso')
269
+			);
270
+		}
271
+		// check that loader is callable or method starts with "load_" and exists in EE_Registry
272
+		if (
273
+			! is_callable($loader)
274
+			&& (
275
+				strpos($loader, 'load_') !== 0
276
+				|| ! method_exists('EE_Registry', $loader)
277
+			)
278
+		) {
279
+			throw new DomainException(
280
+				sprintf(
281
+					esc_html__(
282
+						'"%1$s" is not a valid loader method on EE_Registry.',
283
+						'event_espresso'
284
+					),
285
+					$loader
286
+				)
287
+			);
288
+		}
289
+		$class_name = EE_Dependency_Map::$_instance->getFqnForAlias($class_name);
290
+		if (! isset(EE_Dependency_Map::$_instance->_class_loaders[ $class_name ])) {
291
+			EE_Dependency_Map::$_instance->_class_loaders[ $class_name ] = $loader;
292
+			return true;
293
+		}
294
+		return false;
295
+	}
296
+
297
+
298
+	/**
299
+	 * @return array
300
+	 */
301
+	public function dependency_map()
302
+	{
303
+		return $this->_dependency_map;
304
+	}
305
+
306
+
307
+	/**
308
+	 * returns TRUE if dependency map contains a listing for the provided class name
309
+	 *
310
+	 * @param string $class_name
311
+	 * @return boolean
312
+	 */
313
+	public function has($class_name = '')
314
+	{
315
+		// all legacy models have the same dependencies
316
+		if (strpos($class_name, 'EEM_') === 0) {
317
+			$class_name = 'LEGACY_MODELS';
318
+		}
319
+		return isset($this->_dependency_map[ $class_name ]);
320
+	}
321
+
322
+
323
+	/**
324
+	 * returns TRUE if dependency map contains a listing for the provided class name AND dependency
325
+	 *
326
+	 * @param string $class_name
327
+	 * @param string $dependency
328
+	 * @return bool
329
+	 */
330
+	public function has_dependency_for_class($class_name = '', $dependency = '')
331
+	{
332
+		// all legacy models have the same dependencies
333
+		if (strpos($class_name, 'EEM_') === 0) {
334
+			$class_name = 'LEGACY_MODELS';
335
+		}
336
+		$dependency = $this->getFqnForAlias($dependency, $class_name);
337
+		return isset($this->_dependency_map[ $class_name ][ $dependency ]);
338
+	}
339
+
340
+
341
+	/**
342
+	 * returns loading strategy for whether a previously cached dependency should be loaded or a new instance returned
343
+	 *
344
+	 * @param string $class_name
345
+	 * @param string $dependency
346
+	 * @return int
347
+	 */
348
+	public function loading_strategy_for_class_dependency($class_name = '', $dependency = '')
349
+	{
350
+		// all legacy models have the same dependencies
351
+		if (strpos($class_name, 'EEM_') === 0) {
352
+			$class_name = 'LEGACY_MODELS';
353
+		}
354
+		$dependency = $this->getFqnForAlias($dependency);
355
+		return $this->has_dependency_for_class($class_name, $dependency)
356
+			? $this->_dependency_map[ $class_name ][ $dependency ]
357
+			: EE_Dependency_Map::not_registered;
358
+	}
359
+
360
+
361
+	/**
362
+	 * @param string $class_name
363
+	 * @return string | Closure
364
+	 */
365
+	public function class_loader($class_name)
366
+	{
367
+		// all legacy models use load_model()
368
+		if (strpos($class_name, 'EEM_') === 0) {
369
+			return 'load_model';
370
+		}
371
+		// EE_CPT_*_Strategy classes like EE_CPT_Event_Strategy, EE_CPT_Venue_Strategy, etc
372
+		// perform strpos() first to avoid loading regex every time we load a class
373
+		if (
374
+			strpos($class_name, 'EE_CPT_') === 0
375
+			&& preg_match('/^EE_CPT_([a-zA-Z]+)_Strategy$/', $class_name)
376
+		) {
377
+			return 'load_core';
378
+		}
379
+		$class_name = $this->getFqnForAlias($class_name);
380
+		return isset($this->_class_loaders[ $class_name ]) ? $this->_class_loaders[ $class_name ] : '';
381
+	}
382
+
383
+
384
+	/**
385
+	 * @return array
386
+	 */
387
+	public function class_loaders()
388
+	{
389
+		return $this->_class_loaders;
390
+	}
391
+
392
+
393
+	/**
394
+	 * adds an alias for a classname
395
+	 *
396
+	 * @param string $fqcn      the class name that should be used (concrete class to replace interface)
397
+	 * @param string $alias     the class name that would be type hinted for (abstract parent or interface)
398
+	 * @param string $for_class the class that has the dependency (is type hinting for the interface)
399
+	 * @throws InvalidAliasException
400
+	 */
401
+	public function add_alias($fqcn, $alias, $for_class = '')
402
+	{
403
+		$this->class_cache->addAlias($fqcn, $alias, $for_class);
404
+	}
405
+
406
+
407
+	/**
408
+	 * Returns TRUE if the provided fully qualified name IS an alias
409
+	 * WHY?
410
+	 * Because if a class is type hinting for a concretion,
411
+	 * then why would we need to find another class to supply it?
412
+	 * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
413
+	 * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
414
+	 * Don't go looking for some substitute.
415
+	 * Whereas if a class is type hinting for an interface...
416
+	 * then we need to find an actual class to use.
417
+	 * So the interface IS the alias for some other FQN,
418
+	 * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
419
+	 * represents some other class.
420
+	 *
421
+	 * @param string $fqn
422
+	 * @param string $for_class
423
+	 * @return bool
424
+	 */
425
+	public function isAlias($fqn = '', $for_class = '')
426
+	{
427
+		return $this->class_cache->isAlias($fqn, $for_class);
428
+	}
429
+
430
+
431
+	/**
432
+	 * Returns a FQN for provided alias if one exists, otherwise returns the original $alias
433
+	 * functions recursively, so that multiple aliases can be used to drill down to a FQN
434
+	 *  for example:
435
+	 *      if the following two entries were added to the _aliases array:
436
+	 *          array(
437
+	 *              'interface_alias'           => 'some\namespace\interface'
438
+	 *              'some\namespace\interface'  => 'some\namespace\classname'
439
+	 *          )
440
+	 *      then one could use EE_Registry::instance()->create( 'interface_alias' )
441
+	 *      to load an instance of 'some\namespace\classname'
442
+	 *
443
+	 * @param string $alias
444
+	 * @param string $for_class
445
+	 * @return string
446
+	 */
447
+	public function getFqnForAlias($alias = '', $for_class = '')
448
+	{
449
+		return (string) $this->class_cache->getFqnForAlias($alias, $for_class);
450
+	}
451
+
452
+
453
+	/**
454
+	 * Registers the core dependencies and whether a previously instantiated object should be loaded from the cache,
455
+	 * if one exists, or whether a new object should be generated every time the requested class is loaded.
456
+	 * This is done by using the following class constants:
457
+	 *        EE_Dependency_Map::load_from_cache - loads previously instantiated object
458
+	 *        EE_Dependency_Map::load_new_object - generates a new object every time
459
+	 */
460
+	protected function _register_core_dependencies()
461
+	{
462
+		$this->_dependency_map = [
463
+			'EE_Request_Handler'                                                                                          => [
464
+				'EE_Request' => EE_Dependency_Map::load_from_cache,
465
+			],
466
+			'EE_System'                                                                                                   => [
467
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
468
+				'EE_Maintenance_Mode'                         => EE_Dependency_Map::load_from_cache,
469
+				'EE_Registry'                                 => EE_Dependency_Map::load_from_cache,
470
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
471
+				'EventEspresso\core\services\routing\Router'  => EE_Dependency_Map::load_from_cache,
472
+			],
473
+			'EE_Admin'                                                                                                    => [
474
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
475
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
476
+			],
477
+			'EE_Cart'                                                                                                     => [
478
+				'EE_Session' => EE_Dependency_Map::load_from_cache,
479
+			],
480
+			'EE_Messenger_Collection_Loader'                                                                              => [
481
+				'EE_Messenger_Collection' => EE_Dependency_Map::load_new_object,
482
+			],
483
+			'EE_Message_Type_Collection_Loader'                                                                           => [
484
+				'EE_Message_Type_Collection' => EE_Dependency_Map::load_new_object,
485
+			],
486
+			'EE_Message_Resource_Manager'                                                                                 => [
487
+				'EE_Messenger_Collection_Loader'    => EE_Dependency_Map::load_new_object,
488
+				'EE_Message_Type_Collection_Loader' => EE_Dependency_Map::load_new_object,
489
+				'EEM_Message_Template_Group'        => EE_Dependency_Map::load_from_cache,
490
+			],
491
+			'EE_Message_Factory'                                                                                          => [
492
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
493
+			],
494
+			'EE_messages'                                                                                                 => [
495
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
496
+			],
497
+			'EE_Messages_Generator'                                                                                       => [
498
+				'EE_Messages_Queue'                    => EE_Dependency_Map::load_new_object,
499
+				'EE_Messages_Data_Handler_Collection'  => EE_Dependency_Map::load_new_object,
500
+				'EE_Message_Template_Group_Collection' => EE_Dependency_Map::load_new_object,
501
+				'EEH_Parse_Shortcodes'                 => EE_Dependency_Map::load_from_cache,
502
+			],
503
+			'EE_Messages_Processor'                                                                                       => [
504
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
505
+			],
506
+			'EE_Messages_Queue'                                                                                           => [
507
+				'EE_Message_Repository' => EE_Dependency_Map::load_new_object,
508
+			],
509
+			'EE_Messages_Template_Defaults'                                                                               => [
510
+				'EEM_Message_Template_Group' => EE_Dependency_Map::load_from_cache,
511
+				'EEM_Message_Template'       => EE_Dependency_Map::load_from_cache,
512
+			],
513
+			'EE_Message_To_Generate_From_Request'                                                                         => [
514
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
515
+				'EE_Request_Handler'          => EE_Dependency_Map::load_from_cache,
516
+			],
517
+			'EventEspresso\core\services\commands\CommandBus'                                                             => [
518
+				'EventEspresso\core\services\commands\CommandHandlerManager' => EE_Dependency_Map::load_from_cache,
519
+			],
520
+			'EventEspresso\services\commands\CommandHandler'                                                              => [
521
+				'EE_Registry'         => EE_Dependency_Map::load_from_cache,
522
+				'CommandBusInterface' => EE_Dependency_Map::load_from_cache,
523
+			],
524
+			'EventEspresso\core\services\commands\CommandHandlerManager'                                                  => [
525
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
526
+			],
527
+			'EventEspresso\core\services\commands\CompositeCommandHandler'                                                => [
528
+				'EventEspresso\core\services\commands\CommandBus'     => EE_Dependency_Map::load_from_cache,
529
+				'EventEspresso\core\services\commands\CommandFactory' => EE_Dependency_Map::load_from_cache,
530
+			],
531
+			'EventEspresso\core\services\commands\CommandFactory'                                                         => [
532
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
533
+			],
534
+			'EventEspresso\core\services\commands\middleware\CapChecker'                                                  => [
535
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
536
+			],
537
+			'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker'                                         => [
538
+				'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
539
+			],
540
+			'EventEspresso\core\domain\services\capabilities\RegistrationsCapChecker'                                     => [
541
+				'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
542
+			],
543
+			'EventEspresso\core\services\commands\registration\CreateRegistrationCommandHandler'                          => [
544
+				'EventEspresso\core\domain\services\registration\CreateRegistrationService' => EE_Dependency_Map::load_from_cache,
545
+			],
546
+			'EventEspresso\core\services\commands\registration\CopyRegistrationDetailsCommandHandler'                     => [
547
+				'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
548
+			],
549
+			'EventEspresso\core\services\commands\registration\CopyRegistrationPaymentsCommandHandler'                    => [
550
+				'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
551
+			],
552
+			'EventEspresso\core\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler'         => [
553
+				'EventEspresso\core\domain\services\registration\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
554
+			],
555
+			'EventEspresso\core\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler' => [
556
+				'EventEspresso\core\domain\services\registration\UpdateRegistrationService' => EE_Dependency_Map::load_from_cache,
557
+			],
558
+			'EventEspresso\core\services\commands\ticket\CreateTicketLineItemCommandHandler'                              => [
559
+				'EventEspresso\core\domain\services\ticket\CreateTicketLineItemService' => EE_Dependency_Map::load_from_cache,
560
+			],
561
+			'EventEspresso\core\services\commands\ticket\CancelTicketLineItemCommandHandler'                              => [
562
+				'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
563
+			],
564
+			'EventEspresso\core\domain\services\registration\CancelRegistrationService'                                   => [
565
+				'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
566
+			],
567
+			'EventEspresso\core\services\commands\attendee\CreateAttendeeCommandHandler'                                  => [
568
+				'EEM_Attendee' => EE_Dependency_Map::load_from_cache,
569
+			],
570
+			'EventEspresso\core\services\database\TableManager'                                                           => [
571
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
572
+			],
573
+			'EE_Data_Migration_Class_Base'                                                                                => [
574
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
575
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
576
+			],
577
+			'EE_DMS_Core_4_1_0'                                                                                           => [
578
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
579
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
580
+			],
581
+			'EE_DMS_Core_4_2_0'                                                                                           => [
582
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
583
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
584
+			],
585
+			'EE_DMS_Core_4_3_0'                                                                                           => [
586
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
587
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
588
+			],
589
+			'EE_DMS_Core_4_4_0'                                                                                           => [
590
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
591
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
592
+			],
593
+			'EE_DMS_Core_4_5_0'                                                                                           => [
594
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
595
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
596
+			],
597
+			'EE_DMS_Core_4_6_0'                                                                                           => [
598
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
599
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
600
+			],
601
+			'EE_DMS_Core_4_7_0'                                                                                           => [
602
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
603
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
604
+			],
605
+			'EE_DMS_Core_4_8_0'                                                                                           => [
606
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
607
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
608
+			],
609
+			'EE_DMS_Core_4_9_0'                                                                                           => [
610
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
611
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
612
+			],
613
+			'EE_DMS_Core_4_10_0'                                                                                          => [
614
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
615
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
616
+				'EE_DMS_Core_4_9_0'                                  => EE_Dependency_Map::load_from_cache,
617
+			],
618
+			'EE_DMS_Core_4_11_0'                                                                                          => [
619
+				'EE_DMS_Core_4_10_0'                                 => EE_Dependency_Map::load_from_cache,
620
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
621
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
622
+			],
623
+			'EE_DMS_Core_4_12_0' => [
624
+				'EE_DMS_Core_4_11_0'                                 => EE_Dependency_Map::load_from_cache,
625
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
626
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
627
+			],
628
+			'EventEspresso\core\services\assets\Registry'                                                                 => [
629
+				'EventEspresso\core\services\assets\AssetCollection' => EE_Dependency_Map::load_new_object,
630
+				'EventEspresso\core\services\assets\AssetManifest'   => EE_Dependency_Map::load_from_cache,
631
+			],
632
+			'EventEspresso\core\services\cache\BasicCacheManager'                                                         => [
633
+				'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
634
+			],
635
+			'EventEspresso\core\services\cache\PostRelatedCacheManager'                                                   => [
636
+				'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
637
+			],
638
+			'EventEspresso\core\domain\services\validation\email\EmailValidationService'                                  => [
639
+				'EE_Registration_Config'                     => EE_Dependency_Map::load_from_cache,
640
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
641
+			],
642
+			'EventEspresso\core\domain\values\EmailAddress'                                                               => [
643
+				null,
644
+				'EventEspresso\core\domain\services\validation\email\EmailValidationService' => EE_Dependency_Map::load_from_cache,
645
+			],
646
+			'EventEspresso\core\services\orm\ModelFieldFactory'                                                           => [
647
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
648
+			],
649
+			'LEGACY_MODELS'                                                                                               => [
650
+				null,
651
+				'EventEspresso\core\services\database\ModelFieldFactory' => EE_Dependency_Map::load_from_cache,
652
+			],
653
+			'EE_Module_Request_Router'                                                                                    => [
654
+				'EE_Request' => EE_Dependency_Map::load_from_cache,
655
+			],
656
+			'EE_Registration_Processor'                                                                                   => [
657
+				'EE_Request' => EE_Dependency_Map::load_from_cache,
658
+			],
659
+			'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'                                      => [
660
+				null,
661
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
662
+				'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
663
+			],
664
+			'EventEspresso\caffeinated\modules\recaptcha_invisible\InvisibleRecaptcha'                                    => [
665
+				'EE_Registration_Config' => EE_Dependency_Map::load_from_cache,
666
+				'EE_Session'             => EE_Dependency_Map::load_from_cache,
667
+			],
668
+			'EventEspresso\modules\ticket_selector\DisplayTicketSelector'                                                 => [
669
+				'EventEspresso\core\domain\entities\users\CurrentUser' => EE_Dependency_Map::load_from_cache,
670
+			],
671
+			'EventEspresso\modules\ticket_selector\ProcessTicketSelector'                                                 => [
672
+				'EE_Core_Config'                                                          => EE_Dependency_Map::load_from_cache,
673
+				'EventEspresso\core\services\request\Request'                             => EE_Dependency_Map::load_from_cache,
674
+				'EE_Session'                                                              => EE_Dependency_Map::load_from_cache,
675
+				'EEM_Ticket'                                                              => EE_Dependency_Map::load_from_cache,
676
+				'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker' => EE_Dependency_Map::load_from_cache,
677
+			],
678
+			'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker'                                     => [
679
+				'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
680
+			],
681
+			'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'                              => [
682
+				'EE_Core_Config'                             => EE_Dependency_Map::load_from_cache,
683
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
684
+			],
685
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'                                => [
686
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
687
+			],
688
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'                               => [
689
+				'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
690
+			],
691
+			'EE_CPT_Strategy'                                                                                             => [
692
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
693
+				'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
694
+			],
695
+			'EventEspresso\core\services\loaders\ObjectIdentifier'                                                        => [
696
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
697
+			],
698
+			'EventEspresso\core\CPTs\CptQueryModifier'                                                                    => [
699
+				null,
700
+				null,
701
+				null,
702
+				'EE_Request_Handler'                          => EE_Dependency_Map::load_from_cache,
703
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
704
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
705
+			],
706
+			'EventEspresso\core\services\dependencies\DependencyResolver'                                                 => [
707
+				'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
708
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
709
+				'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
710
+			],
711
+			'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver'                               => [
712
+				'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
713
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
714
+				'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
715
+			],
716
+			'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'                                          => [
717
+				'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver' => EE_Dependency_Map::load_from_cache,
718
+				'EventEspresso\core\services\loaders\Loader'                                    => EE_Dependency_Map::load_from_cache,
719
+			],
720
+			'EventEspresso\core\services\routing\RouteMatchSpecificationManager'                                          => [
721
+				'EventEspresso\core\services\routing\RouteMatchSpecificationCollection' => EE_Dependency_Map::load_from_cache,
722
+				'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'    => EE_Dependency_Map::load_from_cache,
723
+			],
724
+			'EE_URL_Validation_Strategy'                                                                                  => [
725
+				null,
726
+				null,
727
+				'EventEspresso\core\services\validators\URLValidator' => EE_Dependency_Map::load_from_cache,
728
+			],
729
+			'EventEspresso\core\services\request\files\FilesDataHandler'                                                  => [
730
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
731
+			],
732
+			'EventEspressoBatchRequest\BatchRequestProcessor'                                                             => [
733
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
734
+			],
735
+			'EventEspresso\core\domain\services\converters\RestApiSpoofer'                                                => [
736
+				'WP_REST_Server'                                               => EE_Dependency_Map::load_from_cache,
737
+				'EED_Core_Rest_Api'                                            => EE_Dependency_Map::load_from_cache,
738
+				'EventEspresso\core\libraries\rest_api\controllers\model\Read' => EE_Dependency_Map::load_from_cache,
739
+				null,
740
+			],
741
+			'EventEspresso\core\services\routing\RouteHandler'                                                            => [
742
+				'EventEspresso\core\services\json\JsonDataNodeHandler' => EE_Dependency_Map::load_from_cache,
743
+				'EventEspresso\core\services\loaders\Loader'           => EE_Dependency_Map::load_from_cache,
744
+				'EventEspresso\core\services\request\Request'          => EE_Dependency_Map::load_from_cache,
745
+				'EventEspresso\core\services\routing\RouteCollection'  => EE_Dependency_Map::load_from_cache,
746
+			],
747
+			'EventEspresso\core\services\json\JsonDataNodeHandler'                                                        => [
748
+				'EventEspresso\core\services\json\JsonDataNodeValidator' => EE_Dependency_Map::load_from_cache,
749
+			],
750
+			'EventEspresso\core\services\routing\Router'                                                                  => [
751
+				'EE_Dependency_Map'                                => EE_Dependency_Map::load_from_cache,
752
+				'EventEspresso\core\services\loaders\Loader'       => EE_Dependency_Map::load_from_cache,
753
+				'EventEspresso\core\services\routing\RouteHandler' => EE_Dependency_Map::load_from_cache,
754
+			],
755
+			'EventEspresso\core\services\assets\AssetManifest'                                                            => [
756
+				'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
757
+			],
758
+			'EventEspresso\core\services\assets\AssetManifestFactory'                                                     => [
759
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
760
+			],
761
+			'EventEspresso\core\services\assets\BaristaFactory'                                                           => [
762
+				'EventEspresso\core\services\assets\AssetManifestFactory' => EE_Dependency_Map::load_from_cache,
763
+				'EventEspresso\core\services\loaders\Loader'              => EE_Dependency_Map::load_from_cache,
764
+			],
765
+			'EventEspresso\core\domain\services\capabilities\FeatureFlags'                                                => [
766
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
767
+			],
768
+			'EventEspresso\core\services\addon\AddonManager' => [
769
+				'EventEspresso\core\services\addon\AddonCollection'              => EE_Dependency_Map::load_from_cache,
770
+				'EventEspresso\core\Psr4Autoloader'                              => EE_Dependency_Map::load_from_cache,
771
+				'EventEspresso\core\services\addon\api\v1\RegisterAddon'         => EE_Dependency_Map::load_from_cache,
772
+				'EventEspresso\core\services\addon\api\IncompatibleAddonHandler' => EE_Dependency_Map::load_from_cache,
773
+				'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'  => EE_Dependency_Map::load_from_cache,
774
+			],
775
+			'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler' => [
776
+				'EventEspresso\core\services\request\Request'  => EE_Dependency_Map::load_from_cache,
777
+			],
778
+			'EventEspressoBatchRequest\JobHandlers\ExecuteBatchDeletion' => [
779
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache
780
+			],
781
+			'EventEspressoBatchRequest\JobHandlers\PreviewEventDeletion' => [
782
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache
783
+			],
784
+			'EventEspresso\core\domain\services\admin\events\data\PreviewDeletion' => [
785
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
786
+				'EEM_Event' => EE_Dependency_Map::load_from_cache,
787
+				'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
788
+				'EEM_Registration' => EE_Dependency_Map::load_from_cache
789
+			],
790
+			'EventEspresso\core\domain\services\admin\events\data\ConfirmDeletion' => [
791
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
792
+			],
793
+			'EventEspresso\core\domain\entities\users\CurrentUser' => [
794
+				'EventEspresso\core\domain\entities\users\EventManagers' => EE_Dependency_Map::load_from_cache,
795
+			],
796
+			'EventEspresso\core\services\form\meta\InputTypes' => [
797
+				'EventEspresso\core\services\form\meta\inputs\Block'   => EE_Dependency_Map::load_from_cache,
798
+				'EventEspresso\core\services\form\meta\inputs\Button'   => EE_Dependency_Map::load_from_cache,
799
+				'EventEspresso\core\services\form\meta\inputs\DateTime' => EE_Dependency_Map::load_from_cache,
800
+				'EventEspresso\core\services\form\meta\inputs\Input'    => EE_Dependency_Map::load_from_cache,
801
+				'EventEspresso\core\services\form\meta\inputs\Number'   => EE_Dependency_Map::load_from_cache,
802
+				'EventEspresso\core\services\form\meta\inputs\Phone'    => EE_Dependency_Map::load_from_cache,
803
+				'EventEspresso\core\services\form\meta\inputs\Select'   => EE_Dependency_Map::load_from_cache,
804
+				'EventEspresso\core\services\form\meta\inputs\Text'     => EE_Dependency_Map::load_from_cache,
805
+			],
806
+			'EventEspresso\core\domain\services\registration\form\v1\RegFormDependencyHandler' => [
807
+				'EE_Dependency_Map' => EE_Dependency_Map::load_from_cache,
808
+			],
809
+			'EventEspresso\core\services\calculators\LineItemCalculator' => [
810
+				'EventEspresso\core\services\helpers\DecimalValues' => EE_Dependency_Map::load_from_cache,
811
+			],
812
+			'EventEspresso\core\services\helpers\DecimalValues'          => [
813
+				'EE_Currency_Config' => EE_Dependency_Map::load_from_cache,
814
+			],
815
+		];
816
+	}
817
+
818
+
819
+	/**
820
+	 * Registers how core classes are loaded.
821
+	 * This can either be done by simply providing the name of one of the EE_Registry loader methods such as:
822
+	 *        'EE_Request_Handler' => 'load_core'
823
+	 *        'EE_Messages_Queue'  => 'load_lib'
824
+	 *        'EEH_Debug_Tools'    => 'load_helper'
825
+	 * or, if greater control is required, by providing a custom closure. For example:
826
+	 *        'Some_Class' => function () {
827
+	 *            return new Some_Class();
828
+	 *        },
829
+	 * This is required for instantiating dependencies
830
+	 * where an interface has been type hinted in a class constructor. For example:
831
+	 *        'Required_Interface' => function () {
832
+	 *            return new A_Class_That_Implements_Required_Interface();
833
+	 *        },
834
+	 */
835
+	protected function _register_core_class_loaders()
836
+	{
837
+		$this->_class_loaders = [
838
+			// load_core
839
+			'EE_Dependency_Map'                            => function () {
840
+				return $this;
841
+			},
842
+			'EE_Capabilities'                              => 'load_core',
843
+			'EE_Encryption'                                => 'load_core',
844
+			'EE_Front_Controller'                          => 'load_core',
845
+			'EE_Module_Request_Router'                     => 'load_core',
846
+			'EE_Registry'                                  => 'load_core',
847
+			'EE_Request'                                   => function () {
848
+				return $this->legacy_request;
849
+			},
850
+			'EventEspresso\core\services\request\Request'  => function () {
851
+				return $this->request;
852
+			},
853
+			'EventEspresso\core\services\request\Response' => function () {
854
+				return $this->response;
855
+			},
856
+			'EE_Base'                                      => 'load_core',
857
+			'EE_Request_Handler'                           => 'load_core',
858
+			'EE_Session'                                   => 'load_core',
859
+			'EE_Cron_Tasks'                                => 'load_core',
860
+			'EE_System'                                    => 'load_core',
861
+			'EE_Maintenance_Mode'                          => 'load_core',
862
+			'EE_Register_CPTs'                             => 'load_core',
863
+			'EE_Admin'                                     => 'load_core',
864
+			'EE_CPT_Strategy'                              => 'load_core',
865
+			// load_class
866
+			'EE_Registration_Processor'                    => 'load_class',
867
+			// load_lib
868
+			'EE_Message_Resource_Manager'                  => 'load_lib',
869
+			'EE_Message_Type_Collection'                   => 'load_lib',
870
+			'EE_Message_Type_Collection_Loader'            => 'load_lib',
871
+			'EE_Messenger_Collection'                      => 'load_lib',
872
+			'EE_Messenger_Collection_Loader'               => 'load_lib',
873
+			'EE_Messages_Processor'                        => 'load_lib',
874
+			'EE_Message_Repository'                        => 'load_lib',
875
+			'EE_Messages_Queue'                            => 'load_lib',
876
+			'EE_Messages_Data_Handler_Collection'          => 'load_lib',
877
+			'EE_Message_Template_Group_Collection'         => 'load_lib',
878
+			'EE_Payment_Method_Manager'                    => 'load_lib',
879
+			'EE_DMS_Core_4_1_0'                            => 'load_dms',
880
+			'EE_DMS_Core_4_2_0'                            => 'load_dms',
881
+			'EE_DMS_Core_4_3_0'                            => 'load_dms',
882
+			'EE_DMS_Core_4_5_0'                            => 'load_dms',
883
+			'EE_DMS_Core_4_6_0'                            => 'load_dms',
884
+			'EE_DMS_Core_4_7_0'                            => 'load_dms',
885
+			'EE_DMS_Core_4_8_0'                            => 'load_dms',
886
+			'EE_DMS_Core_4_9_0'                            => 'load_dms',
887
+			'EE_DMS_Core_4_10_0'                           => 'load_dms',
888
+			'EE_DMS_Core_4_11_0'                           => 'load_dms',
889
+			'EE_DMS_Core_4_12_0'                           => 'load_dms',
890
+			'EE_Messages_Generator'                        => static function () {
891
+				return EE_Registry::instance()->load_lib(
892
+					'Messages_Generator',
893
+					[],
894
+					false,
895
+					false
896
+				);
897
+			},
898
+			'EE_Messages_Template_Defaults'                => static function ($arguments = []) {
899
+				return EE_Registry::instance()->load_lib(
900
+					'Messages_Template_Defaults',
901
+					$arguments,
902
+					false,
903
+					false
904
+				);
905
+			},
906
+			// load_helper
907
+			'EEH_Parse_Shortcodes'                         => static function () {
908
+				if (EE_Registry::instance()->load_helper('Parse_Shortcodes')) {
909
+					return new EEH_Parse_Shortcodes();
910
+				}
911
+				return null;
912
+			},
913
+			'EE_Template_Config'                           => static function () {
914
+				return EE_Config::instance()->template_settings;
915
+			},
916
+			'EE_Currency_Config'                           => static function () {
917
+				return EE_Config::instance()->currency;
918
+			},
919
+			'EE_Registration_Config'                       => static function () {
920
+				return EE_Config::instance()->registration;
921
+			},
922
+			'EE_Core_Config'                               => static function () {
923
+				return EE_Config::instance()->core;
924
+			},
925
+			'EventEspresso\core\services\loaders\Loader'   => static function () {
926
+				return LoaderFactory::getLoader();
927
+			},
928
+			'EE_Network_Config'                            => static function () {
929
+				return EE_Network_Config::instance();
930
+			},
931
+			'EE_Config'                                    => static function () {
932
+				return EE_Config::instance();
933
+			},
934
+			'EventEspresso\core\domain\Domain'             => static function () {
935
+				return DomainFactory::getEventEspressoCoreDomain();
936
+			},
937
+			'EE_Admin_Config'                              => static function () {
938
+				return EE_Config::instance()->admin;
939
+			},
940
+			'EE_Organization_Config'                       => static function () {
941
+				return EE_Config::instance()->organization;
942
+			},
943
+			'EE_Network_Core_Config'                       => static function () {
944
+				return EE_Network_Config::instance()->core;
945
+			},
946
+			'EE_Environment_Config'                        => static function () {
947
+				return EE_Config::instance()->environment;
948
+			},
949
+			'EED_Core_Rest_Api'                            => static function () {
950
+				return EED_Core_Rest_Api::instance();
951
+			},
952
+			'WP_REST_Server'                               => static function () {
953
+				return rest_get_server();
954
+			},
955
+			'EventEspresso\core\Psr4Autoloader'            => static function () {
956
+				return EE_Psr4AutoloaderInit::psr4_loader();
957
+			},
958
+		];
959
+	}
960
+
961
+
962
+	/**
963
+	 * can be used for supplying alternate names for classes,
964
+	 * or for connecting interface names to instantiable classes
965
+	 *
966
+	 * @throws InvalidAliasException
967
+	 */
968
+	protected function _register_core_aliases()
969
+	{
970
+		$aliases = [
971
+			'CommandBusInterface'                                                          => 'EventEspresso\core\services\commands\CommandBusInterface',
972
+			'EventEspresso\core\services\commands\CommandBusInterface'                     => 'EventEspresso\core\services\commands\CommandBus',
973
+			'CommandHandlerManagerInterface'                                               => 'EventEspresso\core\services\commands\CommandHandlerManagerInterface',
974
+			'EventEspresso\core\services\commands\CommandHandlerManagerInterface'          => 'EventEspresso\core\services\commands\CommandHandlerManager',
975
+			'CapChecker'                                                                   => 'EventEspresso\core\services\commands\middleware\CapChecker',
976
+			'AddActionHook'                                                                => 'EventEspresso\core\services\commands\middleware\AddActionHook',
977
+			'CapabilitiesChecker'                                                          => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
978
+			'CapabilitiesCheckerInterface'                                                 => 'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface',
979
+			'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface' => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
980
+			'CreateRegistrationService'                                                    => 'EventEspresso\core\domain\services\registration\CreateRegistrationService',
981
+			'CreateRegistrationCommandHandler'                                             => 'EventEspresso\core\services\commands\registration\CreateRegistrationCommand',
982
+			'CopyRegistrationDetailsCommandHandler'                                        => 'EventEspresso\core\services\commands\registration\CopyRegistrationDetailsCommand',
983
+			'CopyRegistrationPaymentsCommandHandler'                                       => 'EventEspresso\core\services\commands\registration\CopyRegistrationPaymentsCommand',
984
+			'CancelRegistrationAndTicketLineItemCommandHandler'                            => 'EventEspresso\core\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler',
985
+			'UpdateRegistrationAndTransactionAfterChangeCommandHandler'                    => 'EventEspresso\core\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler',
986
+			'CreateTicketLineItemCommandHandler'                                           => 'EventEspresso\core\services\commands\ticket\CreateTicketLineItemCommand',
987
+			'CreateTransactionCommandHandler'                                              => 'EventEspresso\core\services\commands\transaction\CreateTransactionCommandHandler',
988
+			'CreateAttendeeCommandHandler'                                                 => 'EventEspresso\core\services\commands\attendee\CreateAttendeeCommandHandler',
989
+			'TableManager'                                                                 => 'EventEspresso\core\services\database\TableManager',
990
+			'TableAnalysis'                                                                => 'EventEspresso\core\services\database\TableAnalysis',
991
+			'EspressoShortcode'                                                            => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
992
+			'ShortcodeInterface'                                                           => 'EventEspresso\core\services\shortcodes\ShortcodeInterface',
993
+			'EventEspresso\core\services\shortcodes\ShortcodeInterface'                    => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
994
+			'EventEspresso\core\services\cache\CacheStorageInterface'                      => 'EventEspresso\core\services\cache\TransientCacheStorage',
995
+			'LoaderInterface'                                                              => 'EventEspresso\core\services\loaders\LoaderInterface',
996
+			'EventEspresso\core\services\loaders\LoaderInterface'                          => 'EventEspresso\core\services\loaders\Loader',
997
+			'CommandFactoryInterface'                                                      => 'EventEspresso\core\services\commands\CommandFactoryInterface',
998
+			'EventEspresso\core\services\commands\CommandFactoryInterface'                 => 'EventEspresso\core\services\commands\CommandFactory',
999
+			'EmailValidatorInterface'                                                      => 'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface',
1000
+			'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface'  => 'EventEspresso\core\domain\services\validation\email\EmailValidationService',
1001
+			'NoticeConverterInterface'                                                     => 'EventEspresso\core\services\notices\NoticeConverterInterface',
1002
+			'EventEspresso\core\services\notices\NoticeConverterInterface'                 => 'EventEspresso\core\services\notices\ConvertNoticesToEeErrors',
1003
+			'NoticesContainerInterface'                                                    => 'EventEspresso\core\services\notices\NoticesContainerInterface',
1004
+			'EventEspresso\core\services\notices\NoticesContainerInterface'                => 'EventEspresso\core\services\notices\NoticesContainer',
1005
+			'EventEspresso\core\services\request\RequestInterface'                         => 'EventEspresso\core\services\request\Request',
1006
+			'EventEspresso\core\services\request\ResponseInterface'                        => 'EventEspresso\core\services\request\Response',
1007
+			'EventEspresso\core\domain\DomainInterface'                                    => 'EventEspresso\core\domain\Domain',
1008
+			'Registration_Processor'                                                       => 'EE_Registration_Processor',
1009
+			'EventEspresso\core\services\assets\AssetManifestInterface'                    => 'EventEspresso\core\services\assets\AssetManifest',
1010
+		];
1011
+		foreach ($aliases as $alias => $fqn) {
1012
+			if (is_array($fqn)) {
1013
+				foreach ($fqn as $class => $for_class) {
1014
+					$this->class_cache->addAlias($class, $alias, $for_class);
1015
+				}
1016
+				continue;
1017
+			}
1018
+			$this->class_cache->addAlias($fqn, $alias);
1019
+		}
1020
+		if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1021
+			$this->class_cache->addAlias(
1022
+				'EventEspresso\core\services\notices\ConvertNoticesToAdminNotices',
1023
+				'EventEspresso\core\services\notices\NoticeConverterInterface'
1024
+			);
1025
+		}
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * This is used to reset the internal map and class_loaders to their original default state at the beginning of the
1031
+	 * request Primarily used by unit tests.
1032
+	 */
1033
+	public function reset()
1034
+	{
1035
+		$this->_register_core_class_loaders();
1036
+		$this->_register_core_dependencies();
1037
+	}
1038
+
1039
+
1040
+	/**
1041
+	 * PLZ NOTE: a better name for this method would be is_alias()
1042
+	 * because it returns TRUE if the provided fully qualified name IS an alias
1043
+	 * WHY?
1044
+	 * Because if a class is type hinting for a concretion,
1045
+	 * then why would we need to find another class to supply it?
1046
+	 * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
1047
+	 * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
1048
+	 * Don't go looking for some substitute.
1049
+	 * Whereas if a class is type hinting for an interface...
1050
+	 * then we need to find an actual class to use.
1051
+	 * So the interface IS the alias for some other FQN,
1052
+	 * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
1053
+	 * represents some other class.
1054
+	 *
1055
+	 * @param string $fqn
1056
+	 * @param string $for_class
1057
+	 * @return bool
1058
+	 * @deprecated 4.9.62.p
1059
+	 */
1060
+	public function has_alias($fqn = '', $for_class = '')
1061
+	{
1062
+		return $this->isAlias($fqn, $for_class);
1063
+	}
1064
+
1065
+
1066
+	/**
1067
+	 * PLZ NOTE: a better name for this method would be get_fqn_for_alias()
1068
+	 * because it returns a FQN for provided alias if one exists, otherwise returns the original $alias
1069
+	 * functions recursively, so that multiple aliases can be used to drill down to a FQN
1070
+	 *  for example:
1071
+	 *      if the following two entries were added to the _aliases array:
1072
+	 *          array(
1073
+	 *              'interface_alias'           => 'some\namespace\interface'
1074
+	 *              'some\namespace\interface'  => 'some\namespace\classname'
1075
+	 *          )
1076
+	 *      then one could use EE_Registry::instance()->create( 'interface_alias' )
1077
+	 *      to load an instance of 'some\namespace\classname'
1078
+	 *
1079
+	 * @param string $alias
1080
+	 * @param string $for_class
1081
+	 * @return string
1082
+	 * @deprecated 4.9.62.p
1083
+	 */
1084
+	public function get_alias($alias = '', $for_class = '')
1085
+	{
1086
+		return $this->getFqnForAlias($alias, $for_class);
1087
+	}
1088 1088
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +2128 added lines, -2128 removed lines patch added patch discarded remove patch
@@ -21,2132 +21,2132 @@
 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
-    /**
31
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
34
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
-     * to keep the registration final prices in-sync with the transaction's total.
36
-     *
37
-     * @param EE_Line_Item $parent_line_item
38
-     * @param string       $name
39
-     * @param float        $unit_price
40
-     * @param string       $description
41
-     * @param int          $quantity
42
-     * @param boolean      $taxable
43
-     * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
-     * @param bool         $return_item
45
-     * @param bool         $recalculate_totals
46
-     * @return boolean|EE_Line_Item success
47
-     * @throws EE_Error
48
-     * @throws ReflectionException
49
-     */
50
-    public static function add_unrelated_item(
51
-        EE_Line_Item $parent_line_item,
52
-        string $name,
53
-        float $unit_price,
54
-        string $description = '',
55
-        int $quantity = 1,
56
-        bool $taxable = false,
57
-        ?string $code = null,
58
-        bool $return_item = false,
59
-        bool $recalculate_totals = true
60
-    ) {
61
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
-        $line_item      = EE_Line_Item::new_instance(
63
-            [
64
-                'LIN_name'       => $name,
65
-                'LIN_desc'       => $description,
66
-                'LIN_unit_price' => $unit_price,
67
-                'LIN_quantity'   => $quantity,
68
-                'LIN_percent'    => null,
69
-                'LIN_is_taxable' => $taxable,
70
-                'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
-                    ? count($items_subtotal->children())
72
-                    : 0,
73
-                'LIN_total'      => (float) $unit_price * (int) $quantity,
74
-                'LIN_type'       => EEM_Line_Item::type_line_item,
75
-                'LIN_code'       => $code,
76
-            ]
77
-        );
78
-        $line_item      = apply_filters(
79
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
-            $line_item,
81
-            $parent_line_item
82
-        );
83
-        $added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
-        return $return_item ? $line_item : $added;
85
-    }
86
-
87
-
88
-    /**
89
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
90
-     * in the correct spot in the line item tree. Does not automatically
91
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
-     * registrations' final prices (which should probably change because of this).
93
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
94
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
96
-     *
97
-     * @param EE_Line_Item $parent_line_item
98
-     * @param string       $name
99
-     * @param float        $percentage_amount
100
-     * @param string       $description
101
-     * @param boolean      $taxable
102
-     * @param string|null  $code
103
-     * @param bool         $return_item
104
-     * @return boolean|EE_Line_Item success
105
-     * @throws EE_Error
106
-     * @throws ReflectionException
107
-     */
108
-    public static function add_percentage_based_item(
109
-        EE_Line_Item $parent_line_item,
110
-        string $name,
111
-        float $percentage_amount,
112
-        string $description = '',
113
-        bool $taxable = false,
114
-        ?string $code = null,
115
-        bool $return_item = false
116
-    ) {
117
-        $total = $percentage_amount * $parent_line_item->total() / 100;
118
-        $line_item = EE_Line_Item::new_instance(
119
-            [
120
-                'LIN_name'       => $name,
121
-                'LIN_desc'       => $description,
122
-                'LIN_unit_price' => 0,
123
-                'LIN_percent'    => $percentage_amount,
124
-                'LIN_quantity'   => 1,
125
-                'LIN_is_taxable' => $taxable,
126
-                'LIN_total'      => (float) $total,
127
-                'LIN_type'       => EEM_Line_Item::type_line_item,
128
-                'LIN_parent'     => $parent_line_item->ID(),
129
-                'LIN_code'       => $code,
130
-            ]
131
-        );
132
-        $line_item = apply_filters(
133
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
-            $line_item
135
-        );
136
-        $added     = $parent_line_item->add_child_line_item($line_item, false);
137
-        return $return_item ? $line_item : $added;
138
-    }
139
-
140
-
141
-    /**
142
-     * Returns the new line item created by adding a purchase of the ticket
143
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
144
-     * If this ticket has already been purchased, just increments its count.
145
-     * Automatically re-calculates the line item totals and updates the related transaction. But
146
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
-     * should probably change because of this).
148
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
150
-     *
151
-     * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
152
-     * @param EE_Ticket    $ticket
153
-     * @param int          $qty
154
-     * @return EE_Line_Item
155
-     * @throws EE_Error
156
-     * @throws InvalidArgumentException
157
-     * @throws InvalidDataTypeException
158
-     * @throws InvalidInterfaceException
159
-     * @throws ReflectionException
160
-     */
161
-    public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
162
-    {
163
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
-            throw new EE_Error(
165
-                sprintf(
166
-                    esc_html__(
167
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
-                        'event_espresso'
169
-                    ),
170
-                    $ticket->ID(),
171
-                    $total_line_item->ID()
172
-                )
173
-            );
174
-        }
175
-        // either increment the qty for an existing ticket
176
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
-        // or add a new one
178
-        if (! $line_item instanceof EE_Line_Item) {
179
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
-        }
181
-        $total_line_item->recalculate_total_including_taxes();
182
-        return $line_item;
183
-    }
184
-
185
-
186
-    /**
187
-     * Returns the new line item created by adding a purchase of the ticket
188
-     *
189
-     * @param EE_Line_Item $total_line_item
190
-     * @param EE_Ticket    $ticket
191
-     * @param int          $qty
192
-     * @return EE_Line_Item
193
-     * @throws EE_Error
194
-     * @throws InvalidArgumentException
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws ReflectionException
198
-     */
199
-    public static function increment_ticket_qty_if_already_in_cart(
200
-        EE_Line_Item $total_line_item,
201
-        EE_Ticket $ticket,
202
-        $qty = 1
203
-    ) {
204
-        $line_item = null;
205
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
-            foreach ((array) $ticket_line_items as $ticket_line_item) {
208
-                if (
209
-                    $ticket_line_item instanceof EE_Line_Item
210
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
-                ) {
212
-                    $line_item = $ticket_line_item;
213
-                    break;
214
-                }
215
-            }
216
-        }
217
-        if ($line_item instanceof EE_Line_Item) {
218
-            EEH_Line_Item::increment_quantity($line_item, $qty);
219
-            return $line_item;
220
-        }
221
-        return null;
222
-    }
223
-
224
-
225
-    /**
226
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
-     * Does NOT save or recalculate other line items totals
228
-     *
229
-     * @param EE_Line_Item $line_item
230
-     * @param int          $qty
231
-     * @return void
232
-     * @throws EE_Error
233
-     * @throws InvalidArgumentException
234
-     * @throws InvalidDataTypeException
235
-     * @throws InvalidInterfaceException
236
-     * @throws ReflectionException
237
-     */
238
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
-    {
240
-        if (! $line_item->is_percent()) {
241
-            $qty += $line_item->quantity();
242
-            $line_item->set_quantity($qty);
243
-            $line_item->set_total($line_item->unit_price() * $qty);
244
-            $line_item->save();
245
-        }
246
-        foreach ($line_item->children() as $child) {
247
-            if ($child->is_sub_line_item()) {
248
-                EEH_Line_Item::update_quantity($child, $qty);
249
-            }
250
-        }
251
-    }
252
-
253
-
254
-    /**
255
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
-     * Does NOT save or recalculate other line items totals
257
-     *
258
-     * @param EE_Line_Item $line_item
259
-     * @param int          $qty
260
-     * @return void
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
-    {
269
-        if (! $line_item->is_percent()) {
270
-            $qty = $line_item->quantity() - $qty;
271
-            $qty = max($qty, 0);
272
-            $line_item->set_quantity($qty);
273
-            $line_item->set_total($line_item->unit_price() * $qty);
274
-            $line_item->save();
275
-        }
276
-        foreach ($line_item->children() as $child) {
277
-            if ($child->is_sub_line_item()) {
278
-                EEH_Line_Item::update_quantity($child, $qty);
279
-            }
280
-        }
281
-    }
282
-
283
-
284
-    /**
285
-     * Updates the line item and its children's quantities to the specified number.
286
-     * Does NOT save them or recalculate totals.
287
-     *
288
-     * @param EE_Line_Item $line_item
289
-     * @param int          $new_quantity
290
-     * @throws EE_Error
291
-     * @throws InvalidArgumentException
292
-     * @throws InvalidDataTypeException
293
-     * @throws InvalidInterfaceException
294
-     * @throws ReflectionException
295
-     */
296
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
-    {
298
-        if (! $line_item->is_percent()) {
299
-            $line_item->set_quantity($new_quantity);
300
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
301
-            $line_item->save();
302
-        }
303
-        foreach ($line_item->children() as $child) {
304
-            if ($child->is_sub_line_item()) {
305
-                EEH_Line_Item::update_quantity($child, $new_quantity);
306
-            }
307
-        }
308
-    }
309
-
310
-
311
-    /**
312
-     * Returns the new line item created by adding a purchase of the ticket
313
-     *
314
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
-     * @param EE_Ticket    $ticket
316
-     * @param int          $qty
317
-     * @return EE_Line_Item
318
-     * @throws EE_Error
319
-     * @throws InvalidArgumentException
320
-     * @throws InvalidDataTypeException
321
-     * @throws InvalidInterfaceException
322
-     * @throws ReflectionException
323
-     */
324
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
-    {
326
-        $datetimes = $ticket->datetimes();
327
-        $first_datetime = reset($datetimes);
328
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
329
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
-            $first_datetime_name = $first_datetime->event()->name();
331
-        }
332
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
-        // get event subtotal line
334
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
-        $taxes = $ticket->tax_price_modifiers();
336
-        // add $ticket to cart
337
-        $line_item = EE_Line_Item::new_instance(array(
338
-            'LIN_name'       => $ticket->name(),
339
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
-            'LIN_unit_price' => $ticket->price(),
341
-            'LIN_quantity'   => $qty,
342
-            'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
-            'LIN_order'      => count($events_sub_total->children()),
344
-            'LIN_total'      => $ticket->price() * $qty,
345
-            'LIN_type'       => EEM_Line_Item::type_line_item,
346
-            'OBJ_ID'         => $ticket->ID(),
347
-            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
-        ));
349
-        $line_item = apply_filters(
350
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
-            $line_item
352
-        );
353
-        if (!$line_item instanceof EE_Line_Item) {
354
-            throw new DomainException(
355
-                esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
-            );
357
-        }
358
-        $events_sub_total->add_child_line_item($line_item);
359
-        // now add the sub-line items
360
-        $running_total = 0;
361
-        $running_pre_tax_total = 0;
362
-        foreach ($ticket->prices() as $price) {
363
-            $sign = $price->is_discount() ? -1 : 1;
364
-            $price_total = $price->is_percent()
365
-                ? $running_pre_tax_total * $price->amount() / 100
366
-                : $price->amount() * $qty;
367
-            if ($price->is_percent()) {
368
-                $percent = $sign * $price->amount();
369
-                $unit_price = 0;
370
-            } else {
371
-                $percent    = 0;
372
-                $unit_price = $sign * $price->amount();
373
-            }
374
-            $sub_line_item = EE_Line_Item::new_instance(array(
375
-                'LIN_name'       => $price->name(),
376
-                'LIN_desc'       => $price->desc(),
377
-                'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
-                'LIN_is_taxable' => false,
379
-                'LIN_order'      => $price->order(),
380
-                'LIN_total'      => $price_total,
381
-                'LIN_pretax'     => 0,
382
-                'LIN_unit_price' => $unit_price,
383
-                'LIN_percent'    => $percent,
384
-                'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
-                'OBJ_ID'         => $price->ID(),
386
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
-            ));
388
-            $sub_line_item = apply_filters(
389
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
-                $sub_line_item
391
-            );
392
-            $running_total += $sign * $price_total;
393
-            $running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
-            $line_item->add_child_line_item($sub_line_item);
395
-        }
396
-        $line_item->setPretaxTotal($running_pre_tax_total);
397
-        return $line_item;
398
-    }
399
-
400
-
401
-    /**
402
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
-     * re-calculates the line item totals and updates the related transaction. But
404
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
-     * should probably change because of this).
406
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
408
-     *
409
-     * @param EE_Line_Item $total_line_item
410
-     * @param EE_Line_Item $item to be added
411
-     * @return boolean
412
-     * @throws EE_Error
413
-     * @throws InvalidArgumentException
414
-     * @throws InvalidDataTypeException
415
-     * @throws InvalidInterfaceException
416
-     * @throws ReflectionException
417
-     */
418
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
-    {
420
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
-            $success = $pre_tax_subtotal->add_child_line_item($item);
423
-        } else {
424
-            return false;
425
-        }
426
-        if ($recalculate_totals) {
427
-            $total_line_item->recalculate_total_including_taxes();
428
-        }
429
-        return $success;
430
-    }
431
-
432
-
433
-    /**
434
-     * cancels an existing ticket line item,
435
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
-     *
438
-     * @param EE_Line_Item $ticket_line_item
439
-     * @param int          $qty
440
-     * @return bool success
441
-     * @throws EE_Error
442
-     * @throws InvalidArgumentException
443
-     * @throws InvalidDataTypeException
444
-     * @throws InvalidInterfaceException
445
-     * @throws ReflectionException
446
-     */
447
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
-    {
449
-        // validate incoming line_item
450
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
-            throw new EE_Error(
452
-                sprintf(
453
-                    esc_html__(
454
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
-                        'event_espresso'
456
-                    ),
457
-                    $ticket_line_item->type()
458
-                )
459
-            );
460
-        }
461
-        if ($ticket_line_item->quantity() < $qty) {
462
-            throw new EE_Error(
463
-                sprintf(
464
-                    esc_html__(
465
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
-                        'event_espresso'
467
-                    ),
468
-                    $qty,
469
-                    $ticket_line_item->quantity()
470
-                )
471
-            );
472
-        }
473
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
-        foreach ($ticket_line_item->children() as $child_line_item) {
476
-            if (
477
-                $child_line_item->is_sub_line_item()
478
-                && ! $child_line_item->is_percent()
479
-                && ! $child_line_item->is_cancellation()
480
-            ) {
481
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
-            }
483
-        }
484
-        // get cancellation sub line item
485
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
-            $ticket_line_item,
487
-            EEM_Line_Item::type_cancellation
488
-        );
489
-        $cancellation_line_item = reset($cancellation_line_item);
490
-        // verify that this ticket was indeed previously cancelled
491
-        if ($cancellation_line_item instanceof EE_Line_Item) {
492
-            // increment cancelled quantity
493
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
-        } else {
495
-            // create cancellation sub line item
496
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
497
-                'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
-                'LIN_desc'       => sprintf(
499
-                    esc_html_x(
500
-                        'Cancelled %1$s : %2$s',
501
-                        'Cancelled Ticket Name : 2015-01-01 11:11',
502
-                        'event_espresso'
503
-                    ),
504
-                    $ticket_line_item->name(),
505
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
-                ),
507
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
-                'LIN_quantity'   => $qty,
509
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
-                'LIN_order'      => count($ticket_line_item->children()),
511
-                'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
-                'LIN_type'       => EEM_Line_Item::type_cancellation,
513
-            ));
514
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
515
-        }
516
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
517
-            // decrement parent line item quantity
518
-            $event_line_item = $ticket_line_item->parent();
519
-            if (
520
-                $event_line_item instanceof EE_Line_Item
521
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
-            ) {
523
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
-                $event_line_item->save();
525
-            }
526
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
-            return true;
528
-        }
529
-        return false;
530
-    }
531
-
532
-
533
-    /**
534
-     * reinstates (un-cancels?) a previously canceled ticket line item,
535
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
-     *
538
-     * @param EE_Line_Item $ticket_line_item
539
-     * @param int          $qty
540
-     * @return bool success
541
-     * @throws EE_Error
542
-     * @throws InvalidArgumentException
543
-     * @throws InvalidDataTypeException
544
-     * @throws InvalidInterfaceException
545
-     * @throws ReflectionException
546
-     */
547
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
-    {
549
-        // validate incoming line_item
550
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
-            throw new EE_Error(
552
-                sprintf(
553
-                    esc_html__(
554
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
-                        'event_espresso'
556
-                    ),
557
-                    $ticket_line_item->type()
558
-                )
559
-            );
560
-        }
561
-        // get cancellation sub line item
562
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
-            $ticket_line_item,
564
-            EEM_Line_Item::type_cancellation
565
-        );
566
-        $cancellation_line_item = reset($cancellation_line_item);
567
-        // verify that this ticket was indeed previously cancelled
568
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
569
-            return false;
570
-        }
571
-        if ($cancellation_line_item->quantity() > $qty) {
572
-            // decrement cancelled quantity
573
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
-        } elseif ($cancellation_line_item->quantity() === $qty) {
575
-            // decrement cancelled quantity in case anyone still has the object kicking around
576
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
-            // delete because quantity will end up as 0
578
-            $cancellation_line_item->delete();
579
-            // and attempt to destroy the object,
580
-            // even though PHP won't actually destroy it until it needs the memory
581
-            unset($cancellation_line_item);
582
-        } else {
583
-            // what ?!?! negative quantity ?!?!
584
-            throw new EE_Error(
585
-                sprintf(
586
-                    esc_html__(
587
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
-                        'event_espresso'
589
-                    ),
590
-                    $qty,
591
-                    $cancellation_line_item->quantity()
592
-                )
593
-            );
594
-        }
595
-        // increment ticket quantity
596
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
598
-            // increment parent line item quantity
599
-            $event_line_item = $ticket_line_item->parent();
600
-            if (
601
-                $event_line_item instanceof EE_Line_Item
602
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
-            ) {
604
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
-            }
606
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
-            return true;
608
-        }
609
-        return false;
610
-    }
611
-
612
-
613
-    /**
614
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
-     *
617
-     * @param EE_Line_Item $line_item
618
-     * @return float
619
-     * @throws EE_Error
620
-     * @throws InvalidArgumentException
621
-     * @throws InvalidDataTypeException
622
-     * @throws InvalidInterfaceException
623
-     * @throws ReflectionException
624
-     */
625
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
-    {
627
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
-        return $grand_total_line_item->recalculate_total_including_taxes();
629
-    }
630
-
631
-
632
-    /**
633
-     * Gets the line item which contains the subtotal of all the items
634
-     *
635
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
-     * @return EE_Line_Item
637
-     * @throws EE_Error
638
-     * @throws InvalidArgumentException
639
-     * @throws InvalidDataTypeException
640
-     * @throws InvalidInterfaceException
641
-     * @throws ReflectionException
642
-     */
643
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
-    {
645
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
-        return $pre_tax_subtotal instanceof EE_Line_Item
647
-            ? $pre_tax_subtotal
648
-            : self::create_pre_tax_subtotal($total_line_item);
649
-    }
650
-
651
-
652
-    /**
653
-     * Gets the line item for the taxes subtotal
654
-     *
655
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
-     * @return EE_Line_Item
657
-     * @throws EE_Error
658
-     * @throws InvalidArgumentException
659
-     * @throws InvalidDataTypeException
660
-     * @throws InvalidInterfaceException
661
-     * @throws ReflectionException
662
-     */
663
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
-    {
665
-        $taxes = $total_line_item->get_child_line_item('taxes');
666
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
-    }
668
-
669
-
670
-    /**
671
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
-     *
673
-     * @param EE_Line_Item   $line_item
674
-     * @param EE_Transaction $transaction
675
-     * @return void
676
-     * @throws EE_Error
677
-     * @throws InvalidArgumentException
678
-     * @throws InvalidDataTypeException
679
-     * @throws InvalidInterfaceException
680
-     * @throws ReflectionException
681
-     */
682
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
-    {
684
-        if ($transaction) {
685
-            /** @type EEM_Transaction $EEM_Transaction */
686
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
-            $line_item->set_TXN_ID($TXN_ID);
689
-        }
690
-    }
691
-
692
-
693
-    /**
694
-     * Creates a new default total line item for the transaction,
695
-     * and its tickets subtotal and taxes subtotal line items (and adds the
696
-     * existing taxes as children of the taxes subtotal line item)
697
-     *
698
-     * @param EE_Transaction $transaction
699
-     * @return EE_Line_Item of type total
700
-     * @throws EE_Error
701
-     * @throws InvalidArgumentException
702
-     * @throws InvalidDataTypeException
703
-     * @throws InvalidInterfaceException
704
-     * @throws ReflectionException
705
-     */
706
-    public static function create_total_line_item($transaction = null)
707
-    {
708
-        $total_line_item = EE_Line_Item::new_instance(array(
709
-            'LIN_code' => 'total',
710
-            'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
-            'LIN_type' => EEM_Line_Item::type_total,
712
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
-        ));
714
-        $total_line_item = apply_filters(
715
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
-            $total_line_item
717
-        );
718
-        self::set_TXN_ID($total_line_item, $transaction);
719
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
720
-        self::create_taxes_subtotal($total_line_item, $transaction);
721
-        return $total_line_item;
722
-    }
723
-
724
-
725
-    /**
726
-     * Creates a default items subtotal line item
727
-     *
728
-     * @param EE_Line_Item   $total_line_item
729
-     * @param EE_Transaction $transaction
730
-     * @return EE_Line_Item
731
-     * @throws EE_Error
732
-     * @throws InvalidArgumentException
733
-     * @throws InvalidDataTypeException
734
-     * @throws InvalidInterfaceException
735
-     * @throws ReflectionException
736
-     */
737
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
-    {
739
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
740
-            'LIN_code' => 'pre-tax-subtotal',
741
-            'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
-            'LIN_type' => EEM_Line_Item::type_sub_total,
743
-        ));
744
-        $pre_tax_line_item = apply_filters(
745
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
-            $pre_tax_line_item
747
-        );
748
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
749
-        $total_line_item->add_child_line_item($pre_tax_line_item);
750
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
751
-        return $pre_tax_line_item;
752
-    }
753
-
754
-
755
-    /**
756
-     * Creates a line item for the taxes subtotal and finds all the tax prices
757
-     * and applies taxes to it
758
-     *
759
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
-     * @param EE_Transaction $transaction
761
-     * @return EE_Line_Item
762
-     * @throws EE_Error
763
-     * @throws InvalidArgumentException
764
-     * @throws InvalidDataTypeException
765
-     * @throws InvalidInterfaceException
766
-     * @throws ReflectionException
767
-     */
768
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
-    {
770
-        $tax_line_item = EE_Line_Item::new_instance(array(
771
-            'LIN_code'  => 'taxes',
772
-            'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
-            'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
-            'LIN_order' => 1000,// this should always come last
775
-        ));
776
-        $tax_line_item = apply_filters(
777
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
-            $tax_line_item
779
-        );
780
-        self::set_TXN_ID($tax_line_item, $transaction);
781
-        $total_line_item->add_child_line_item($tax_line_item);
782
-        // and lastly, add the actual taxes
783
-        self::apply_taxes($total_line_item);
784
-        return $tax_line_item;
785
-    }
786
-
787
-
788
-    /**
789
-     * Creates a default items subtotal line item
790
-     *
791
-     * @param EE_Line_Item   $pre_tax_line_item
792
-     * @param EE_Transaction $transaction
793
-     * @param EE_Event       $event
794
-     * @return EE_Line_Item
795
-     * @throws EE_Error
796
-     * @throws InvalidArgumentException
797
-     * @throws InvalidDataTypeException
798
-     * @throws InvalidInterfaceException
799
-     * @throws ReflectionException
800
-     */
801
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
-    {
803
-        $event_line_item = EE_Line_Item::new_instance(array(
804
-            'LIN_code' => self::get_event_code($event),
805
-            'LIN_name' => self::get_event_name($event),
806
-            'LIN_desc' => self::get_event_desc($event),
807
-            'LIN_type' => EEM_Line_Item::type_sub_total,
808
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
-            'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
-        ));
811
-        $event_line_item = apply_filters(
812
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
-            $event_line_item
814
-        );
815
-        self::set_TXN_ID($event_line_item, $transaction);
816
-        $pre_tax_line_item->add_child_line_item($event_line_item);
817
-        return $event_line_item;
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets what the event ticket's code SHOULD be
823
-     *
824
-     * @param EE_Event $event
825
-     * @return string
826
-     * @throws EE_Error
827
-     */
828
-    public static function get_event_code($event)
829
-    {
830
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets the event name
836
-     *
837
-     * @param EE_Event $event
838
-     * @return string
839
-     * @throws EE_Error
840
-     */
841
-    public static function get_event_name($event)
842
-    {
843
-        return $event instanceof EE_Event
844
-            ? mb_substr($event->name(), 0, 245)
845
-            : esc_html__('Event', 'event_espresso');
846
-    }
847
-
848
-
849
-    /**
850
-     * Gets the event excerpt
851
-     *
852
-     * @param EE_Event $event
853
-     * @return string
854
-     * @throws EE_Error
855
-     */
856
-    public static function get_event_desc($event)
857
-    {
858
-        return $event instanceof EE_Event ? $event->short_description() : '';
859
-    }
860
-
861
-
862
-    /**
863
-     * Given the grand total line item and a ticket, finds the event sub-total
864
-     * line item the ticket's purchase should be added onto
865
-     *
866
-     * @access public
867
-     * @param EE_Line_Item $grand_total the grand total line item
868
-     * @param EE_Ticket    $ticket
869
-     * @return EE_Line_Item
870
-     * @throws EE_Error
871
-     * @throws InvalidArgumentException
872
-     * @throws InvalidDataTypeException
873
-     * @throws InvalidInterfaceException
874
-     * @throws ReflectionException
875
-     */
876
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
-    {
878
-        $first_datetime = $ticket->first_datetime();
879
-        if (! $first_datetime instanceof EE_Datetime) {
880
-            throw new EE_Error(
881
-                sprintf(
882
-                    __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
-                    $ticket->ID()
884
-                )
885
-            );
886
-        }
887
-        $event = $first_datetime->event();
888
-        if (! $event instanceof EE_Event) {
889
-            throw new EE_Error(
890
-                sprintf(
891
-                    esc_html__(
892
-                        'The supplied ticket (ID %d) has no event data associated with it.',
893
-                        'event_espresso'
894
-                    ),
895
-                    $ticket->ID()
896
-                )
897
-            );
898
-        }
899
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
-        if (! $events_sub_total instanceof EE_Line_Item) {
901
-            throw new EE_Error(
902
-                sprintf(
903
-                    esc_html__(
904
-                        'There is no events sub-total for ticket %s on total line item %d',
905
-                        'event_espresso'
906
-                    ),
907
-                    $ticket->ID(),
908
-                    $grand_total->ID()
909
-                )
910
-            );
911
-        }
912
-        return $events_sub_total;
913
-    }
914
-
915
-
916
-    /**
917
-     * Gets the event line item
918
-     *
919
-     * @param EE_Line_Item $grand_total
920
-     * @param EE_Event     $event
921
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
-     * @throws EE_Error
923
-     * @throws InvalidArgumentException
924
-     * @throws InvalidDataTypeException
925
-     * @throws InvalidInterfaceException
926
-     * @throws ReflectionException
927
-     */
928
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
-    {
930
-        /** @type EE_Event $event */
931
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
932
-        $event_line_item = null;
933
-        $found = false;
934
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
-            // default event subtotal, we should only ever find this the first time this method is called
936
-            if (! $event_line_item->OBJ_ID()) {
937
-                // let's use this! but first... set the event details
938
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
-                $found = true;
940
-                break;
941
-            }
942
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
943
-                // found existing line item for this event in the cart, so break out of loop and use this one
944
-                $found = true;
945
-                break;
946
-            }
947
-        }
948
-        if (! $found) {
949
-            // there is no event sub-total yet, so add it
950
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
-            // create a new "event" subtotal below that
952
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
-            // and set the event details
954
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
-        }
956
-        return $event_line_item;
957
-    }
958
-
959
-
960
-    /**
961
-     * Creates a default items subtotal line item
962
-     *
963
-     * @param EE_Line_Item   $event_line_item
964
-     * @param EE_Event       $event
965
-     * @param EE_Transaction $transaction
966
-     * @return void
967
-     * @throws EE_Error
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidDataTypeException
970
-     * @throws InvalidInterfaceException
971
-     * @throws ReflectionException
972
-     */
973
-    public static function set_event_subtotal_details(
974
-        EE_Line_Item $event_line_item,
975
-        EE_Event $event,
976
-        $transaction = null
977
-    ) {
978
-        if ($event instanceof EE_Event) {
979
-            $event_line_item->set_code(self::get_event_code($event));
980
-            $event_line_item->set_name(self::get_event_name($event));
981
-            $event_line_item->set_desc(self::get_event_desc($event));
982
-            $event_line_item->set_OBJ_ID($event->ID());
983
-        }
984
-        self::set_TXN_ID($event_line_item, $transaction);
985
-    }
986
-
987
-
988
-    /**
989
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
-     * any old taxes are removed
992
-     *
993
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
-     * @param bool         $update_txn_status
995
-     * @return bool
996
-     * @throws EE_Error
997
-     * @throws InvalidArgumentException
998
-     * @throws InvalidDataTypeException
999
-     * @throws InvalidInterfaceException
1000
-     * @throws ReflectionException
1001
-     * @throws RuntimeException
1002
-     */
1003
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
-    {
1005
-        $total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
-        $existing_global_taxes = $taxes_line_item->tax_descendants();
1008
-        $updates = false;
1009
-        // loop thru taxes
1010
-        $global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
-        foreach ($global_taxes as $order => $taxes) {
1012
-            foreach ($taxes as $tax) {
1013
-                if ($tax instanceof EE_Price) {
1014
-                    $found = false;
1015
-                    // check if this is already an existing tax
1016
-                    foreach ($existing_global_taxes as $existing_global_tax) {
1017
-                        if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
-                            // maybe update the tax rate in case it has changed
1019
-                            if ($existing_global_tax->percent() !== $tax->amount()) {
1020
-                                $existing_global_tax->set_percent($tax->amount());
1021
-                                $existing_global_tax->save();
1022
-                                $updates = true;
1023
-                            }
1024
-                            $found = true;
1025
-                            break;
1026
-                        }
1027
-                    }
1028
-                    if (! $found) {
1029
-                        // add a new line item for this global tax
1030
-                        $tax_line_item = apply_filters(
1031
-                            'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
-                            EE_Line_Item::new_instance(
1033
-                                [
1034
-                                    'LIN_name'       => $tax->name(),
1035
-                                    'LIN_desc'       => $tax->desc(),
1036
-                                    'LIN_percent'    => $tax->amount(),
1037
-                                    'LIN_is_taxable' => false,
1038
-                                    'LIN_order'      => $order,
1039
-                                    'LIN_total'      => 0,
1040
-                                    'LIN_type'       => EEM_Line_Item::type_tax,
1041
-                                    'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
-                                    'OBJ_ID'         => $tax->ID(),
1043
-                                ]
1044
-                            )
1045
-                        );
1046
-                        $updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
-                    }
1048
-                }
1049
-            }
1050
-        }
1051
-        // only recalculate totals if something changed
1052
-        if ($updates) {
1053
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
-            return true;
1055
-        }
1056
-        return false;
1057
-    }
1058
-
1059
-
1060
-    /**
1061
-     * Ensures that taxes have been applied to the order, if not applies them.
1062
-     * Returns the total amount of tax
1063
-     *
1064
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
-     * @return float
1066
-     * @throws EE_Error
1067
-     * @throws InvalidArgumentException
1068
-     * @throws InvalidDataTypeException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws ReflectionException
1071
-     */
1072
-    public static function ensure_taxes_applied($total_line_item)
1073
-    {
1074
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
-        if (! $taxes_subtotal->children()) {
1076
-            self::apply_taxes($total_line_item);
1077
-        }
1078
-        return $taxes_subtotal->total();
1079
-    }
1080
-
1081
-
1082
-    /**
1083
-     * Deletes ALL children of the passed line item
1084
-     *
1085
-     * @param EE_Line_Item $parent_line_item
1086
-     * @return bool
1087
-     * @throws EE_Error
1088
-     * @throws InvalidArgumentException
1089
-     * @throws InvalidDataTypeException
1090
-     * @throws InvalidInterfaceException
1091
-     * @throws ReflectionException
1092
-     */
1093
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
-    {
1095
-        $deleted = 0;
1096
-        foreach ($parent_line_item->children() as $child_line_item) {
1097
-            if ($child_line_item instanceof EE_Line_Item) {
1098
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
-                if ($child_line_item->ID()) {
1100
-                    $child_line_item->delete();
1101
-                    unset($child_line_item);
1102
-                } else {
1103
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1104
-                }
1105
-                $deleted++;
1106
-            }
1107
-        }
1108
-        return $deleted;
1109
-    }
1110
-
1111
-
1112
-    /**
1113
-     * Deletes the line items as indicated by the line item code(s) provided,
1114
-     * regardless of where they're found in the line item tree. Automatically
1115
-     * re-calculates the line item totals and updates the related transaction. But
1116
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
-     * should probably change because of this).
1118
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
-     *
1121
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
-     * @param array|bool|string $line_item_codes
1123
-     * @return int number of items successfully removed
1124
-     * @throws EE_Error
1125
-     */
1126
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
-    {
1128
-
1129
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
-            EE_Error::doing_it_wrong(
1131
-                'EEH_Line_Item::delete_items',
1132
-                esc_html__(
1133
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
-                    'event_espresso'
1135
-                ),
1136
-                '4.6.18'
1137
-            );
1138
-        }
1139
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
-
1141
-        // check if only a single line_item_id was passed
1142
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
-            // place single line_item_id in an array to appear as multiple line_item_ids
1144
-            $line_item_codes = array($line_item_codes);
1145
-        }
1146
-        $removals = 0;
1147
-        // cycle thru line_item_ids
1148
-        foreach ($line_item_codes as $line_item_id) {
1149
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1150
-        }
1151
-
1152
-        if ($removals > 0) {
1153
-            $total_line_item->recalculate_taxes_and_tax_total();
1154
-            return $removals;
1155
-        } else {
1156
-            return false;
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
-     * tax and updates the total line item accordingly
1164
-     *
1165
-     * @param EE_Line_Item $total_line_item
1166
-     * @param float        $amount
1167
-     * @param string       $name
1168
-     * @param string       $description
1169
-     * @param string       $code
1170
-     * @param boolean      $add_to_existing_line_item
1171
-     *                          if true, and a duplicate line item with the same code is found,
1172
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
-     * @return EE_Line_Item the new tax line item created
1174
-     * @throws EE_Error
1175
-     * @throws InvalidArgumentException
1176
-     * @throws InvalidDataTypeException
1177
-     * @throws InvalidInterfaceException
1178
-     * @throws ReflectionException
1179
-     */
1180
-    public static function set_total_tax_to(
1181
-        EE_Line_Item $total_line_item,
1182
-        $amount,
1183
-        $name = null,
1184
-        $description = null,
1185
-        $code = null,
1186
-        $add_to_existing_line_item = false
1187
-    ) {
1188
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
-        $taxable_total = $total_line_item->taxable_total();
1190
-
1191
-        if ($add_to_existing_line_item) {
1192
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1193
-            EEM_Line_Item::instance()->delete(
1194
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
-            );
1196
-        } else {
1197
-            $new_tax = null;
1198
-            $tax_subtotal->delete_children_line_items();
1199
-        }
1200
-        if ($new_tax) {
1201
-            $new_tax->set_total($new_tax->total() + $amount);
1202
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
-        } else {
1204
-            // no existing tax item. Create it
1205
-            $new_tax = EE_Line_Item::new_instance(array(
1206
-                'TXN_ID'      => $total_line_item->TXN_ID(),
1207
-                'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
-                'LIN_desc'    => $description ?: '',
1209
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
-                'LIN_total'   => $amount,
1211
-                'LIN_parent'  => $tax_subtotal->ID(),
1212
-                'LIN_type'    => EEM_Line_Item::type_tax,
1213
-                'LIN_code'    => $code,
1214
-            ));
1215
-        }
1216
-
1217
-        $new_tax = apply_filters(
1218
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
-            $new_tax,
1220
-            $total_line_item
1221
-        );
1222
-        $new_tax->save();
1223
-        $tax_subtotal->set_total($new_tax->total());
1224
-        $tax_subtotal->save();
1225
-        $total_line_item->recalculate_total_including_taxes();
1226
-        return $new_tax;
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Makes all the line items which are children of $line_item taxable (or not).
1232
-     * Does NOT save the line items
1233
-     *
1234
-     * @param EE_Line_Item $line_item
1235
-     * @param boolean      $taxable
1236
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
-     * @throws EE_Error
1239
-     */
1240
-    public static function set_line_items_taxable(
1241
-        EE_Line_Item $line_item,
1242
-        $taxable = true,
1243
-        $code_substring_for_whitelist = null
1244
-    ) {
1245
-        $whitelisted = false;
1246
-        if ($code_substring_for_whitelist !== null) {
1247
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
-        }
1249
-        if (! $whitelisted && $line_item->is_line_item()) {
1250
-            $line_item->set_is_taxable($taxable);
1251
-        }
1252
-        foreach ($line_item->children() as $child_line_item) {
1253
-            EEH_Line_Item::set_line_items_taxable(
1254
-                $child_line_item,
1255
-                $taxable,
1256
-                $code_substring_for_whitelist
1257
-            );
1258
-        }
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Gets all descendants that are event subtotals
1264
-     *
1265
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
-     * @return EE_Line_Item[]
1268
-     * @throws EE_Error
1269
-     */
1270
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
-    {
1272
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
-    }
1274
-
1275
-
1276
-    /**
1277
-     * Gets all descendants subtotals that match the supplied object type
1278
-     *
1279
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
-     * @param string       $obj_type
1282
-     * @return EE_Line_Item[]
1283
-     * @throws EE_Error
1284
-     */
1285
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
-    {
1287
-        return self::_get_descendants_by_type_and_object_type(
1288
-            $parent_line_item,
1289
-            EEM_Line_Item::type_sub_total,
1290
-            $obj_type
1291
-        );
1292
-    }
1293
-
1294
-
1295
-    /**
1296
-     * Gets all descendants that are tickets
1297
-     *
1298
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
-     * @return EE_Line_Item[]
1301
-     * @throws EE_Error
1302
-     */
1303
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
-    {
1305
-        return self::get_line_items_of_object_type(
1306
-            $parent_line_item,
1307
-            EEM_Line_Item::OBJ_TYPE_TICKET
1308
-        );
1309
-    }
1310
-
1311
-
1312
-    /**
1313
-     * Gets all descendants subtotals that match the supplied object type
1314
-     *
1315
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
-     * @param string       $obj_type
1318
-     * @return EE_Line_Item[]
1319
-     * @throws EE_Error
1320
-     */
1321
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
-    {
1323
-        return self::_get_descendants_by_type_and_object_type(
1324
-            $parent_line_item,
1325
-            EEM_Line_Item::type_line_item,
1326
-            $obj_type
1327
-        );
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
-     *
1334
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1335
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
-     * @return EE_Line_Item[]
1337
-     * @throws EE_Error
1338
-     */
1339
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
-    {
1341
-        return EEH_Line_Item::get_descendants_of_type(
1342
-            $parent_line_item,
1343
-            EEM_Line_Item::type_tax
1344
-        );
1345
-    }
1346
-
1347
-
1348
-    /**
1349
-     * Gets all the real items purchased which are children of this item
1350
-     *
1351
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1352
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
-     * @return EE_Line_Item[]
1354
-     * @throws EE_Error
1355
-     */
1356
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
-    {
1358
-        return EEH_Line_Item::get_descendants_of_type(
1359
-            $parent_line_item,
1360
-            EEM_Line_Item::type_line_item
1361
-        );
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * Gets all descendants of supplied line item that match the supplied line item type
1367
-     *
1368
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
-     * @return EE_Line_Item[]
1372
-     * @throws EE_Error
1373
-     */
1374
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
-    {
1376
-        return self::_get_descendants_by_type_and_object_type(
1377
-            $parent_line_item,
1378
-            $line_item_type,
1379
-            null
1380
-        );
1381
-    }
1382
-
1383
-
1384
-    /**
1385
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
-     * as well
1387
-     *
1388
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
-     * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
-     *                                        searching
1392
-     * @return EE_Line_Item[]
1393
-     * @throws EE_Error
1394
-     */
1395
-    protected static function _get_descendants_by_type_and_object_type(
1396
-        EE_Line_Item $parent_line_item,
1397
-        $line_item_type,
1398
-        $obj_type = null
1399
-    ) {
1400
-        $objects = array();
1401
-        foreach ($parent_line_item->children() as $child_line_item) {
1402
-            if ($child_line_item instanceof EE_Line_Item) {
1403
-                if (
1404
-                    $child_line_item->type() === $line_item_type
1405
-                    && (
1406
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
-                    )
1408
-                ) {
1409
-                    $objects[] = $child_line_item;
1410
-                } else {
1411
-                    // go-through-all-its children looking for more matches
1412
-                    $objects = array_merge(
1413
-                        $objects,
1414
-                        self::_get_descendants_by_type_and_object_type(
1415
-                            $child_line_item,
1416
-                            $line_item_type,
1417
-                            $obj_type
1418
-                        )
1419
-                    );
1420
-                }
1421
-            }
1422
-        }
1423
-        return $objects;
1424
-    }
1425
-
1426
-
1427
-    /**
1428
-     * Gets all descendants subtotals that match the supplied object type
1429
-     *
1430
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
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
-    public static function get_line_items_by_object_type_and_IDs(
1438
-        EE_Line_Item $parent_line_item,
1439
-        $OBJ_type = '',
1440
-        $OBJ_IDs = array()
1441
-    ) {
1442
-        return self::_get_descendants_by_object_type_and_object_ID(
1443
-            $parent_line_item,
1444
-            $OBJ_type,
1445
-            $OBJ_IDs
1446
-        );
1447
-    }
1448
-
1449
-
1450
-    /**
1451
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
-     * as well
1453
-     *
1454
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
-     * @param string       $OBJ_type         object type (like Event)
1456
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1457
-     * @return EE_Line_Item[]
1458
-     * @throws EE_Error
1459
-     */
1460
-    protected static function _get_descendants_by_object_type_and_object_ID(
1461
-        EE_Line_Item $parent_line_item,
1462
-        $OBJ_type,
1463
-        $OBJ_IDs
1464
-    ) {
1465
-        $objects = array();
1466
-        foreach ($parent_line_item->children() as $child_line_item) {
1467
-            if ($child_line_item instanceof EE_Line_Item) {
1468
-                if (
1469
-                    $child_line_item->OBJ_type() === $OBJ_type
1470
-                    && is_array($OBJ_IDs)
1471
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
-                ) {
1473
-                    $objects[] = $child_line_item;
1474
-                } else {
1475
-                    // go-through-all-its children looking for more matches
1476
-                    $objects = array_merge(
1477
-                        $objects,
1478
-                        self::_get_descendants_by_object_type_and_object_ID(
1479
-                            $child_line_item,
1480
-                            $OBJ_type,
1481
-                            $OBJ_IDs
1482
-                        )
1483
-                    );
1484
-                }
1485
-            }
1486
-        }
1487
-        return $objects;
1488
-    }
1489
-
1490
-
1491
-    /**
1492
-     * Uses a breadth-first-search in order to find the nearest descendant of
1493
-     * the specified type and returns it, else NULL
1494
-     *
1495
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1498
-     * @return EE_Line_Item
1499
-     * @throws EE_Error
1500
-     * @throws InvalidArgumentException
1501
-     * @throws InvalidDataTypeException
1502
-     * @throws InvalidInterfaceException
1503
-     * @throws ReflectionException
1504
-     */
1505
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
-    {
1507
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Uses a breadth-first-search in order to find the nearest descendant
1513
-     * having the specified LIN_code and returns it, else NULL
1514
-     *
1515
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
-     * @param string       $code             any value used for LIN_code
1518
-     * @return EE_Line_Item
1519
-     * @throws EE_Error
1520
-     * @throws InvalidArgumentException
1521
-     * @throws InvalidDataTypeException
1522
-     * @throws InvalidInterfaceException
1523
-     * @throws ReflectionException
1524
-     */
1525
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
-    {
1527
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
-    }
1529
-
1530
-
1531
-    /**
1532
-     * Uses a breadth-first-search in order to find the nearest descendant
1533
-     * having the specified LIN_code and returns it, else NULL
1534
-     *
1535
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
-     * @param string       $search_field     name of EE_Line_Item property
1537
-     * @param string       $value            any value stored in $search_field
1538
-     * @return EE_Line_Item
1539
-     * @throws EE_Error
1540
-     * @throws InvalidArgumentException
1541
-     * @throws InvalidDataTypeException
1542
-     * @throws InvalidInterfaceException
1543
-     * @throws ReflectionException
1544
-     */
1545
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
-    {
1547
-        foreach ($parent_line_item->children() as $child) {
1548
-            if ($child->get($search_field) == $value) {
1549
-                return $child;
1550
-            }
1551
-        }
1552
-        foreach ($parent_line_item->children() as $child) {
1553
-            $descendant_found = self::_get_nearest_descendant(
1554
-                $child,
1555
-                $search_field,
1556
-                $value
1557
-            );
1558
-            if ($descendant_found) {
1559
-                return $descendant_found;
1560
-            }
1561
-        }
1562
-        return null;
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
-     * else recursively walks up the line item tree until a parent of type total is found,
1569
-     *
1570
-     * @param EE_Line_Item $line_item
1571
-     * @return EE_Line_Item
1572
-     * @throws EE_Error
1573
-     * @throws ReflectionException
1574
-     */
1575
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
-    {
1577
-        if ($line_item->is_total()) {
1578
-            return $line_item;
1579
-        }
1580
-        if ($line_item->TXN_ID()) {
1581
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1582
-            if ($total_line_item instanceof EE_Line_Item) {
1583
-                return $total_line_item;
1584
-            }
1585
-        } else {
1586
-            $line_item_parent = $line_item->parent();
1587
-            if ($line_item_parent instanceof EE_Line_Item) {
1588
-                if ($line_item_parent->is_total()) {
1589
-                    return $line_item_parent;
1590
-                }
1591
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
-            }
1593
-        }
1594
-        throw new EE_Error(
1595
-            sprintf(
1596
-                esc_html__(
1597
-                    'A valid grand total for line item %1$d was not found.',
1598
-                    'event_espresso'
1599
-                ),
1600
-                $line_item->ID()
1601
-            )
1602
-        );
1603
-    }
1604
-
1605
-
1606
-    /**
1607
-     * Prints out a representation of the line item tree
1608
-     *
1609
-     * @param EE_Line_Item $line_item
1610
-     * @param int          $indentation
1611
-     * @return void
1612
-     * @throws EE_Error
1613
-     */
1614
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
-    {
1616
-        $new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
-        echo $new_line;
1618
-        if (! $indentation) {
1619
-            echo $new_line;
1620
-        }
1621
-        echo str_repeat('. ', $indentation);
1622
-        $breakdown = '';
1623
-        if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
-            if ($line_item->is_percent()) {
1625
-                $breakdown = "{$line_item->percent()}%";
1626
-            } else {
1627
-                $breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
-            }
1629
-        }
1630
-        echo $line_item->name();
1631
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
-        echo "\${$line_item->total()}";
1633
-        if ($breakdown) {
1634
-            echo " ( {$breakdown} )";
1635
-        }
1636
-        if ($line_item->is_taxable()) {
1637
-            echo '  * taxable';
1638
-        }
1639
-        if ($line_item->children()) {
1640
-            foreach ($line_item->children() as $child) {
1641
-                self::visualize($child, $indentation + 1);
1642
-            }
1643
-        }
1644
-        if (! $indentation) {
1645
-            echo $new_line . $new_line;
1646
-        }
1647
-    }
1648
-
1649
-
1650
-    /**
1651
-     * Calculates the registration's final price, taking into account that they
1652
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
-     * and receive a portion of any transaction-wide discounts.
1654
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
-     * and brent's final price should be $5.50.
1658
-     * In order to do this, we basically need to traverse the line item tree calculating
1659
-     * the running totals (just as if we were recalculating the total), but when we identify
1660
-     * regular line items, we need to keep track of their share of the grand total.
1661
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
-     * when there are non-taxable items; otherwise they would be the same)
1664
-     *
1665
-     * @param EE_Line_Item $line_item
1666
-     * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
-     *                                                  can be included in price calculations at this moment
1668
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
-     *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
-     *                                                  the ticket IDs.
1671
-     *                                                  Eg array(
1672
-     *                                                      12 => 4.3
1673
-     *                                                      23 => 8.0
1674
-     *                                                      'total' => 16.6,
1675
-     *                                                      'taxable' => array(
1676
-     *                                                          12 => 10,
1677
-     *                                                          23 => 4
1678
-     *                                                      ).
1679
-     *                                                  So to find which registrations have which final price, we need
1680
-     *                                                  to find which line item is theirs, which can be done with
1681
-     *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
-     *                                                  $registration );`
1683
-     * @throws EE_Error
1684
-     * @throws InvalidArgumentException
1685
-     * @throws InvalidDataTypeException
1686
-     * @throws InvalidInterfaceException
1687
-     * @throws ReflectionException
1688
-     */
1689
-    public static function calculate_reg_final_prices_per_line_item(
1690
-        EE_Line_Item $line_item,
1691
-        $billable_ticket_quantities = array()
1692
-    ) {
1693
-        $running_totals = [
1694
-            'total'   => 0,
1695
-            'taxable' => ['total' => 0]
1696
-        ];
1697
-        foreach ($line_item->children() as $child_line_item) {
1698
-            switch ($child_line_item->type()) {
1699
-                case EEM_Line_Item::type_sub_total:
1700
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
-                        $child_line_item,
1702
-                        $billable_ticket_quantities
1703
-                    );
1704
-                    // combine arrays but preserve numeric keys
1705
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1707
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
-                    break;
1709
-
1710
-                case EEM_Line_Item::type_tax_sub_total:
1711
-                    // find how much the taxes percentage is
1712
-                    if ($child_line_item->percent() !== 0) {
1713
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1714
-                    } else {
1715
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
-                    }
1717
-                    // and apply to all the taxable totals, and add to the pretax totals
1718
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1719
-                        // "total" and "taxable" array key is an exception
1720
-                        if ($line_item_id === 'taxable') {
1721
-                            continue;
1722
-                        }
1723
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
-                    }
1726
-                    break;
1727
-
1728
-                case EEM_Line_Item::type_line_item:
1729
-                    // ticket line items or ????
1730
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
-                        // kk it's a ticket
1732
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1733
-                            // huh? that shouldn't happen.
1734
-                            $running_totals['total'] += $child_line_item->total();
1735
-                        } else {
1736
-                            // its not in our running totals yet. great.
1737
-                            if ($child_line_item->is_taxable()) {
1738
-                                $taxable_amount = $child_line_item->unit_price();
1739
-                            } else {
1740
-                                $taxable_amount = 0;
1741
-                            }
1742
-                            // are we only calculating totals for some tickets?
1743
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1746
-                                    ? $child_line_item->unit_price()
1747
-                                    : 0;
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
-                                    ? $taxable_amount
1750
-                                    : 0;
1751
-                            } else {
1752
-                                $quantity = $child_line_item->quantity();
1753
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
-                            }
1756
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
-                        }
1759
-                    } else {
1760
-                        // it's some other type of item added to the cart
1761
-                        // it should affect the running totals
1762
-                        // basically we want to convert it into a PERCENT modifier. Because
1763
-                        // more clearly affect all registration's final price equally
1764
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1765
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1766
-                            : 1;
1767
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1768
-                            // the "taxable" array key is an exception
1769
-                            if ($line_item_id === 'taxable') {
1770
-                                continue;
1771
-                            }
1772
-                            // update the running totals
1773
-                            // yes this actually even works for the running grand total!
1774
-                            $running_totals[ $line_item_id ] =
1775
-                                $line_items_percent_of_running_total * $this_running_total;
1776
-
1777
-                            if ($child_line_item->is_taxable()) {
1778
-                                $running_totals['taxable'][ $line_item_id ] =
1779
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
-                            }
1781
-                        }
1782
-                    }
1783
-                    break;
1784
-            }
1785
-        }
1786
-        return $running_totals;
1787
-    }
1788
-
1789
-
1790
-    /**
1791
-     * @param EE_Line_Item $total_line_item
1792
-     * @param EE_Line_Item $ticket_line_item
1793
-     * @return float | null
1794
-     * @throws EE_Error
1795
-     * @throws InvalidArgumentException
1796
-     * @throws InvalidDataTypeException
1797
-     * @throws InvalidInterfaceException
1798
-     * @throws OutOfRangeException
1799
-     * @throws ReflectionException
1800
-     */
1801
-    public static function calculate_final_price_for_ticket_line_item(
1802
-        EE_Line_Item $total_line_item,
1803
-        EE_Line_Item $ticket_line_item
1804
-    ) {
1805
-        static $final_prices_per_ticket_line_item = array();
1806
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
-                $total_line_item
1809
-            );
1810
-        }
1811
-        // ok now find this new registration's final price
1812
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
-        }
1815
-        $message = sprintf(
1816
-            esc_html__(
1817
-                'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
-                'event_espresso'
1819
-            ),
1820
-            $ticket_line_item->ID(),
1821
-            $total_line_item->ID()
1822
-        );
1823
-        if (WP_DEBUG) {
1824
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
-            throw new OutOfRangeException($message);
1826
-        }
1827
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
-        return null;
1829
-    }
1830
-
1831
-
1832
-    /**
1833
-     * Creates a duplicate of the line item tree, except only includes billable items
1834
-     * and the portion of line items attributed to billable things
1835
-     *
1836
-     * @param EE_Line_Item      $line_item
1837
-     * @param EE_Registration[] $registrations
1838
-     * @return EE_Line_Item
1839
-     * @throws EE_Error
1840
-     * @throws InvalidArgumentException
1841
-     * @throws InvalidDataTypeException
1842
-     * @throws InvalidInterfaceException
1843
-     * @throws ReflectionException
1844
-     */
1845
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
-    {
1847
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
-        foreach ($line_item->children() as $child_li) {
1849
-            $copy_li->add_child_line_item(
1850
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
-            );
1852
-        }
1853
-        // if this is the grand total line item, make sure the totals all add up
1854
-        // (we could have duplicated this logic AS we copied the line items, but
1855
-        // it seems DRYer this way)
1856
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
-            $copy_li->recalculate_total_including_taxes();
1858
-        }
1859
-        return $copy_li;
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * Creates a new, unsaved line item from $line_item that factors in the
1865
-     * number of billable registrations on $registrations.
1866
-     *
1867
-     * @param EE_Line_Item      $line_item
1868
-     * @param EE_Registration[] $registrations
1869
-     * @return EE_Line_Item
1870
-     * @throws EE_Error
1871
-     * @throws InvalidArgumentException
1872
-     * @throws InvalidDataTypeException
1873
-     * @throws InvalidInterfaceException
1874
-     * @throws ReflectionException
1875
-     */
1876
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
-    {
1878
-        $new_li_fields = $line_item->model_field_array();
1879
-        if (
1880
-            $line_item->type() === EEM_Line_Item::type_line_item &&
1881
-            $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
-        ) {
1883
-            $count = 0;
1884
-            foreach ($registrations as $registration) {
1885
-                if (
1886
-                    $line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
-                    in_array(
1888
-                        $registration->status_ID(),
1889
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1890
-                        true
1891
-                    )
1892
-                ) {
1893
-                    $count++;
1894
-                }
1895
-            }
1896
-            $new_li_fields['LIN_quantity'] = $count;
1897
-        }
1898
-        // don't set the total. We'll leave that up to the code that calculates it
1899
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
-        return EE_Line_Item::new_instance($new_li_fields);
1901
-    }
1902
-
1903
-
1904
-    /**
1905
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1906
-     * are removed, and line items with a quantity of 0
1907
-     *
1908
-     * @param EE_Line_Item $line_item |null
1909
-     * @return EE_Line_Item|null
1910
-     * @throws EE_Error
1911
-     * @throws InvalidArgumentException
1912
-     * @throws InvalidDataTypeException
1913
-     * @throws InvalidInterfaceException
1914
-     * @throws ReflectionException
1915
-     */
1916
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1917
-    {
1918
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
-        if ($copied_li === null) {
1920
-            return null;
1921
-        }
1922
-        // if this is an event subtotal, we want to only include it if it
1923
-        // has a non-zero total and at least one ticket line item child
1924
-        $ticket_children = 0;
1925
-        foreach ($line_item->children() as $child_li) {
1926
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
-            if ($child_li_copy !== null) {
1928
-                $copied_li->add_child_line_item($child_li_copy);
1929
-                if (
1930
-                    $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
-                    $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
-                ) {
1933
-                    $ticket_children++;
1934
-                }
1935
-            }
1936
-        }
1937
-        // if this is an event subtotal with NO ticket children
1938
-        // we basically want to ignore it
1939
-        if (
1940
-            $ticket_children === 0
1941
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1942
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
-            && $line_item->total() === 0
1944
-        ) {
1945
-            return null;
1946
-        }
1947
-        return $copied_li;
1948
-    }
1949
-
1950
-
1951
-    /**
1952
-     * Creates a new, unsaved line item, but if it's a ticket line item
1953
-     * with a total of 0, or a subtotal of 0, returns null instead
1954
-     *
1955
-     * @param EE_Line_Item $line_item
1956
-     * @return EE_Line_Item
1957
-     * @throws EE_Error
1958
-     * @throws InvalidArgumentException
1959
-     * @throws InvalidDataTypeException
1960
-     * @throws InvalidInterfaceException
1961
-     * @throws ReflectionException
1962
-     */
1963
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1964
-    {
1965
-        if (
1966
-            $line_item->type() === EEM_Line_Item::type_line_item
1967
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
-            && $line_item->quantity() === 0
1969
-        ) {
1970
-            return null;
1971
-        }
1972
-        $new_li_fields = $line_item->model_field_array();
1973
-        // don't set the total. We'll leave that up to the code that calculates it
1974
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
-        return EE_Line_Item::new_instance($new_li_fields);
1976
-    }
1977
-
1978
-
1979
-    /**
1980
-     * Cycles through all of the ticket line items for the supplied total line item
1981
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
-     *
1983
-     * @param EE_Line_Item $total_line_item
1984
-     * @since 4.9.79.p
1985
-     * @throws EE_Error
1986
-     * @throws InvalidArgumentException
1987
-     * @throws InvalidDataTypeException
1988
-     * @throws InvalidInterfaceException
1989
-     * @throws ReflectionException
1990
-     */
1991
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
-    {
1993
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
-        foreach ($ticket_line_items as $ticket_line_item) {
1995
-            if (
1996
-                $ticket_line_item instanceof EE_Line_Item
1997
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
-            ) {
1999
-                $ticket = $ticket_line_item->ticket();
2000
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
2002
-                    $ticket_line_item->save();
2003
-                }
2004
-            }
2005
-        }
2006
-    }
2007
-
2008
-
2009
-    /**
2010
-     * @return EE_Line_Item[]
2011
-     * @throws EE_Error
2012
-     * @throws ReflectionException
2013
-     * @since   $VID:$
2014
-     */
2015
-    private static function getGlobalTaxes(): array
2016
-    {
2017
-        if (EEH_Line_Item::$global_taxes === null) {
2018
-
2019
-            /** @type EEM_Price $EEM_Price */
2020
-            $EEM_Price = EE_Registry::instance()->load_model('Price');
2021
-            // get array of taxes via Price Model
2022
-            EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
-            ksort(EEH_Line_Item::$global_taxes);
2024
-        }
2025
-        return EEH_Line_Item::$global_taxes;
2026
-    }
2027
-
2028
-
2029
-
2030
-    /**************************************** @DEPRECATED METHODS *************************************** */
2031
-    /**
2032
-     * @deprecated
2033
-     * @param EE_Line_Item $total_line_item
2034
-     * @return EE_Line_Item
2035
-     * @throws EE_Error
2036
-     * @throws InvalidArgumentException
2037
-     * @throws InvalidDataTypeException
2038
-     * @throws InvalidInterfaceException
2039
-     * @throws ReflectionException
2040
-     */
2041
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
-    {
2043
-        EE_Error::doing_it_wrong(
2044
-            'EEH_Line_Item::get_items_subtotal()',
2045
-            sprintf(
2046
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
-                'EEH_Line_Item::get_pre_tax_subtotal()'
2048
-            ),
2049
-            '4.6.0'
2050
-        );
2051
-        return self::get_pre_tax_subtotal($total_line_item);
2052
-    }
2053
-
2054
-
2055
-    /**
2056
-     * @deprecated
2057
-     * @param EE_Transaction $transaction
2058
-     * @return EE_Line_Item
2059
-     * @throws EE_Error
2060
-     * @throws InvalidArgumentException
2061
-     * @throws InvalidDataTypeException
2062
-     * @throws InvalidInterfaceException
2063
-     * @throws ReflectionException
2064
-     */
2065
-    public static function create_default_total_line_item($transaction = null)
2066
-    {
2067
-        EE_Error::doing_it_wrong(
2068
-            'EEH_Line_Item::create_default_total_line_item()',
2069
-            sprintf(
2070
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
-                'EEH_Line_Item::create_total_line_item()'
2072
-            ),
2073
-            '4.6.0'
2074
-        );
2075
-        return self::create_total_line_item($transaction);
2076
-    }
2077
-
2078
-
2079
-    /**
2080
-     * @deprecated
2081
-     * @param EE_Line_Item   $total_line_item
2082
-     * @param EE_Transaction $transaction
2083
-     * @return EE_Line_Item
2084
-     * @throws EE_Error
2085
-     * @throws InvalidArgumentException
2086
-     * @throws InvalidDataTypeException
2087
-     * @throws InvalidInterfaceException
2088
-     * @throws ReflectionException
2089
-     */
2090
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
-    {
2092
-        EE_Error::doing_it_wrong(
2093
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2094
-            sprintf(
2095
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2097
-            ),
2098
-            '4.6.0'
2099
-        );
2100
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
-    }
2102
-
2103
-
2104
-    /**
2105
-     * @deprecated
2106
-     * @param EE_Line_Item   $total_line_item
2107
-     * @param EE_Transaction $transaction
2108
-     * @return EE_Line_Item
2109
-     * @throws EE_Error
2110
-     * @throws InvalidArgumentException
2111
-     * @throws InvalidDataTypeException
2112
-     * @throws InvalidInterfaceException
2113
-     * @throws ReflectionException
2114
-     */
2115
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
-    {
2117
-        EE_Error::doing_it_wrong(
2118
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2119
-            sprintf(
2120
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
-                'EEH_Line_Item::create_taxes_subtotal()'
2122
-            ),
2123
-            '4.6.0'
2124
-        );
2125
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2126
-    }
2127
-
2128
-
2129
-    /**
2130
-     * @deprecated
2131
-     * @param EE_Line_Item   $total_line_item
2132
-     * @param EE_Transaction $transaction
2133
-     * @return EE_Line_Item
2134
-     * @throws EE_Error
2135
-     * @throws InvalidArgumentException
2136
-     * @throws InvalidDataTypeException
2137
-     * @throws InvalidInterfaceException
2138
-     * @throws ReflectionException
2139
-     */
2140
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
-    {
2142
-        EE_Error::doing_it_wrong(
2143
-            'EEH_Line_Item::create_default_event_subtotal()',
2144
-            sprintf(
2145
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
-                'EEH_Line_Item::create_event_subtotal()'
2147
-            ),
2148
-            '4.6.0'
2149
-        );
2150
-        return self::create_event_subtotal($total_line_item, $transaction);
2151
-    }
24
+	/**
25
+	 * @var EE_Line_Item[]
26
+	 */
27
+	private static $global_taxes;
28
+
29
+
30
+	/**
31
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
34
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
+	 * to keep the registration final prices in-sync with the transaction's total.
36
+	 *
37
+	 * @param EE_Line_Item $parent_line_item
38
+	 * @param string       $name
39
+	 * @param float        $unit_price
40
+	 * @param string       $description
41
+	 * @param int          $quantity
42
+	 * @param boolean      $taxable
43
+	 * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
+	 * @param bool         $return_item
45
+	 * @param bool         $recalculate_totals
46
+	 * @return boolean|EE_Line_Item success
47
+	 * @throws EE_Error
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function add_unrelated_item(
51
+		EE_Line_Item $parent_line_item,
52
+		string $name,
53
+		float $unit_price,
54
+		string $description = '',
55
+		int $quantity = 1,
56
+		bool $taxable = false,
57
+		?string $code = null,
58
+		bool $return_item = false,
59
+		bool $recalculate_totals = true
60
+	) {
61
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
+		$line_item      = EE_Line_Item::new_instance(
63
+			[
64
+				'LIN_name'       => $name,
65
+				'LIN_desc'       => $description,
66
+				'LIN_unit_price' => $unit_price,
67
+				'LIN_quantity'   => $quantity,
68
+				'LIN_percent'    => null,
69
+				'LIN_is_taxable' => $taxable,
70
+				'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
+					? count($items_subtotal->children())
72
+					: 0,
73
+				'LIN_total'      => (float) $unit_price * (int) $quantity,
74
+				'LIN_type'       => EEM_Line_Item::type_line_item,
75
+				'LIN_code'       => $code,
76
+			]
77
+		);
78
+		$line_item      = apply_filters(
79
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
+			$line_item,
81
+			$parent_line_item
82
+		);
83
+		$added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
+		return $return_item ? $line_item : $added;
85
+	}
86
+
87
+
88
+	/**
89
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
90
+	 * in the correct spot in the line item tree. Does not automatically
91
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
+	 * registrations' final prices (which should probably change because of this).
93
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
94
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
96
+	 *
97
+	 * @param EE_Line_Item $parent_line_item
98
+	 * @param string       $name
99
+	 * @param float        $percentage_amount
100
+	 * @param string       $description
101
+	 * @param boolean      $taxable
102
+	 * @param string|null  $code
103
+	 * @param bool         $return_item
104
+	 * @return boolean|EE_Line_Item success
105
+	 * @throws EE_Error
106
+	 * @throws ReflectionException
107
+	 */
108
+	public static function add_percentage_based_item(
109
+		EE_Line_Item $parent_line_item,
110
+		string $name,
111
+		float $percentage_amount,
112
+		string $description = '',
113
+		bool $taxable = false,
114
+		?string $code = null,
115
+		bool $return_item = false
116
+	) {
117
+		$total = $percentage_amount * $parent_line_item->total() / 100;
118
+		$line_item = EE_Line_Item::new_instance(
119
+			[
120
+				'LIN_name'       => $name,
121
+				'LIN_desc'       => $description,
122
+				'LIN_unit_price' => 0,
123
+				'LIN_percent'    => $percentage_amount,
124
+				'LIN_quantity'   => 1,
125
+				'LIN_is_taxable' => $taxable,
126
+				'LIN_total'      => (float) $total,
127
+				'LIN_type'       => EEM_Line_Item::type_line_item,
128
+				'LIN_parent'     => $parent_line_item->ID(),
129
+				'LIN_code'       => $code,
130
+			]
131
+		);
132
+		$line_item = apply_filters(
133
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
+			$line_item
135
+		);
136
+		$added     = $parent_line_item->add_child_line_item($line_item, false);
137
+		return $return_item ? $line_item : $added;
138
+	}
139
+
140
+
141
+	/**
142
+	 * Returns the new line item created by adding a purchase of the ticket
143
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
144
+	 * If this ticket has already been purchased, just increments its count.
145
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
146
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
+	 * should probably change because of this).
148
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
150
+	 *
151
+	 * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
152
+	 * @param EE_Ticket    $ticket
153
+	 * @param int          $qty
154
+	 * @return EE_Line_Item
155
+	 * @throws EE_Error
156
+	 * @throws InvalidArgumentException
157
+	 * @throws InvalidDataTypeException
158
+	 * @throws InvalidInterfaceException
159
+	 * @throws ReflectionException
160
+	 */
161
+	public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
162
+	{
163
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
+			throw new EE_Error(
165
+				sprintf(
166
+					esc_html__(
167
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
+						'event_espresso'
169
+					),
170
+					$ticket->ID(),
171
+					$total_line_item->ID()
172
+				)
173
+			);
174
+		}
175
+		// either increment the qty for an existing ticket
176
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
+		// or add a new one
178
+		if (! $line_item instanceof EE_Line_Item) {
179
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
+		}
181
+		$total_line_item->recalculate_total_including_taxes();
182
+		return $line_item;
183
+	}
184
+
185
+
186
+	/**
187
+	 * Returns the new line item created by adding a purchase of the ticket
188
+	 *
189
+	 * @param EE_Line_Item $total_line_item
190
+	 * @param EE_Ticket    $ticket
191
+	 * @param int          $qty
192
+	 * @return EE_Line_Item
193
+	 * @throws EE_Error
194
+	 * @throws InvalidArgumentException
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws ReflectionException
198
+	 */
199
+	public static function increment_ticket_qty_if_already_in_cart(
200
+		EE_Line_Item $total_line_item,
201
+		EE_Ticket $ticket,
202
+		$qty = 1
203
+	) {
204
+		$line_item = null;
205
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
+			foreach ((array) $ticket_line_items as $ticket_line_item) {
208
+				if (
209
+					$ticket_line_item instanceof EE_Line_Item
210
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
+				) {
212
+					$line_item = $ticket_line_item;
213
+					break;
214
+				}
215
+			}
216
+		}
217
+		if ($line_item instanceof EE_Line_Item) {
218
+			EEH_Line_Item::increment_quantity($line_item, $qty);
219
+			return $line_item;
220
+		}
221
+		return null;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
+	 * Does NOT save or recalculate other line items totals
228
+	 *
229
+	 * @param EE_Line_Item $line_item
230
+	 * @param int          $qty
231
+	 * @return void
232
+	 * @throws EE_Error
233
+	 * @throws InvalidArgumentException
234
+	 * @throws InvalidDataTypeException
235
+	 * @throws InvalidInterfaceException
236
+	 * @throws ReflectionException
237
+	 */
238
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
+	{
240
+		if (! $line_item->is_percent()) {
241
+			$qty += $line_item->quantity();
242
+			$line_item->set_quantity($qty);
243
+			$line_item->set_total($line_item->unit_price() * $qty);
244
+			$line_item->save();
245
+		}
246
+		foreach ($line_item->children() as $child) {
247
+			if ($child->is_sub_line_item()) {
248
+				EEH_Line_Item::update_quantity($child, $qty);
249
+			}
250
+		}
251
+	}
252
+
253
+
254
+	/**
255
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
+	 * Does NOT save or recalculate other line items totals
257
+	 *
258
+	 * @param EE_Line_Item $line_item
259
+	 * @param int          $qty
260
+	 * @return void
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
+	{
269
+		if (! $line_item->is_percent()) {
270
+			$qty = $line_item->quantity() - $qty;
271
+			$qty = max($qty, 0);
272
+			$line_item->set_quantity($qty);
273
+			$line_item->set_total($line_item->unit_price() * $qty);
274
+			$line_item->save();
275
+		}
276
+		foreach ($line_item->children() as $child) {
277
+			if ($child->is_sub_line_item()) {
278
+				EEH_Line_Item::update_quantity($child, $qty);
279
+			}
280
+		}
281
+	}
282
+
283
+
284
+	/**
285
+	 * Updates the line item and its children's quantities to the specified number.
286
+	 * Does NOT save them or recalculate totals.
287
+	 *
288
+	 * @param EE_Line_Item $line_item
289
+	 * @param int          $new_quantity
290
+	 * @throws EE_Error
291
+	 * @throws InvalidArgumentException
292
+	 * @throws InvalidDataTypeException
293
+	 * @throws InvalidInterfaceException
294
+	 * @throws ReflectionException
295
+	 */
296
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
+	{
298
+		if (! $line_item->is_percent()) {
299
+			$line_item->set_quantity($new_quantity);
300
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
301
+			$line_item->save();
302
+		}
303
+		foreach ($line_item->children() as $child) {
304
+			if ($child->is_sub_line_item()) {
305
+				EEH_Line_Item::update_quantity($child, $new_quantity);
306
+			}
307
+		}
308
+	}
309
+
310
+
311
+	/**
312
+	 * Returns the new line item created by adding a purchase of the ticket
313
+	 *
314
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
+	 * @param EE_Ticket    $ticket
316
+	 * @param int          $qty
317
+	 * @return EE_Line_Item
318
+	 * @throws EE_Error
319
+	 * @throws InvalidArgumentException
320
+	 * @throws InvalidDataTypeException
321
+	 * @throws InvalidInterfaceException
322
+	 * @throws ReflectionException
323
+	 */
324
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
+	{
326
+		$datetimes = $ticket->datetimes();
327
+		$first_datetime = reset($datetimes);
328
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
329
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
+			$first_datetime_name = $first_datetime->event()->name();
331
+		}
332
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
+		// get event subtotal line
334
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
+		$taxes = $ticket->tax_price_modifiers();
336
+		// add $ticket to cart
337
+		$line_item = EE_Line_Item::new_instance(array(
338
+			'LIN_name'       => $ticket->name(),
339
+			'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
+			'LIN_unit_price' => $ticket->price(),
341
+			'LIN_quantity'   => $qty,
342
+			'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
+			'LIN_order'      => count($events_sub_total->children()),
344
+			'LIN_total'      => $ticket->price() * $qty,
345
+			'LIN_type'       => EEM_Line_Item::type_line_item,
346
+			'OBJ_ID'         => $ticket->ID(),
347
+			'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
+		));
349
+		$line_item = apply_filters(
350
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
+			$line_item
352
+		);
353
+		if (!$line_item instanceof EE_Line_Item) {
354
+			throw new DomainException(
355
+				esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
+			);
357
+		}
358
+		$events_sub_total->add_child_line_item($line_item);
359
+		// now add the sub-line items
360
+		$running_total = 0;
361
+		$running_pre_tax_total = 0;
362
+		foreach ($ticket->prices() as $price) {
363
+			$sign = $price->is_discount() ? -1 : 1;
364
+			$price_total = $price->is_percent()
365
+				? $running_pre_tax_total * $price->amount() / 100
366
+				: $price->amount() * $qty;
367
+			if ($price->is_percent()) {
368
+				$percent = $sign * $price->amount();
369
+				$unit_price = 0;
370
+			} else {
371
+				$percent    = 0;
372
+				$unit_price = $sign * $price->amount();
373
+			}
374
+			$sub_line_item = EE_Line_Item::new_instance(array(
375
+				'LIN_name'       => $price->name(),
376
+				'LIN_desc'       => $price->desc(),
377
+				'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
+				'LIN_is_taxable' => false,
379
+				'LIN_order'      => $price->order(),
380
+				'LIN_total'      => $price_total,
381
+				'LIN_pretax'     => 0,
382
+				'LIN_unit_price' => $unit_price,
383
+				'LIN_percent'    => $percent,
384
+				'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
+				'OBJ_ID'         => $price->ID(),
386
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
+			));
388
+			$sub_line_item = apply_filters(
389
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
+				$sub_line_item
391
+			);
392
+			$running_total += $sign * $price_total;
393
+			$running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
+			$line_item->add_child_line_item($sub_line_item);
395
+		}
396
+		$line_item->setPretaxTotal($running_pre_tax_total);
397
+		return $line_item;
398
+	}
399
+
400
+
401
+	/**
402
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
+	 * re-calculates the line item totals and updates the related transaction. But
404
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
+	 * should probably change because of this).
406
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
408
+	 *
409
+	 * @param EE_Line_Item $total_line_item
410
+	 * @param EE_Line_Item $item to be added
411
+	 * @return boolean
412
+	 * @throws EE_Error
413
+	 * @throws InvalidArgumentException
414
+	 * @throws InvalidDataTypeException
415
+	 * @throws InvalidInterfaceException
416
+	 * @throws ReflectionException
417
+	 */
418
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
+	{
420
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
+			$success = $pre_tax_subtotal->add_child_line_item($item);
423
+		} else {
424
+			return false;
425
+		}
426
+		if ($recalculate_totals) {
427
+			$total_line_item->recalculate_total_including_taxes();
428
+		}
429
+		return $success;
430
+	}
431
+
432
+
433
+	/**
434
+	 * cancels an existing ticket line item,
435
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
+	 *
438
+	 * @param EE_Line_Item $ticket_line_item
439
+	 * @param int          $qty
440
+	 * @return bool success
441
+	 * @throws EE_Error
442
+	 * @throws InvalidArgumentException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws InvalidInterfaceException
445
+	 * @throws ReflectionException
446
+	 */
447
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
+	{
449
+		// validate incoming line_item
450
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
+			throw new EE_Error(
452
+				sprintf(
453
+					esc_html__(
454
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
+						'event_espresso'
456
+					),
457
+					$ticket_line_item->type()
458
+				)
459
+			);
460
+		}
461
+		if ($ticket_line_item->quantity() < $qty) {
462
+			throw new EE_Error(
463
+				sprintf(
464
+					esc_html__(
465
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
+						'event_espresso'
467
+					),
468
+					$qty,
469
+					$ticket_line_item->quantity()
470
+				)
471
+			);
472
+		}
473
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
+		foreach ($ticket_line_item->children() as $child_line_item) {
476
+			if (
477
+				$child_line_item->is_sub_line_item()
478
+				&& ! $child_line_item->is_percent()
479
+				&& ! $child_line_item->is_cancellation()
480
+			) {
481
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
+			}
483
+		}
484
+		// get cancellation sub line item
485
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
+			$ticket_line_item,
487
+			EEM_Line_Item::type_cancellation
488
+		);
489
+		$cancellation_line_item = reset($cancellation_line_item);
490
+		// verify that this ticket was indeed previously cancelled
491
+		if ($cancellation_line_item instanceof EE_Line_Item) {
492
+			// increment cancelled quantity
493
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
+		} else {
495
+			// create cancellation sub line item
496
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
497
+				'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
+				'LIN_desc'       => sprintf(
499
+					esc_html_x(
500
+						'Cancelled %1$s : %2$s',
501
+						'Cancelled Ticket Name : 2015-01-01 11:11',
502
+						'event_espresso'
503
+					),
504
+					$ticket_line_item->name(),
505
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
+				),
507
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
+				'LIN_quantity'   => $qty,
509
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
+				'LIN_order'      => count($ticket_line_item->children()),
511
+				'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
+				'LIN_type'       => EEM_Line_Item::type_cancellation,
513
+			));
514
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
515
+		}
516
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
517
+			// decrement parent line item quantity
518
+			$event_line_item = $ticket_line_item->parent();
519
+			if (
520
+				$event_line_item instanceof EE_Line_Item
521
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
+			) {
523
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
+				$event_line_item->save();
525
+			}
526
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
+			return true;
528
+		}
529
+		return false;
530
+	}
531
+
532
+
533
+	/**
534
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
535
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
+	 *
538
+	 * @param EE_Line_Item $ticket_line_item
539
+	 * @param int          $qty
540
+	 * @return bool success
541
+	 * @throws EE_Error
542
+	 * @throws InvalidArgumentException
543
+	 * @throws InvalidDataTypeException
544
+	 * @throws InvalidInterfaceException
545
+	 * @throws ReflectionException
546
+	 */
547
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
+	{
549
+		// validate incoming line_item
550
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
+			throw new EE_Error(
552
+				sprintf(
553
+					esc_html__(
554
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
+						'event_espresso'
556
+					),
557
+					$ticket_line_item->type()
558
+				)
559
+			);
560
+		}
561
+		// get cancellation sub line item
562
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
+			$ticket_line_item,
564
+			EEM_Line_Item::type_cancellation
565
+		);
566
+		$cancellation_line_item = reset($cancellation_line_item);
567
+		// verify that this ticket was indeed previously cancelled
568
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
569
+			return false;
570
+		}
571
+		if ($cancellation_line_item->quantity() > $qty) {
572
+			// decrement cancelled quantity
573
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
+		} elseif ($cancellation_line_item->quantity() === $qty) {
575
+			// decrement cancelled quantity in case anyone still has the object kicking around
576
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
+			// delete because quantity will end up as 0
578
+			$cancellation_line_item->delete();
579
+			// and attempt to destroy the object,
580
+			// even though PHP won't actually destroy it until it needs the memory
581
+			unset($cancellation_line_item);
582
+		} else {
583
+			// what ?!?! negative quantity ?!?!
584
+			throw new EE_Error(
585
+				sprintf(
586
+					esc_html__(
587
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
+						'event_espresso'
589
+					),
590
+					$qty,
591
+					$cancellation_line_item->quantity()
592
+				)
593
+			);
594
+		}
595
+		// increment ticket quantity
596
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
598
+			// increment parent line item quantity
599
+			$event_line_item = $ticket_line_item->parent();
600
+			if (
601
+				$event_line_item instanceof EE_Line_Item
602
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
+			) {
604
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
+			}
606
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
+			return true;
608
+		}
609
+		return false;
610
+	}
611
+
612
+
613
+	/**
614
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
+	 *
617
+	 * @param EE_Line_Item $line_item
618
+	 * @return float
619
+	 * @throws EE_Error
620
+	 * @throws InvalidArgumentException
621
+	 * @throws InvalidDataTypeException
622
+	 * @throws InvalidInterfaceException
623
+	 * @throws ReflectionException
624
+	 */
625
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
+	{
627
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
+		return $grand_total_line_item->recalculate_total_including_taxes();
629
+	}
630
+
631
+
632
+	/**
633
+	 * Gets the line item which contains the subtotal of all the items
634
+	 *
635
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
+	 * @return EE_Line_Item
637
+	 * @throws EE_Error
638
+	 * @throws InvalidArgumentException
639
+	 * @throws InvalidDataTypeException
640
+	 * @throws InvalidInterfaceException
641
+	 * @throws ReflectionException
642
+	 */
643
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
+	{
645
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
+		return $pre_tax_subtotal instanceof EE_Line_Item
647
+			? $pre_tax_subtotal
648
+			: self::create_pre_tax_subtotal($total_line_item);
649
+	}
650
+
651
+
652
+	/**
653
+	 * Gets the line item for the taxes subtotal
654
+	 *
655
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
+	 * @return EE_Line_Item
657
+	 * @throws EE_Error
658
+	 * @throws InvalidArgumentException
659
+	 * @throws InvalidDataTypeException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws ReflectionException
662
+	 */
663
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
+	{
665
+		$taxes = $total_line_item->get_child_line_item('taxes');
666
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
+	}
668
+
669
+
670
+	/**
671
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
+	 *
673
+	 * @param EE_Line_Item   $line_item
674
+	 * @param EE_Transaction $transaction
675
+	 * @return void
676
+	 * @throws EE_Error
677
+	 * @throws InvalidArgumentException
678
+	 * @throws InvalidDataTypeException
679
+	 * @throws InvalidInterfaceException
680
+	 * @throws ReflectionException
681
+	 */
682
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
+	{
684
+		if ($transaction) {
685
+			/** @type EEM_Transaction $EEM_Transaction */
686
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
+			$line_item->set_TXN_ID($TXN_ID);
689
+		}
690
+	}
691
+
692
+
693
+	/**
694
+	 * Creates a new default total line item for the transaction,
695
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
696
+	 * existing taxes as children of the taxes subtotal line item)
697
+	 *
698
+	 * @param EE_Transaction $transaction
699
+	 * @return EE_Line_Item of type total
700
+	 * @throws EE_Error
701
+	 * @throws InvalidArgumentException
702
+	 * @throws InvalidDataTypeException
703
+	 * @throws InvalidInterfaceException
704
+	 * @throws ReflectionException
705
+	 */
706
+	public static function create_total_line_item($transaction = null)
707
+	{
708
+		$total_line_item = EE_Line_Item::new_instance(array(
709
+			'LIN_code' => 'total',
710
+			'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
+			'LIN_type' => EEM_Line_Item::type_total,
712
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
+		));
714
+		$total_line_item = apply_filters(
715
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
+			$total_line_item
717
+		);
718
+		self::set_TXN_ID($total_line_item, $transaction);
719
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
720
+		self::create_taxes_subtotal($total_line_item, $transaction);
721
+		return $total_line_item;
722
+	}
723
+
724
+
725
+	/**
726
+	 * Creates a default items subtotal line item
727
+	 *
728
+	 * @param EE_Line_Item   $total_line_item
729
+	 * @param EE_Transaction $transaction
730
+	 * @return EE_Line_Item
731
+	 * @throws EE_Error
732
+	 * @throws InvalidArgumentException
733
+	 * @throws InvalidDataTypeException
734
+	 * @throws InvalidInterfaceException
735
+	 * @throws ReflectionException
736
+	 */
737
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
+	{
739
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
740
+			'LIN_code' => 'pre-tax-subtotal',
741
+			'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
+			'LIN_type' => EEM_Line_Item::type_sub_total,
743
+		));
744
+		$pre_tax_line_item = apply_filters(
745
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
+			$pre_tax_line_item
747
+		);
748
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
749
+		$total_line_item->add_child_line_item($pre_tax_line_item);
750
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
751
+		return $pre_tax_line_item;
752
+	}
753
+
754
+
755
+	/**
756
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
757
+	 * and applies taxes to it
758
+	 *
759
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
+	 * @param EE_Transaction $transaction
761
+	 * @return EE_Line_Item
762
+	 * @throws EE_Error
763
+	 * @throws InvalidArgumentException
764
+	 * @throws InvalidDataTypeException
765
+	 * @throws InvalidInterfaceException
766
+	 * @throws ReflectionException
767
+	 */
768
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
+	{
770
+		$tax_line_item = EE_Line_Item::new_instance(array(
771
+			'LIN_code'  => 'taxes',
772
+			'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
+			'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
+			'LIN_order' => 1000,// this should always come last
775
+		));
776
+		$tax_line_item = apply_filters(
777
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
+			$tax_line_item
779
+		);
780
+		self::set_TXN_ID($tax_line_item, $transaction);
781
+		$total_line_item->add_child_line_item($tax_line_item);
782
+		// and lastly, add the actual taxes
783
+		self::apply_taxes($total_line_item);
784
+		return $tax_line_item;
785
+	}
786
+
787
+
788
+	/**
789
+	 * Creates a default items subtotal line item
790
+	 *
791
+	 * @param EE_Line_Item   $pre_tax_line_item
792
+	 * @param EE_Transaction $transaction
793
+	 * @param EE_Event       $event
794
+	 * @return EE_Line_Item
795
+	 * @throws EE_Error
796
+	 * @throws InvalidArgumentException
797
+	 * @throws InvalidDataTypeException
798
+	 * @throws InvalidInterfaceException
799
+	 * @throws ReflectionException
800
+	 */
801
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
+	{
803
+		$event_line_item = EE_Line_Item::new_instance(array(
804
+			'LIN_code' => self::get_event_code($event),
805
+			'LIN_name' => self::get_event_name($event),
806
+			'LIN_desc' => self::get_event_desc($event),
807
+			'LIN_type' => EEM_Line_Item::type_sub_total,
808
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
+			'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
+		));
811
+		$event_line_item = apply_filters(
812
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
+			$event_line_item
814
+		);
815
+		self::set_TXN_ID($event_line_item, $transaction);
816
+		$pre_tax_line_item->add_child_line_item($event_line_item);
817
+		return $event_line_item;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets what the event ticket's code SHOULD be
823
+	 *
824
+	 * @param EE_Event $event
825
+	 * @return string
826
+	 * @throws EE_Error
827
+	 */
828
+	public static function get_event_code($event)
829
+	{
830
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets the event name
836
+	 *
837
+	 * @param EE_Event $event
838
+	 * @return string
839
+	 * @throws EE_Error
840
+	 */
841
+	public static function get_event_name($event)
842
+	{
843
+		return $event instanceof EE_Event
844
+			? mb_substr($event->name(), 0, 245)
845
+			: esc_html__('Event', 'event_espresso');
846
+	}
847
+
848
+
849
+	/**
850
+	 * Gets the event excerpt
851
+	 *
852
+	 * @param EE_Event $event
853
+	 * @return string
854
+	 * @throws EE_Error
855
+	 */
856
+	public static function get_event_desc($event)
857
+	{
858
+		return $event instanceof EE_Event ? $event->short_description() : '';
859
+	}
860
+
861
+
862
+	/**
863
+	 * Given the grand total line item and a ticket, finds the event sub-total
864
+	 * line item the ticket's purchase should be added onto
865
+	 *
866
+	 * @access public
867
+	 * @param EE_Line_Item $grand_total the grand total line item
868
+	 * @param EE_Ticket    $ticket
869
+	 * @return EE_Line_Item
870
+	 * @throws EE_Error
871
+	 * @throws InvalidArgumentException
872
+	 * @throws InvalidDataTypeException
873
+	 * @throws InvalidInterfaceException
874
+	 * @throws ReflectionException
875
+	 */
876
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
+	{
878
+		$first_datetime = $ticket->first_datetime();
879
+		if (! $first_datetime instanceof EE_Datetime) {
880
+			throw new EE_Error(
881
+				sprintf(
882
+					__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
+					$ticket->ID()
884
+				)
885
+			);
886
+		}
887
+		$event = $first_datetime->event();
888
+		if (! $event instanceof EE_Event) {
889
+			throw new EE_Error(
890
+				sprintf(
891
+					esc_html__(
892
+						'The supplied ticket (ID %d) has no event data associated with it.',
893
+						'event_espresso'
894
+					),
895
+					$ticket->ID()
896
+				)
897
+			);
898
+		}
899
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
+		if (! $events_sub_total instanceof EE_Line_Item) {
901
+			throw new EE_Error(
902
+				sprintf(
903
+					esc_html__(
904
+						'There is no events sub-total for ticket %s on total line item %d',
905
+						'event_espresso'
906
+					),
907
+					$ticket->ID(),
908
+					$grand_total->ID()
909
+				)
910
+			);
911
+		}
912
+		return $events_sub_total;
913
+	}
914
+
915
+
916
+	/**
917
+	 * Gets the event line item
918
+	 *
919
+	 * @param EE_Line_Item $grand_total
920
+	 * @param EE_Event     $event
921
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
+	 * @throws EE_Error
923
+	 * @throws InvalidArgumentException
924
+	 * @throws InvalidDataTypeException
925
+	 * @throws InvalidInterfaceException
926
+	 * @throws ReflectionException
927
+	 */
928
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
+	{
930
+		/** @type EE_Event $event */
931
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
932
+		$event_line_item = null;
933
+		$found = false;
934
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
+			// default event subtotal, we should only ever find this the first time this method is called
936
+			if (! $event_line_item->OBJ_ID()) {
937
+				// let's use this! but first... set the event details
938
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
+				$found = true;
940
+				break;
941
+			}
942
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
943
+				// found existing line item for this event in the cart, so break out of loop and use this one
944
+				$found = true;
945
+				break;
946
+			}
947
+		}
948
+		if (! $found) {
949
+			// there is no event sub-total yet, so add it
950
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
+			// create a new "event" subtotal below that
952
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
+			// and set the event details
954
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
+		}
956
+		return $event_line_item;
957
+	}
958
+
959
+
960
+	/**
961
+	 * Creates a default items subtotal line item
962
+	 *
963
+	 * @param EE_Line_Item   $event_line_item
964
+	 * @param EE_Event       $event
965
+	 * @param EE_Transaction $transaction
966
+	 * @return void
967
+	 * @throws EE_Error
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidDataTypeException
970
+	 * @throws InvalidInterfaceException
971
+	 * @throws ReflectionException
972
+	 */
973
+	public static function set_event_subtotal_details(
974
+		EE_Line_Item $event_line_item,
975
+		EE_Event $event,
976
+		$transaction = null
977
+	) {
978
+		if ($event instanceof EE_Event) {
979
+			$event_line_item->set_code(self::get_event_code($event));
980
+			$event_line_item->set_name(self::get_event_name($event));
981
+			$event_line_item->set_desc(self::get_event_desc($event));
982
+			$event_line_item->set_OBJ_ID($event->ID());
983
+		}
984
+		self::set_TXN_ID($event_line_item, $transaction);
985
+	}
986
+
987
+
988
+	/**
989
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
+	 * any old taxes are removed
992
+	 *
993
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
+	 * @param bool         $update_txn_status
995
+	 * @return bool
996
+	 * @throws EE_Error
997
+	 * @throws InvalidArgumentException
998
+	 * @throws InvalidDataTypeException
999
+	 * @throws InvalidInterfaceException
1000
+	 * @throws ReflectionException
1001
+	 * @throws RuntimeException
1002
+	 */
1003
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
+	{
1005
+		$total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
+		$existing_global_taxes = $taxes_line_item->tax_descendants();
1008
+		$updates = false;
1009
+		// loop thru taxes
1010
+		$global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
+		foreach ($global_taxes as $order => $taxes) {
1012
+			foreach ($taxes as $tax) {
1013
+				if ($tax instanceof EE_Price) {
1014
+					$found = false;
1015
+					// check if this is already an existing tax
1016
+					foreach ($existing_global_taxes as $existing_global_tax) {
1017
+						if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
+							// maybe update the tax rate in case it has changed
1019
+							if ($existing_global_tax->percent() !== $tax->amount()) {
1020
+								$existing_global_tax->set_percent($tax->amount());
1021
+								$existing_global_tax->save();
1022
+								$updates = true;
1023
+							}
1024
+							$found = true;
1025
+							break;
1026
+						}
1027
+					}
1028
+					if (! $found) {
1029
+						// add a new line item for this global tax
1030
+						$tax_line_item = apply_filters(
1031
+							'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
+							EE_Line_Item::new_instance(
1033
+								[
1034
+									'LIN_name'       => $tax->name(),
1035
+									'LIN_desc'       => $tax->desc(),
1036
+									'LIN_percent'    => $tax->amount(),
1037
+									'LIN_is_taxable' => false,
1038
+									'LIN_order'      => $order,
1039
+									'LIN_total'      => 0,
1040
+									'LIN_type'       => EEM_Line_Item::type_tax,
1041
+									'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
+									'OBJ_ID'         => $tax->ID(),
1043
+								]
1044
+							)
1045
+						);
1046
+						$updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
+					}
1048
+				}
1049
+			}
1050
+		}
1051
+		// only recalculate totals if something changed
1052
+		if ($updates) {
1053
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
+			return true;
1055
+		}
1056
+		return false;
1057
+	}
1058
+
1059
+
1060
+	/**
1061
+	 * Ensures that taxes have been applied to the order, if not applies them.
1062
+	 * Returns the total amount of tax
1063
+	 *
1064
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
+	 * @return float
1066
+	 * @throws EE_Error
1067
+	 * @throws InvalidArgumentException
1068
+	 * @throws InvalidDataTypeException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws ReflectionException
1071
+	 */
1072
+	public static function ensure_taxes_applied($total_line_item)
1073
+	{
1074
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
+		if (! $taxes_subtotal->children()) {
1076
+			self::apply_taxes($total_line_item);
1077
+		}
1078
+		return $taxes_subtotal->total();
1079
+	}
1080
+
1081
+
1082
+	/**
1083
+	 * Deletes ALL children of the passed line item
1084
+	 *
1085
+	 * @param EE_Line_Item $parent_line_item
1086
+	 * @return bool
1087
+	 * @throws EE_Error
1088
+	 * @throws InvalidArgumentException
1089
+	 * @throws InvalidDataTypeException
1090
+	 * @throws InvalidInterfaceException
1091
+	 * @throws ReflectionException
1092
+	 */
1093
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
+	{
1095
+		$deleted = 0;
1096
+		foreach ($parent_line_item->children() as $child_line_item) {
1097
+			if ($child_line_item instanceof EE_Line_Item) {
1098
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
+				if ($child_line_item->ID()) {
1100
+					$child_line_item->delete();
1101
+					unset($child_line_item);
1102
+				} else {
1103
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1104
+				}
1105
+				$deleted++;
1106
+			}
1107
+		}
1108
+		return $deleted;
1109
+	}
1110
+
1111
+
1112
+	/**
1113
+	 * Deletes the line items as indicated by the line item code(s) provided,
1114
+	 * regardless of where they're found in the line item tree. Automatically
1115
+	 * re-calculates the line item totals and updates the related transaction. But
1116
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
+	 * should probably change because of this).
1118
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
+	 *
1121
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
+	 * @param array|bool|string $line_item_codes
1123
+	 * @return int number of items successfully removed
1124
+	 * @throws EE_Error
1125
+	 */
1126
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
+	{
1128
+
1129
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
+			EE_Error::doing_it_wrong(
1131
+				'EEH_Line_Item::delete_items',
1132
+				esc_html__(
1133
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
+					'event_espresso'
1135
+				),
1136
+				'4.6.18'
1137
+			);
1138
+		}
1139
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
+
1141
+		// check if only a single line_item_id was passed
1142
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
+			// place single line_item_id in an array to appear as multiple line_item_ids
1144
+			$line_item_codes = array($line_item_codes);
1145
+		}
1146
+		$removals = 0;
1147
+		// cycle thru line_item_ids
1148
+		foreach ($line_item_codes as $line_item_id) {
1149
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1150
+		}
1151
+
1152
+		if ($removals > 0) {
1153
+			$total_line_item->recalculate_taxes_and_tax_total();
1154
+			return $removals;
1155
+		} else {
1156
+			return false;
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
+	 * tax and updates the total line item accordingly
1164
+	 *
1165
+	 * @param EE_Line_Item $total_line_item
1166
+	 * @param float        $amount
1167
+	 * @param string       $name
1168
+	 * @param string       $description
1169
+	 * @param string       $code
1170
+	 * @param boolean      $add_to_existing_line_item
1171
+	 *                          if true, and a duplicate line item with the same code is found,
1172
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
+	 * @return EE_Line_Item the new tax line item created
1174
+	 * @throws EE_Error
1175
+	 * @throws InvalidArgumentException
1176
+	 * @throws InvalidDataTypeException
1177
+	 * @throws InvalidInterfaceException
1178
+	 * @throws ReflectionException
1179
+	 */
1180
+	public static function set_total_tax_to(
1181
+		EE_Line_Item $total_line_item,
1182
+		$amount,
1183
+		$name = null,
1184
+		$description = null,
1185
+		$code = null,
1186
+		$add_to_existing_line_item = false
1187
+	) {
1188
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
+		$taxable_total = $total_line_item->taxable_total();
1190
+
1191
+		if ($add_to_existing_line_item) {
1192
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1193
+			EEM_Line_Item::instance()->delete(
1194
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
+			);
1196
+		} else {
1197
+			$new_tax = null;
1198
+			$tax_subtotal->delete_children_line_items();
1199
+		}
1200
+		if ($new_tax) {
1201
+			$new_tax->set_total($new_tax->total() + $amount);
1202
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
+		} else {
1204
+			// no existing tax item. Create it
1205
+			$new_tax = EE_Line_Item::new_instance(array(
1206
+				'TXN_ID'      => $total_line_item->TXN_ID(),
1207
+				'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
+				'LIN_desc'    => $description ?: '',
1209
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
+				'LIN_total'   => $amount,
1211
+				'LIN_parent'  => $tax_subtotal->ID(),
1212
+				'LIN_type'    => EEM_Line_Item::type_tax,
1213
+				'LIN_code'    => $code,
1214
+			));
1215
+		}
1216
+
1217
+		$new_tax = apply_filters(
1218
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
+			$new_tax,
1220
+			$total_line_item
1221
+		);
1222
+		$new_tax->save();
1223
+		$tax_subtotal->set_total($new_tax->total());
1224
+		$tax_subtotal->save();
1225
+		$total_line_item->recalculate_total_including_taxes();
1226
+		return $new_tax;
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Makes all the line items which are children of $line_item taxable (or not).
1232
+	 * Does NOT save the line items
1233
+	 *
1234
+	 * @param EE_Line_Item $line_item
1235
+	 * @param boolean      $taxable
1236
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
+	 * @throws EE_Error
1239
+	 */
1240
+	public static function set_line_items_taxable(
1241
+		EE_Line_Item $line_item,
1242
+		$taxable = true,
1243
+		$code_substring_for_whitelist = null
1244
+	) {
1245
+		$whitelisted = false;
1246
+		if ($code_substring_for_whitelist !== null) {
1247
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
+		}
1249
+		if (! $whitelisted && $line_item->is_line_item()) {
1250
+			$line_item->set_is_taxable($taxable);
1251
+		}
1252
+		foreach ($line_item->children() as $child_line_item) {
1253
+			EEH_Line_Item::set_line_items_taxable(
1254
+				$child_line_item,
1255
+				$taxable,
1256
+				$code_substring_for_whitelist
1257
+			);
1258
+		}
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Gets all descendants that are event subtotals
1264
+	 *
1265
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
+	 * @return EE_Line_Item[]
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
+	{
1272
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
+	}
1274
+
1275
+
1276
+	/**
1277
+	 * Gets all descendants subtotals that match the supplied object type
1278
+	 *
1279
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
+	 * @param string       $obj_type
1282
+	 * @return EE_Line_Item[]
1283
+	 * @throws EE_Error
1284
+	 */
1285
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
+	{
1287
+		return self::_get_descendants_by_type_and_object_type(
1288
+			$parent_line_item,
1289
+			EEM_Line_Item::type_sub_total,
1290
+			$obj_type
1291
+		);
1292
+	}
1293
+
1294
+
1295
+	/**
1296
+	 * Gets all descendants that are tickets
1297
+	 *
1298
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
+	 * @return EE_Line_Item[]
1301
+	 * @throws EE_Error
1302
+	 */
1303
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
+	{
1305
+		return self::get_line_items_of_object_type(
1306
+			$parent_line_item,
1307
+			EEM_Line_Item::OBJ_TYPE_TICKET
1308
+		);
1309
+	}
1310
+
1311
+
1312
+	/**
1313
+	 * Gets all descendants subtotals that match the supplied object type
1314
+	 *
1315
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
+	 * @param string       $obj_type
1318
+	 * @return EE_Line_Item[]
1319
+	 * @throws EE_Error
1320
+	 */
1321
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
+	{
1323
+		return self::_get_descendants_by_type_and_object_type(
1324
+			$parent_line_item,
1325
+			EEM_Line_Item::type_line_item,
1326
+			$obj_type
1327
+		);
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
+	 *
1334
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1335
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
+	 * @return EE_Line_Item[]
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
+	{
1341
+		return EEH_Line_Item::get_descendants_of_type(
1342
+			$parent_line_item,
1343
+			EEM_Line_Item::type_tax
1344
+		);
1345
+	}
1346
+
1347
+
1348
+	/**
1349
+	 * Gets all the real items purchased which are children of this item
1350
+	 *
1351
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1352
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
+	 * @return EE_Line_Item[]
1354
+	 * @throws EE_Error
1355
+	 */
1356
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
+	{
1358
+		return EEH_Line_Item::get_descendants_of_type(
1359
+			$parent_line_item,
1360
+			EEM_Line_Item::type_line_item
1361
+		);
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * Gets all descendants of supplied line item that match the supplied line item type
1367
+	 *
1368
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
+	 * @return EE_Line_Item[]
1372
+	 * @throws EE_Error
1373
+	 */
1374
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
+	{
1376
+		return self::_get_descendants_by_type_and_object_type(
1377
+			$parent_line_item,
1378
+			$line_item_type,
1379
+			null
1380
+		);
1381
+	}
1382
+
1383
+
1384
+	/**
1385
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
+	 * as well
1387
+	 *
1388
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
+	 * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
+	 *                                        searching
1392
+	 * @return EE_Line_Item[]
1393
+	 * @throws EE_Error
1394
+	 */
1395
+	protected static function _get_descendants_by_type_and_object_type(
1396
+		EE_Line_Item $parent_line_item,
1397
+		$line_item_type,
1398
+		$obj_type = null
1399
+	) {
1400
+		$objects = array();
1401
+		foreach ($parent_line_item->children() as $child_line_item) {
1402
+			if ($child_line_item instanceof EE_Line_Item) {
1403
+				if (
1404
+					$child_line_item->type() === $line_item_type
1405
+					&& (
1406
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
+					)
1408
+				) {
1409
+					$objects[] = $child_line_item;
1410
+				} else {
1411
+					// go-through-all-its children looking for more matches
1412
+					$objects = array_merge(
1413
+						$objects,
1414
+						self::_get_descendants_by_type_and_object_type(
1415
+							$child_line_item,
1416
+							$line_item_type,
1417
+							$obj_type
1418
+						)
1419
+					);
1420
+				}
1421
+			}
1422
+		}
1423
+		return $objects;
1424
+	}
1425
+
1426
+
1427
+	/**
1428
+	 * Gets all descendants subtotals that match the supplied object type
1429
+	 *
1430
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
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
+	public static function get_line_items_by_object_type_and_IDs(
1438
+		EE_Line_Item $parent_line_item,
1439
+		$OBJ_type = '',
1440
+		$OBJ_IDs = array()
1441
+	) {
1442
+		return self::_get_descendants_by_object_type_and_object_ID(
1443
+			$parent_line_item,
1444
+			$OBJ_type,
1445
+			$OBJ_IDs
1446
+		);
1447
+	}
1448
+
1449
+
1450
+	/**
1451
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
+	 * as well
1453
+	 *
1454
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
+	 * @param string       $OBJ_type         object type (like Event)
1456
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1457
+	 * @return EE_Line_Item[]
1458
+	 * @throws EE_Error
1459
+	 */
1460
+	protected static function _get_descendants_by_object_type_and_object_ID(
1461
+		EE_Line_Item $parent_line_item,
1462
+		$OBJ_type,
1463
+		$OBJ_IDs
1464
+	) {
1465
+		$objects = array();
1466
+		foreach ($parent_line_item->children() as $child_line_item) {
1467
+			if ($child_line_item instanceof EE_Line_Item) {
1468
+				if (
1469
+					$child_line_item->OBJ_type() === $OBJ_type
1470
+					&& is_array($OBJ_IDs)
1471
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
+				) {
1473
+					$objects[] = $child_line_item;
1474
+				} else {
1475
+					// go-through-all-its children looking for more matches
1476
+					$objects = array_merge(
1477
+						$objects,
1478
+						self::_get_descendants_by_object_type_and_object_ID(
1479
+							$child_line_item,
1480
+							$OBJ_type,
1481
+							$OBJ_IDs
1482
+						)
1483
+					);
1484
+				}
1485
+			}
1486
+		}
1487
+		return $objects;
1488
+	}
1489
+
1490
+
1491
+	/**
1492
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1493
+	 * the specified type and returns it, else NULL
1494
+	 *
1495
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1498
+	 * @return EE_Line_Item
1499
+	 * @throws EE_Error
1500
+	 * @throws InvalidArgumentException
1501
+	 * @throws InvalidDataTypeException
1502
+	 * @throws InvalidInterfaceException
1503
+	 * @throws ReflectionException
1504
+	 */
1505
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
+	{
1507
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Uses a breadth-first-search in order to find the nearest descendant
1513
+	 * having the specified LIN_code and returns it, else NULL
1514
+	 *
1515
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
+	 * @param string       $code             any value used for LIN_code
1518
+	 * @return EE_Line_Item
1519
+	 * @throws EE_Error
1520
+	 * @throws InvalidArgumentException
1521
+	 * @throws InvalidDataTypeException
1522
+	 * @throws InvalidInterfaceException
1523
+	 * @throws ReflectionException
1524
+	 */
1525
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
+	{
1527
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
+	}
1529
+
1530
+
1531
+	/**
1532
+	 * Uses a breadth-first-search in order to find the nearest descendant
1533
+	 * having the specified LIN_code and returns it, else NULL
1534
+	 *
1535
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
+	 * @param string       $search_field     name of EE_Line_Item property
1537
+	 * @param string       $value            any value stored in $search_field
1538
+	 * @return EE_Line_Item
1539
+	 * @throws EE_Error
1540
+	 * @throws InvalidArgumentException
1541
+	 * @throws InvalidDataTypeException
1542
+	 * @throws InvalidInterfaceException
1543
+	 * @throws ReflectionException
1544
+	 */
1545
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
+	{
1547
+		foreach ($parent_line_item->children() as $child) {
1548
+			if ($child->get($search_field) == $value) {
1549
+				return $child;
1550
+			}
1551
+		}
1552
+		foreach ($parent_line_item->children() as $child) {
1553
+			$descendant_found = self::_get_nearest_descendant(
1554
+				$child,
1555
+				$search_field,
1556
+				$value
1557
+			);
1558
+			if ($descendant_found) {
1559
+				return $descendant_found;
1560
+			}
1561
+		}
1562
+		return null;
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
+	 * else recursively walks up the line item tree until a parent of type total is found,
1569
+	 *
1570
+	 * @param EE_Line_Item $line_item
1571
+	 * @return EE_Line_Item
1572
+	 * @throws EE_Error
1573
+	 * @throws ReflectionException
1574
+	 */
1575
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
+	{
1577
+		if ($line_item->is_total()) {
1578
+			return $line_item;
1579
+		}
1580
+		if ($line_item->TXN_ID()) {
1581
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1582
+			if ($total_line_item instanceof EE_Line_Item) {
1583
+				return $total_line_item;
1584
+			}
1585
+		} else {
1586
+			$line_item_parent = $line_item->parent();
1587
+			if ($line_item_parent instanceof EE_Line_Item) {
1588
+				if ($line_item_parent->is_total()) {
1589
+					return $line_item_parent;
1590
+				}
1591
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
+			}
1593
+		}
1594
+		throw new EE_Error(
1595
+			sprintf(
1596
+				esc_html__(
1597
+					'A valid grand total for line item %1$d was not found.',
1598
+					'event_espresso'
1599
+				),
1600
+				$line_item->ID()
1601
+			)
1602
+		);
1603
+	}
1604
+
1605
+
1606
+	/**
1607
+	 * Prints out a representation of the line item tree
1608
+	 *
1609
+	 * @param EE_Line_Item $line_item
1610
+	 * @param int          $indentation
1611
+	 * @return void
1612
+	 * @throws EE_Error
1613
+	 */
1614
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
+	{
1616
+		$new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
+		echo $new_line;
1618
+		if (! $indentation) {
1619
+			echo $new_line;
1620
+		}
1621
+		echo str_repeat('. ', $indentation);
1622
+		$breakdown = '';
1623
+		if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
+			if ($line_item->is_percent()) {
1625
+				$breakdown = "{$line_item->percent()}%";
1626
+			} else {
1627
+				$breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
+			}
1629
+		}
1630
+		echo $line_item->name();
1631
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
+		echo "\${$line_item->total()}";
1633
+		if ($breakdown) {
1634
+			echo " ( {$breakdown} )";
1635
+		}
1636
+		if ($line_item->is_taxable()) {
1637
+			echo '  * taxable';
1638
+		}
1639
+		if ($line_item->children()) {
1640
+			foreach ($line_item->children() as $child) {
1641
+				self::visualize($child, $indentation + 1);
1642
+			}
1643
+		}
1644
+		if (! $indentation) {
1645
+			echo $new_line . $new_line;
1646
+		}
1647
+	}
1648
+
1649
+
1650
+	/**
1651
+	 * Calculates the registration's final price, taking into account that they
1652
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
+	 * and receive a portion of any transaction-wide discounts.
1654
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
+	 * and brent's final price should be $5.50.
1658
+	 * In order to do this, we basically need to traverse the line item tree calculating
1659
+	 * the running totals (just as if we were recalculating the total), but when we identify
1660
+	 * regular line items, we need to keep track of their share of the grand total.
1661
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
+	 * when there are non-taxable items; otherwise they would be the same)
1664
+	 *
1665
+	 * @param EE_Line_Item $line_item
1666
+	 * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
+	 *                                                  can be included in price calculations at this moment
1668
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
+	 *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
+	 *                                                  the ticket IDs.
1671
+	 *                                                  Eg array(
1672
+	 *                                                      12 => 4.3
1673
+	 *                                                      23 => 8.0
1674
+	 *                                                      'total' => 16.6,
1675
+	 *                                                      'taxable' => array(
1676
+	 *                                                          12 => 10,
1677
+	 *                                                          23 => 4
1678
+	 *                                                      ).
1679
+	 *                                                  So to find which registrations have which final price, we need
1680
+	 *                                                  to find which line item is theirs, which can be done with
1681
+	 *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
+	 *                                                  $registration );`
1683
+	 * @throws EE_Error
1684
+	 * @throws InvalidArgumentException
1685
+	 * @throws InvalidDataTypeException
1686
+	 * @throws InvalidInterfaceException
1687
+	 * @throws ReflectionException
1688
+	 */
1689
+	public static function calculate_reg_final_prices_per_line_item(
1690
+		EE_Line_Item $line_item,
1691
+		$billable_ticket_quantities = array()
1692
+	) {
1693
+		$running_totals = [
1694
+			'total'   => 0,
1695
+			'taxable' => ['total' => 0]
1696
+		];
1697
+		foreach ($line_item->children() as $child_line_item) {
1698
+			switch ($child_line_item->type()) {
1699
+				case EEM_Line_Item::type_sub_total:
1700
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
+						$child_line_item,
1702
+						$billable_ticket_quantities
1703
+					);
1704
+					// combine arrays but preserve numeric keys
1705
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1707
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
+					break;
1709
+
1710
+				case EEM_Line_Item::type_tax_sub_total:
1711
+					// find how much the taxes percentage is
1712
+					if ($child_line_item->percent() !== 0) {
1713
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1714
+					} else {
1715
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
+					}
1717
+					// and apply to all the taxable totals, and add to the pretax totals
1718
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1719
+						// "total" and "taxable" array key is an exception
1720
+						if ($line_item_id === 'taxable') {
1721
+							continue;
1722
+						}
1723
+						$taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
+						$running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
+					}
1726
+					break;
1727
+
1728
+				case EEM_Line_Item::type_line_item:
1729
+					// ticket line items or ????
1730
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
+						// kk it's a ticket
1732
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1733
+							// huh? that shouldn't happen.
1734
+							$running_totals['total'] += $child_line_item->total();
1735
+						} else {
1736
+							// its not in our running totals yet. great.
1737
+							if ($child_line_item->is_taxable()) {
1738
+								$taxable_amount = $child_line_item->unit_price();
1739
+							} else {
1740
+								$taxable_amount = 0;
1741
+							}
1742
+							// are we only calculating totals for some tickets?
1743
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
+								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
+								$running_totals[ $child_line_item->ID() ] = $quantity
1746
+									? $child_line_item->unit_price()
1747
+									: 0;
1748
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
+									? $taxable_amount
1750
+									: 0;
1751
+							} else {
1752
+								$quantity = $child_line_item->quantity();
1753
+								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
+							}
1756
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
+						}
1759
+					} else {
1760
+						// it's some other type of item added to the cart
1761
+						// it should affect the running totals
1762
+						// basically we want to convert it into a PERCENT modifier. Because
1763
+						// more clearly affect all registration's final price equally
1764
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1765
+							? ($child_line_item->total() / $running_totals['total']) + 1
1766
+							: 1;
1767
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1768
+							// the "taxable" array key is an exception
1769
+							if ($line_item_id === 'taxable') {
1770
+								continue;
1771
+							}
1772
+							// update the running totals
1773
+							// yes this actually even works for the running grand total!
1774
+							$running_totals[ $line_item_id ] =
1775
+								$line_items_percent_of_running_total * $this_running_total;
1776
+
1777
+							if ($child_line_item->is_taxable()) {
1778
+								$running_totals['taxable'][ $line_item_id ] =
1779
+									$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
+							}
1781
+						}
1782
+					}
1783
+					break;
1784
+			}
1785
+		}
1786
+		return $running_totals;
1787
+	}
1788
+
1789
+
1790
+	/**
1791
+	 * @param EE_Line_Item $total_line_item
1792
+	 * @param EE_Line_Item $ticket_line_item
1793
+	 * @return float | null
1794
+	 * @throws EE_Error
1795
+	 * @throws InvalidArgumentException
1796
+	 * @throws InvalidDataTypeException
1797
+	 * @throws InvalidInterfaceException
1798
+	 * @throws OutOfRangeException
1799
+	 * @throws ReflectionException
1800
+	 */
1801
+	public static function calculate_final_price_for_ticket_line_item(
1802
+		EE_Line_Item $total_line_item,
1803
+		EE_Line_Item $ticket_line_item
1804
+	) {
1805
+		static $final_prices_per_ticket_line_item = array();
1806
+		if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
+				$total_line_item
1809
+			);
1810
+		}
1811
+		// ok now find this new registration's final price
1812
+		if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
+			return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
+		}
1815
+		$message = sprintf(
1816
+			esc_html__(
1817
+				'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
+				'event_espresso'
1819
+			),
1820
+			$ticket_line_item->ID(),
1821
+			$total_line_item->ID()
1822
+		);
1823
+		if (WP_DEBUG) {
1824
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
+			throw new OutOfRangeException($message);
1826
+		}
1827
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
+		return null;
1829
+	}
1830
+
1831
+
1832
+	/**
1833
+	 * Creates a duplicate of the line item tree, except only includes billable items
1834
+	 * and the portion of line items attributed to billable things
1835
+	 *
1836
+	 * @param EE_Line_Item      $line_item
1837
+	 * @param EE_Registration[] $registrations
1838
+	 * @return EE_Line_Item
1839
+	 * @throws EE_Error
1840
+	 * @throws InvalidArgumentException
1841
+	 * @throws InvalidDataTypeException
1842
+	 * @throws InvalidInterfaceException
1843
+	 * @throws ReflectionException
1844
+	 */
1845
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
+	{
1847
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
+		foreach ($line_item->children() as $child_li) {
1849
+			$copy_li->add_child_line_item(
1850
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
+			);
1852
+		}
1853
+		// if this is the grand total line item, make sure the totals all add up
1854
+		// (we could have duplicated this logic AS we copied the line items, but
1855
+		// it seems DRYer this way)
1856
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
+			$copy_li->recalculate_total_including_taxes();
1858
+		}
1859
+		return $copy_li;
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * Creates a new, unsaved line item from $line_item that factors in the
1865
+	 * number of billable registrations on $registrations.
1866
+	 *
1867
+	 * @param EE_Line_Item      $line_item
1868
+	 * @param EE_Registration[] $registrations
1869
+	 * @return EE_Line_Item
1870
+	 * @throws EE_Error
1871
+	 * @throws InvalidArgumentException
1872
+	 * @throws InvalidDataTypeException
1873
+	 * @throws InvalidInterfaceException
1874
+	 * @throws ReflectionException
1875
+	 */
1876
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
+	{
1878
+		$new_li_fields = $line_item->model_field_array();
1879
+		if (
1880
+			$line_item->type() === EEM_Line_Item::type_line_item &&
1881
+			$line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
+		) {
1883
+			$count = 0;
1884
+			foreach ($registrations as $registration) {
1885
+				if (
1886
+					$line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
+					in_array(
1888
+						$registration->status_ID(),
1889
+						EEM_Registration::reg_statuses_that_allow_payment(),
1890
+						true
1891
+					)
1892
+				) {
1893
+					$count++;
1894
+				}
1895
+			}
1896
+			$new_li_fields['LIN_quantity'] = $count;
1897
+		}
1898
+		// don't set the total. We'll leave that up to the code that calculates it
1899
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
+		return EE_Line_Item::new_instance($new_li_fields);
1901
+	}
1902
+
1903
+
1904
+	/**
1905
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1906
+	 * are removed, and line items with a quantity of 0
1907
+	 *
1908
+	 * @param EE_Line_Item $line_item |null
1909
+	 * @return EE_Line_Item|null
1910
+	 * @throws EE_Error
1911
+	 * @throws InvalidArgumentException
1912
+	 * @throws InvalidDataTypeException
1913
+	 * @throws InvalidInterfaceException
1914
+	 * @throws ReflectionException
1915
+	 */
1916
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1917
+	{
1918
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
+		if ($copied_li === null) {
1920
+			return null;
1921
+		}
1922
+		// if this is an event subtotal, we want to only include it if it
1923
+		// has a non-zero total and at least one ticket line item child
1924
+		$ticket_children = 0;
1925
+		foreach ($line_item->children() as $child_li) {
1926
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
+			if ($child_li_copy !== null) {
1928
+				$copied_li->add_child_line_item($child_li_copy);
1929
+				if (
1930
+					$child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
+					$child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
+				) {
1933
+					$ticket_children++;
1934
+				}
1935
+			}
1936
+		}
1937
+		// if this is an event subtotal with NO ticket children
1938
+		// we basically want to ignore it
1939
+		if (
1940
+			$ticket_children === 0
1941
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1942
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
+			&& $line_item->total() === 0
1944
+		) {
1945
+			return null;
1946
+		}
1947
+		return $copied_li;
1948
+	}
1949
+
1950
+
1951
+	/**
1952
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1953
+	 * with a total of 0, or a subtotal of 0, returns null instead
1954
+	 *
1955
+	 * @param EE_Line_Item $line_item
1956
+	 * @return EE_Line_Item
1957
+	 * @throws EE_Error
1958
+	 * @throws InvalidArgumentException
1959
+	 * @throws InvalidDataTypeException
1960
+	 * @throws InvalidInterfaceException
1961
+	 * @throws ReflectionException
1962
+	 */
1963
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1964
+	{
1965
+		if (
1966
+			$line_item->type() === EEM_Line_Item::type_line_item
1967
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
+			&& $line_item->quantity() === 0
1969
+		) {
1970
+			return null;
1971
+		}
1972
+		$new_li_fields = $line_item->model_field_array();
1973
+		// don't set the total. We'll leave that up to the code that calculates it
1974
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
+		return EE_Line_Item::new_instance($new_li_fields);
1976
+	}
1977
+
1978
+
1979
+	/**
1980
+	 * Cycles through all of the ticket line items for the supplied total line item
1981
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
+	 *
1983
+	 * @param EE_Line_Item $total_line_item
1984
+	 * @since 4.9.79.p
1985
+	 * @throws EE_Error
1986
+	 * @throws InvalidArgumentException
1987
+	 * @throws InvalidDataTypeException
1988
+	 * @throws InvalidInterfaceException
1989
+	 * @throws ReflectionException
1990
+	 */
1991
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
+	{
1993
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
+		foreach ($ticket_line_items as $ticket_line_item) {
1995
+			if (
1996
+				$ticket_line_item instanceof EE_Line_Item
1997
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
+			) {
1999
+				$ticket = $ticket_line_item->ticket();
2000
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
+					$ticket_line_item->set_is_taxable($ticket->taxable());
2002
+					$ticket_line_item->save();
2003
+				}
2004
+			}
2005
+		}
2006
+	}
2007
+
2008
+
2009
+	/**
2010
+	 * @return EE_Line_Item[]
2011
+	 * @throws EE_Error
2012
+	 * @throws ReflectionException
2013
+	 * @since   $VID:$
2014
+	 */
2015
+	private static function getGlobalTaxes(): array
2016
+	{
2017
+		if (EEH_Line_Item::$global_taxes === null) {
2018
+
2019
+			/** @type EEM_Price $EEM_Price */
2020
+			$EEM_Price = EE_Registry::instance()->load_model('Price');
2021
+			// get array of taxes via Price Model
2022
+			EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
+			ksort(EEH_Line_Item::$global_taxes);
2024
+		}
2025
+		return EEH_Line_Item::$global_taxes;
2026
+	}
2027
+
2028
+
2029
+
2030
+	/**************************************** @DEPRECATED METHODS *************************************** */
2031
+	/**
2032
+	 * @deprecated
2033
+	 * @param EE_Line_Item $total_line_item
2034
+	 * @return EE_Line_Item
2035
+	 * @throws EE_Error
2036
+	 * @throws InvalidArgumentException
2037
+	 * @throws InvalidDataTypeException
2038
+	 * @throws InvalidInterfaceException
2039
+	 * @throws ReflectionException
2040
+	 */
2041
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
+	{
2043
+		EE_Error::doing_it_wrong(
2044
+			'EEH_Line_Item::get_items_subtotal()',
2045
+			sprintf(
2046
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
+				'EEH_Line_Item::get_pre_tax_subtotal()'
2048
+			),
2049
+			'4.6.0'
2050
+		);
2051
+		return self::get_pre_tax_subtotal($total_line_item);
2052
+	}
2053
+
2054
+
2055
+	/**
2056
+	 * @deprecated
2057
+	 * @param EE_Transaction $transaction
2058
+	 * @return EE_Line_Item
2059
+	 * @throws EE_Error
2060
+	 * @throws InvalidArgumentException
2061
+	 * @throws InvalidDataTypeException
2062
+	 * @throws InvalidInterfaceException
2063
+	 * @throws ReflectionException
2064
+	 */
2065
+	public static function create_default_total_line_item($transaction = null)
2066
+	{
2067
+		EE_Error::doing_it_wrong(
2068
+			'EEH_Line_Item::create_default_total_line_item()',
2069
+			sprintf(
2070
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
+				'EEH_Line_Item::create_total_line_item()'
2072
+			),
2073
+			'4.6.0'
2074
+		);
2075
+		return self::create_total_line_item($transaction);
2076
+	}
2077
+
2078
+
2079
+	/**
2080
+	 * @deprecated
2081
+	 * @param EE_Line_Item   $total_line_item
2082
+	 * @param EE_Transaction $transaction
2083
+	 * @return EE_Line_Item
2084
+	 * @throws EE_Error
2085
+	 * @throws InvalidArgumentException
2086
+	 * @throws InvalidDataTypeException
2087
+	 * @throws InvalidInterfaceException
2088
+	 * @throws ReflectionException
2089
+	 */
2090
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
+	{
2092
+		EE_Error::doing_it_wrong(
2093
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2094
+			sprintf(
2095
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2097
+			),
2098
+			'4.6.0'
2099
+		);
2100
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
+	}
2102
+
2103
+
2104
+	/**
2105
+	 * @deprecated
2106
+	 * @param EE_Line_Item   $total_line_item
2107
+	 * @param EE_Transaction $transaction
2108
+	 * @return EE_Line_Item
2109
+	 * @throws EE_Error
2110
+	 * @throws InvalidArgumentException
2111
+	 * @throws InvalidDataTypeException
2112
+	 * @throws InvalidInterfaceException
2113
+	 * @throws ReflectionException
2114
+	 */
2115
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
+	{
2117
+		EE_Error::doing_it_wrong(
2118
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2119
+			sprintf(
2120
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
+				'EEH_Line_Item::create_taxes_subtotal()'
2122
+			),
2123
+			'4.6.0'
2124
+		);
2125
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2126
+	}
2127
+
2128
+
2129
+	/**
2130
+	 * @deprecated
2131
+	 * @param EE_Line_Item   $total_line_item
2132
+	 * @param EE_Transaction $transaction
2133
+	 * @return EE_Line_Item
2134
+	 * @throws EE_Error
2135
+	 * @throws InvalidArgumentException
2136
+	 * @throws InvalidDataTypeException
2137
+	 * @throws InvalidInterfaceException
2138
+	 * @throws ReflectionException
2139
+	 */
2140
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
+	{
2142
+		EE_Error::doing_it_wrong(
2143
+			'EEH_Line_Item::create_default_event_subtotal()',
2144
+			sprintf(
2145
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
+				'EEH_Line_Item::create_event_subtotal()'
2147
+			),
2148
+			'4.6.0'
2149
+		);
2150
+		return self::create_event_subtotal($total_line_item, $transaction);
2151
+	}
2152 2152
 }
Please login to merge, or discard this patch.
Spacing   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -75,12 +75,12 @@  discard block
 block discarded – undo
75 75
                 'LIN_code'       => $code,
76 76
             ]
77 77
         );
78
-        $line_item      = apply_filters(
78
+        $line_item = apply_filters(
79 79
             'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80 80
             $line_item,
81 81
             $parent_line_item
82 82
         );
83
-        $added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
83
+        $added = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84 84
         return $return_item ? $line_item : $added;
85 85
     }
86 86
 
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
             'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134 134
             $line_item
135 135
         );
136
-        $added     = $parent_line_item->add_child_line_item($line_item, false);
136
+        $added = $parent_line_item->add_child_line_item($line_item, false);
137 137
         return $return_item ? $line_item : $added;
138 138
     }
139 139
 
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
      */
161 161
     public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
162 162
     {
163
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
163
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164 164
             throw new EE_Error(
165 165
                 sprintf(
166 166
                     esc_html__(
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
         // either increment the qty for an existing ticket
176 176
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177 177
         // or add a new one
178
-        if (! $line_item instanceof EE_Line_Item) {
178
+        if ( ! $line_item instanceof EE_Line_Item) {
179 179
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180 180
         }
181 181
         $total_line_item->recalculate_total_including_taxes();
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
      */
238 238
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239 239
     {
240
-        if (! $line_item->is_percent()) {
240
+        if ( ! $line_item->is_percent()) {
241 241
             $qty += $line_item->quantity();
242 242
             $line_item->set_quantity($qty);
243 243
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
      */
267 267
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268 268
     {
269
-        if (! $line_item->is_percent()) {
269
+        if ( ! $line_item->is_percent()) {
270 270
             $qty = $line_item->quantity() - $qty;
271 271
             $qty = max($qty, 0);
272 272
             $line_item->set_quantity($qty);
@@ -295,7 +295,7 @@  discard block
 block discarded – undo
295 295
      */
296 296
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297 297
     {
298
-        if (! $line_item->is_percent()) {
298
+        if ( ! $line_item->is_percent()) {
299 299
             $line_item->set_quantity($new_quantity);
300 300
             $line_item->set_total($line_item->unit_price() * $new_quantity);
301 301
             $line_item->save();
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
         // add $ticket to cart
337 337
         $line_item = EE_Line_Item::new_instance(array(
338 338
             'LIN_name'       => $ticket->name(),
339
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
339
+            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
340 340
             'LIN_unit_price' => $ticket->price(),
341 341
             'LIN_quantity'   => $qty,
342 342
             'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
             'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351 351
             $line_item
352 352
         );
353
-        if (!$line_item instanceof EE_Line_Item) {
353
+        if ( ! $line_item instanceof EE_Line_Item) {
354 354
             throw new DomainException(
355 355
                 esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356 356
             );
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
                         'event_espresso'
503 503
                     ),
504 504
                     $ticket_line_item->name(),
505
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
505
+                    current_time(get_option('date_format').' '.get_option('time_format'))
506 506
                 ),
507 507
                 'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508 508
                 'LIN_quantity'   => $qty,
@@ -565,7 +565,7 @@  discard block
 block discarded – undo
565 565
         );
566 566
         $cancellation_line_item = reset($cancellation_line_item);
567 567
         // verify that this ticket was indeed previously cancelled
568
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
568
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
569 569
             return false;
570 570
         }
571 571
         if ($cancellation_line_item->quantity() > $qty) {
@@ -771,7 +771,7 @@  discard block
 block discarded – undo
771 771
             'LIN_code'  => 'taxes',
772 772
             'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773 773
             'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
-            'LIN_order' => 1000,// this should always come last
774
+            'LIN_order' => 1000, // this should always come last
775 775
         ));
776 776
         $tax_line_item = apply_filters(
777 777
             'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
@@ -827,7 +827,7 @@  discard block
 block discarded – undo
827 827
      */
828 828
     public static function get_event_code($event)
829 829
     {
830
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
830
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
831 831
     }
832 832
 
833 833
 
@@ -876,7 +876,7 @@  discard block
 block discarded – undo
876 876
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877 877
     {
878 878
         $first_datetime = $ticket->first_datetime();
879
-        if (! $first_datetime instanceof EE_Datetime) {
879
+        if ( ! $first_datetime instanceof EE_Datetime) {
880 880
             throw new EE_Error(
881 881
                 sprintf(
882 882
                     __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
@@ -885,7 +885,7 @@  discard block
 block discarded – undo
885 885
             );
886 886
         }
887 887
         $event = $first_datetime->event();
888
-        if (! $event instanceof EE_Event) {
888
+        if ( ! $event instanceof EE_Event) {
889 889
             throw new EE_Error(
890 890
                 sprintf(
891 891
                     esc_html__(
@@ -897,7 +897,7 @@  discard block
 block discarded – undo
897 897
             );
898 898
         }
899 899
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
-        if (! $events_sub_total instanceof EE_Line_Item) {
900
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
901 901
             throw new EE_Error(
902 902
                 sprintf(
903 903
                     esc_html__(
@@ -933,7 +933,7 @@  discard block
 block discarded – undo
933 933
         $found = false;
934 934
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935 935
             // default event subtotal, we should only ever find this the first time this method is called
936
-            if (! $event_line_item->OBJ_ID()) {
936
+            if ( ! $event_line_item->OBJ_ID()) {
937 937
                 // let's use this! but first... set the event details
938 938
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939 939
                 $found = true;
@@ -945,7 +945,7 @@  discard block
 block discarded – undo
945 945
                 break;
946 946
             }
947 947
         }
948
-        if (! $found) {
948
+        if ( ! $found) {
949 949
             // there is no event sub-total yet, so add it
950 950
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951 951
             // create a new "event" subtotal below that
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
                             break;
1026 1026
                         }
1027 1027
                     }
1028
-                    if (! $found) {
1028
+                    if ( ! $found) {
1029 1029
                         // add a new line item for this global tax
1030 1030
                         $tax_line_item = apply_filters(
1031 1031
                             'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
@@ -1072,7 +1072,7 @@  discard block
 block discarded – undo
1072 1072
     public static function ensure_taxes_applied($total_line_item)
1073 1073
     {
1074 1074
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
-        if (! $taxes_subtotal->children()) {
1075
+        if ( ! $taxes_subtotal->children()) {
1076 1076
             self::apply_taxes($total_line_item);
1077 1077
         }
1078 1078
         return $taxes_subtotal->total();
@@ -1139,7 +1139,7 @@  discard block
 block discarded – undo
1139 1139
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140 1140
 
1141 1141
         // check if only a single line_item_id was passed
1142
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1142
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143 1143
             // place single line_item_id in an array to appear as multiple line_item_ids
1144 1144
             $line_item_codes = array($line_item_codes);
1145 1145
         }
@@ -1246,7 +1246,7 @@  discard block
 block discarded – undo
1246 1246
         if ($code_substring_for_whitelist !== null) {
1247 1247
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248 1248
         }
1249
-        if (! $whitelisted && $line_item->is_line_item()) {
1249
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1250 1250
             $line_item->set_is_taxable($taxable);
1251 1251
         }
1252 1252
         foreach ($line_item->children() as $child_line_item) {
@@ -1615,7 +1615,7 @@  discard block
 block discarded – undo
1615 1615
     {
1616 1616
         $new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617 1617
         echo $new_line;
1618
-        if (! $indentation) {
1618
+        if ( ! $indentation) {
1619 1619
             echo $new_line;
1620 1620
         }
1621 1621
         echo str_repeat('. ', $indentation);
@@ -1641,8 +1641,8 @@  discard block
 block discarded – undo
1641 1641
                 self::visualize($child, $indentation + 1);
1642 1642
             }
1643 1643
         }
1644
-        if (! $indentation) {
1645
-            echo $new_line . $new_line;
1644
+        if ( ! $indentation) {
1645
+            echo $new_line.$new_line;
1646 1646
         }
1647 1647
     }
1648 1648
 
@@ -1720,8 +1720,8 @@  discard block
 block discarded – undo
1720 1720
                         if ($line_item_id === 'taxable') {
1721 1721
                             continue;
1722 1722
                         }
1723
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1723
+                        $taxable_total = $running_totals['taxable'][$line_item_id];
1724
+                        $running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1725 1725
                     }
1726 1726
                     break;
1727 1727
 
@@ -1729,7 +1729,7 @@  discard block
 block discarded – undo
1729 1729
                     // ticket line items or ????
1730 1730
                     if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731 1731
                         // kk it's a ticket
1732
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1732
+                        if (isset($running_totals[$child_line_item->ID()])) {
1733 1733
                             // huh? that shouldn't happen.
1734 1734
                             $running_totals['total'] += $child_line_item->total();
1735 1735
                         } else {
@@ -1740,18 +1740,18 @@  discard block
 block discarded – undo
1740 1740
                                 $taxable_amount = 0;
1741 1741
                             }
1742 1742
                             // are we only calculating totals for some tickets?
1743
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1743
+                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1744
+                                $quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1745
+                                $running_totals[$child_line_item->ID()] = $quantity
1746 1746
                                     ? $child_line_item->unit_price()
1747 1747
                                     : 0;
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1748
+                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1749 1749
                                     ? $taxable_amount
1750 1750
                                     : 0;
1751 1751
                             } else {
1752 1752
                                 $quantity = $child_line_item->quantity();
1753
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1753
+                                $running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1754
+                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1755 1755
                             }
1756 1756
                             $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757 1757
                             $running_totals['total'] += $child_line_item->unit_price() * $quantity;
@@ -1771,12 +1771,12 @@  discard block
 block discarded – undo
1771 1771
                             }
1772 1772
                             // update the running totals
1773 1773
                             // yes this actually even works for the running grand total!
1774
-                            $running_totals[ $line_item_id ] =
1774
+                            $running_totals[$line_item_id] =
1775 1775
                                 $line_items_percent_of_running_total * $this_running_total;
1776 1776
 
1777 1777
                             if ($child_line_item->is_taxable()) {
1778
-                                $running_totals['taxable'][ $line_item_id ] =
1779
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1778
+                                $running_totals['taxable'][$line_item_id] =
1779
+                                    $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1780 1780
                             }
1781 1781
                         }
1782 1782
                     }
@@ -1803,14 +1803,14 @@  discard block
 block discarded – undo
1803 1803
         EE_Line_Item $ticket_line_item
1804 1804
     ) {
1805 1805
         static $final_prices_per_ticket_line_item = array();
1806
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1806
+        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[$total_line_item->ID()])) {
1807
+            $final_prices_per_ticket_line_item[$total_line_item->ID()] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808 1808
                 $total_line_item
1809 1809
             );
1810 1810
         }
1811 1811
         // ok now find this new registration's final price
1812
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1812
+        if (isset($final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()])) {
1813
+            return $final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()];
1814 1814
         }
1815 1815
         $message = sprintf(
1816 1816
             esc_html__(
@@ -1821,7 +1821,7 @@  discard block
 block discarded – undo
1821 1821
             $total_line_item->ID()
1822 1822
         );
1823 1823
         if (WP_DEBUG) {
1824
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1824
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1825 1825
             throw new OutOfRangeException($message);
1826 1826
         }
1827 1827
         EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/db_models/fields/EE_Money_Field.php 1 patch
Indentation   +75 added lines, -75 removed lines patch added patch discarded remove patch
@@ -9,86 +9,86 @@
 block discarded – undo
9 9
  */
10 10
 class EE_Money_Field extends EE_Float_Field
11 11
 {
12
-    /**
13
-     * @var DecimalValues
14
-     */
15
-    protected $decimal_values;
12
+	/**
13
+	 * @var DecimalValues
14
+	 */
15
+	protected $decimal_values;
16 16
 
17
-    /**
18
-     * @param string $table_column
19
-     * @param string $nicename
20
-     * @param bool   $nullable
21
-     * @param null   $default_value
22
-     */
23
-    public function __construct($table_column, $nicename, $nullable, $default_value = null)
24
-    {
25
-        parent::__construct($table_column, $nicename, $nullable, $default_value);
26
-        $this->setSchemaType('object');
27
-        $this->decimal_values = LoaderFactory::getShared(DecimalValues::class);
28
-    }
17
+	/**
18
+	 * @param string $table_column
19
+	 * @param string $nicename
20
+	 * @param bool   $nullable
21
+	 * @param null   $default_value
22
+	 */
23
+	public function __construct($table_column, $nicename, $nullable, $default_value = null)
24
+	{
25
+		parent::__construct($table_column, $nicename, $nullable, $default_value);
26
+		$this->setSchemaType('object');
27
+		$this->decimal_values = LoaderFactory::getShared(DecimalValues::class);
28
+	}
29 29
 
30 30
 
31
-    /**
32
-     * Schemas:
33
-     *    'localized_float': "3,023.00"
34
-     *    'no_currency_code': "$3,023.00"
35
-     *    null: "$3,023.00<span>USD</span>"
36
-     *
37
-     * @param string $value_on_field_to_be_outputted
38
-     * @param string $schema
39
-     * @return string
40
-     * @throws EE_Error
41
-     */
42
-    public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema = null): string
43
-    {
44
-        if ($schema == 'localized_float') {
45
-            return parent::prepare_for_pretty_echoing($value_on_field_to_be_outputted);
46
-        }
47
-        $display_code = $schema !== 'no_currency_code';
48
-        // we don't use the $pretty_float because format_currency will take care of it.
49
-        return EEH_Template::format_currency($value_on_field_to_be_outputted, false, $display_code);
50
-    }
31
+	/**
32
+	 * Schemas:
33
+	 *    'localized_float': "3,023.00"
34
+	 *    'no_currency_code': "$3,023.00"
35
+	 *    null: "$3,023.00<span>USD</span>"
36
+	 *
37
+	 * @param string $value_on_field_to_be_outputted
38
+	 * @param string $schema
39
+	 * @return string
40
+	 * @throws EE_Error
41
+	 */
42
+	public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema = null): string
43
+	{
44
+		if ($schema == 'localized_float') {
45
+			return parent::prepare_for_pretty_echoing($value_on_field_to_be_outputted);
46
+		}
47
+		$display_code = $schema !== 'no_currency_code';
48
+		// we don't use the $pretty_float because format_currency will take care of it.
49
+		return EEH_Template::format_currency($value_on_field_to_be_outputted, false, $display_code);
50
+	}
51 51
 
52 52
 
53
-    /**
54
-     * If provided with a string, strips out money-related formatting to turn it into a proper float.
55
-     * Rounds the float to the correct number of decimal places for this country's currency.
56
-     * Also, interprets periods and commas according to the country's currency settings.
57
-     * So if you want to pass in a string that NEEDS to interpret periods as decimal marks, call floatval() on it first.
58
-     *
59
-     * @param string $value_inputted_for_field_on_model_object
60
-     * @return float
61
-     */
62
-    public function prepare_for_set($value_inputted_for_field_on_model_object): float
63
-    {
64
-        // now it's a float-style string or number
65
-        $float_val = parent::prepare_for_set($value_inputted_for_field_on_model_object);
66
-        // round to the correctly number of decimal places for this  currency
67
-        return $this->decimal_values->roundDecimalValue($float_val);
68
-    }
53
+	/**
54
+	 * If provided with a string, strips out money-related formatting to turn it into a proper float.
55
+	 * Rounds the float to the correct number of decimal places for this country's currency.
56
+	 * Also, interprets periods and commas according to the country's currency settings.
57
+	 * So if you want to pass in a string that NEEDS to interpret periods as decimal marks, call floatval() on it first.
58
+	 *
59
+	 * @param string $value_inputted_for_field_on_model_object
60
+	 * @return float
61
+	 */
62
+	public function prepare_for_set($value_inputted_for_field_on_model_object): float
63
+	{
64
+		// now it's a float-style string or number
65
+		$float_val = parent::prepare_for_set($value_inputted_for_field_on_model_object);
66
+		// round to the correctly number of decimal places for this  currency
67
+		return $this->decimal_values->roundDecimalValue($float_val);
68
+	}
69 69
 
70 70
 
71
-    /**
72
-     * @return array[]
73
-     */
74
-    public function getSchemaProperties(): array
75
-    {
76
-        return [
77
-            'raw'    => [
78
-                'description' => sprintf(
79
-                    __('%s - the raw value as it exists in the database as a simple float.', 'event_espresso'),
80
-                    $this->get_nicename()
81
-                ),
82
-                'type'        => 'number',
83
-            ],
84
-            'pretty' => [
85
-                'description' => sprintf(
86
-                    __('%s - formatted for display in the set currency and decimal places.', 'event_espresso'),
87
-                    $this->get_nicename()
88
-                ),
89
-                'type'        => 'string',
90
-                'format'      => 'money',
91
-            ],
92
-        ];
93
-    }
71
+	/**
72
+	 * @return array[]
73
+	 */
74
+	public function getSchemaProperties(): array
75
+	{
76
+		return [
77
+			'raw'    => [
78
+				'description' => sprintf(
79
+					__('%s - the raw value as it exists in the database as a simple float.', 'event_espresso'),
80
+					$this->get_nicename()
81
+				),
82
+				'type'        => 'number',
83
+			],
84
+			'pretty' => [
85
+				'description' => sprintf(
86
+					__('%s - formatted for display in the set currency and decimal places.', 'event_espresso'),
87
+					$this->get_nicename()
88
+				),
89
+				'type'        => 'string',
90
+				'format'      => 'money',
91
+			],
92
+		];
93
+	}
94 94
 }
Please login to merge, or discard this patch.
core/EE_Object_Collection.core.php 1 patch
Indentation   +156 added lines, -156 removed lines patch added patch discarded remove patch
@@ -16,160 +16,160 @@
 block discarded – undo
16 16
 abstract class EE_Object_Collection extends SplObjectStorage implements EEI_Collection
17 17
 {
18 18
 
19
-    /**
20
-     * an interface (or class) name to be used for restricting the type of objects added to the storage
21
-     * this should be set from within the child class constructor
22
-     *
23
-     * @type string $interface
24
-     */
25
-    protected $interface;
26
-
27
-
28
-    /**
29
-     * add
30
-     *
31
-     * attaches an object to the Collection
32
-     * and sets any supplied data associated with the current iterator entry
33
-     * by calling EE_Object_Collection::set_info()
34
-     *
35
-     * @access public
36
-     * @param object $object
37
-     * @param mixed  $info
38
-     * @return bool
39
-     */
40
-    public function add($object, $info = null)
41
-    {
42
-        $class = $this->interface;
43
-        if (! $object instanceof $class) {
44
-            return false;
45
-        }
46
-        $this->attach($object);
47
-        $this->set_info($object, $info);
48
-        return $this->contains($object);
49
-    }
50
-
51
-
52
-    /**
53
-     * set_info
54
-     *
55
-     * Sets the data associated with an object in the Collection
56
-     * if no $info is supplied, then the spl_object_hash() is used
57
-     *
58
-     * @access public
59
-     * @param object $object
60
-     * @param mixed  $info
61
-     * @return bool
62
-     */
63
-    public function set_info($object, $info = null)
64
-    {
65
-        $info = ! empty($info) ? $info : spl_object_hash($object);
66
-        $this->rewind();
67
-        while ($this->valid()) {
68
-            if ($object === $this->current()) {
69
-                $this->setInfo($info);
70
-                $this->rewind();
71
-                return true;
72
-            }
73
-            $this->next();
74
-        }
75
-        return false;
76
-    }
77
-
78
-
79
-    /**
80
-     * get_by_info
81
-     *
82
-     * finds and returns an object in the Collection based on the info that was set using addObject()
83
-     * PLZ NOTE: the pointer is reset to the beginning of the collection before returning
84
-     *
85
-     * @access public
86
-     * @param mixed
87
-     * @return null | object
88
-     */
89
-    public function get_by_info($info)
90
-    {
91
-        $this->rewind();
92
-        while ($this->valid()) {
93
-            if ($info === $this->getInfo()) {
94
-                $object = $this->current();
95
-                $this->rewind();
96
-                return $object;
97
-            }
98
-            $this->next();
99
-        }
100
-        return null;
101
-    }
102
-
103
-
104
-    /**
105
-     * has
106
-     *
107
-     * returns TRUE or FALSE depending on whether the supplied object is within the Collection
108
-     *
109
-     * @access public
110
-     * @param object $object
111
-     * @return bool
112
-     */
113
-    public function has($object)
114
-    {
115
-        return $this->contains($object);
116
-    }
117
-
118
-
119
-    /**
120
-     * remove
121
-     *
122
-     * detaches an object from the Collection
123
-     *
124
-     * @access public
125
-     * @param $object
126
-     * @return bool
127
-     */
128
-    public function remove($object)
129
-    {
130
-        $this->detach($object);
131
-        return true;
132
-    }
133
-
134
-
135
-    /**
136
-     * set_current
137
-     *
138
-     * advances pointer to the provided object
139
-     *
140
-     * @access public
141
-     * @param $object
142
-     * @return void
143
-     */
144
-    public function set_current($object)
145
-    {
146
-        $this->rewind();
147
-        while ($this->valid()) {
148
-            if ($this->current() === $object) {
149
-                break;
150
-            }
151
-            $this->next();
152
-        }
153
-    }
154
-
155
-
156
-    /**
157
-     * set_current_by_info
158
-     *
159
-     * advances pointer to the object whose info matches that which was provided
160
-     *
161
-     * @access public
162
-     * @param $info
163
-     * @return void
164
-     */
165
-    public function set_current_by_info($info)
166
-    {
167
-        $this->rewind();
168
-        while ($this->valid()) {
169
-            if ($info === $this->getInfo()) {
170
-                break;
171
-            }
172
-            $this->next();
173
-        }
174
-    }
19
+	/**
20
+	 * an interface (or class) name to be used for restricting the type of objects added to the storage
21
+	 * this should be set from within the child class constructor
22
+	 *
23
+	 * @type string $interface
24
+	 */
25
+	protected $interface;
26
+
27
+
28
+	/**
29
+	 * add
30
+	 *
31
+	 * attaches an object to the Collection
32
+	 * and sets any supplied data associated with the current iterator entry
33
+	 * by calling EE_Object_Collection::set_info()
34
+	 *
35
+	 * @access public
36
+	 * @param object $object
37
+	 * @param mixed  $info
38
+	 * @return bool
39
+	 */
40
+	public function add($object, $info = null)
41
+	{
42
+		$class = $this->interface;
43
+		if (! $object instanceof $class) {
44
+			return false;
45
+		}
46
+		$this->attach($object);
47
+		$this->set_info($object, $info);
48
+		return $this->contains($object);
49
+	}
50
+
51
+
52
+	/**
53
+	 * set_info
54
+	 *
55
+	 * Sets the data associated with an object in the Collection
56
+	 * if no $info is supplied, then the spl_object_hash() is used
57
+	 *
58
+	 * @access public
59
+	 * @param object $object
60
+	 * @param mixed  $info
61
+	 * @return bool
62
+	 */
63
+	public function set_info($object, $info = null)
64
+	{
65
+		$info = ! empty($info) ? $info : spl_object_hash($object);
66
+		$this->rewind();
67
+		while ($this->valid()) {
68
+			if ($object === $this->current()) {
69
+				$this->setInfo($info);
70
+				$this->rewind();
71
+				return true;
72
+			}
73
+			$this->next();
74
+		}
75
+		return false;
76
+	}
77
+
78
+
79
+	/**
80
+	 * get_by_info
81
+	 *
82
+	 * finds and returns an object in the Collection based on the info that was set using addObject()
83
+	 * PLZ NOTE: the pointer is reset to the beginning of the collection before returning
84
+	 *
85
+	 * @access public
86
+	 * @param mixed
87
+	 * @return null | object
88
+	 */
89
+	public function get_by_info($info)
90
+	{
91
+		$this->rewind();
92
+		while ($this->valid()) {
93
+			if ($info === $this->getInfo()) {
94
+				$object = $this->current();
95
+				$this->rewind();
96
+				return $object;
97
+			}
98
+			$this->next();
99
+		}
100
+		return null;
101
+	}
102
+
103
+
104
+	/**
105
+	 * has
106
+	 *
107
+	 * returns TRUE or FALSE depending on whether the supplied object is within the Collection
108
+	 *
109
+	 * @access public
110
+	 * @param object $object
111
+	 * @return bool
112
+	 */
113
+	public function has($object)
114
+	{
115
+		return $this->contains($object);
116
+	}
117
+
118
+
119
+	/**
120
+	 * remove
121
+	 *
122
+	 * detaches an object from the Collection
123
+	 *
124
+	 * @access public
125
+	 * @param $object
126
+	 * @return bool
127
+	 */
128
+	public function remove($object)
129
+	{
130
+		$this->detach($object);
131
+		return true;
132
+	}
133
+
134
+
135
+	/**
136
+	 * set_current
137
+	 *
138
+	 * advances pointer to the provided object
139
+	 *
140
+	 * @access public
141
+	 * @param $object
142
+	 * @return void
143
+	 */
144
+	public function set_current($object)
145
+	{
146
+		$this->rewind();
147
+		while ($this->valid()) {
148
+			if ($this->current() === $object) {
149
+				break;
150
+			}
151
+			$this->next();
152
+		}
153
+	}
154
+
155
+
156
+	/**
157
+	 * set_current_by_info
158
+	 *
159
+	 * advances pointer to the object whose info matches that which was provided
160
+	 *
161
+	 * @access public
162
+	 * @param $info
163
+	 * @return void
164
+	 */
165
+	public function set_current_by_info($info)
166
+	{
167
+		$this->rewind();
168
+		while ($this->valid()) {
169
+			if ($info === $this->getInfo()) {
170
+				break;
171
+			}
172
+			$this->next();
173
+		}
174
+	}
175 175
 }
Please login to merge, or discard this patch.