Completed
Branch dev (5b2722)
by
unknown
05:49 queued 10s
created
core/db_classes/EE_Line_Item.class.php 1 patch
Indentation   +1640 added lines, -1640 removed lines patch added patch discarded remove patch
@@ -15,1644 +15,1644 @@
 block discarded – undo
15 15
  */
16 16
 class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
17 17
 {
18
-    /**
19
-     * for children line items (currently not a normal relation)
20
-     *
21
-     * @type EE_Line_Item[]
22
-     */
23
-    protected $_children = array();
24
-
25
-    /**
26
-     * for the parent line item
27
-     *
28
-     * @var EE_Line_Item
29
-     */
30
-    protected $_parent;
31
-
32
-    /**
33
-     * @var LineItemCalculator
34
-     */
35
-    protected $calculator;
36
-
37
-
38
-    /**
39
-     * @param array  $props_n_values          incoming values
40
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
41
-     *                                        used.)
42
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
43
-     *                                        date_format and the second value is the time format
44
-     * @return EE_Line_Item
45
-     * @throws EE_Error
46
-     * @throws InvalidArgumentException
47
-     * @throws InvalidDataTypeException
48
-     * @throws InvalidInterfaceException
49
-     * @throws ReflectionException
50
-     */
51
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
52
-    {
53
-        $has_object = parent::_check_for_object(
54
-            $props_n_values,
55
-            __CLASS__,
56
-            $timezone,
57
-            $date_formats
58
-        );
59
-        return $has_object
60
-            ? $has_object
61
-            : new self($props_n_values, false, $timezone);
62
-    }
63
-
64
-
65
-    /**
66
-     * @param array  $props_n_values  incoming values from the database
67
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
68
-     *                                the website will be used.
69
-     * @return EE_Line_Item
70
-     * @throws EE_Error
71
-     * @throws InvalidArgumentException
72
-     * @throws InvalidDataTypeException
73
-     * @throws InvalidInterfaceException
74
-     * @throws ReflectionException
75
-     */
76
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
77
-    {
78
-        return new self($props_n_values, true, $timezone);
79
-    }
80
-
81
-
82
-    /**
83
-     * Adds some defaults if they're not specified
84
-     *
85
-     * @param array  $fieldValues
86
-     * @param bool   $bydb
87
-     * @param string $timezone
88
-     * @throws EE_Error
89
-     * @throws InvalidArgumentException
90
-     * @throws InvalidDataTypeException
91
-     * @throws InvalidInterfaceException
92
-     * @throws ReflectionException
93
-     */
94
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
95
-    {
96
-        $this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
97
-        parent::__construct($fieldValues, $bydb, $timezone);
98
-        if (! $this->get('LIN_code')) {
99
-            $this->set_code($this->generate_code());
100
-        }
101
-    }
102
-
103
-
104
-    /**
105
-     * Gets ID
106
-     *
107
-     * @return int
108
-     * @throws EE_Error
109
-     * @throws InvalidArgumentException
110
-     * @throws InvalidDataTypeException
111
-     * @throws InvalidInterfaceException
112
-     * @throws ReflectionException
113
-     */
114
-    public function ID()
115
-    {
116
-        return $this->get('LIN_ID');
117
-    }
118
-
119
-
120
-    /**
121
-     * Gets TXN_ID
122
-     *
123
-     * @return int
124
-     * @throws EE_Error
125
-     * @throws InvalidArgumentException
126
-     * @throws InvalidDataTypeException
127
-     * @throws InvalidInterfaceException
128
-     * @throws ReflectionException
129
-     */
130
-    public function TXN_ID()
131
-    {
132
-        return $this->get('TXN_ID');
133
-    }
134
-
135
-
136
-    /**
137
-     * Sets TXN_ID
138
-     *
139
-     * @param int $TXN_ID
140
-     * @throws EE_Error
141
-     * @throws InvalidArgumentException
142
-     * @throws InvalidDataTypeException
143
-     * @throws InvalidInterfaceException
144
-     * @throws ReflectionException
145
-     */
146
-    public function set_TXN_ID($TXN_ID)
147
-    {
148
-        $this->set('TXN_ID', $TXN_ID);
149
-    }
150
-
151
-
152
-    /**
153
-     * Gets name
154
-     *
155
-     * @return string
156
-     * @throws EE_Error
157
-     * @throws InvalidArgumentException
158
-     * @throws InvalidDataTypeException
159
-     * @throws InvalidInterfaceException
160
-     * @throws ReflectionException
161
-     */
162
-    public function name()
163
-    {
164
-        $name = $this->get('LIN_name');
165
-        if (! $name) {
166
-            $name = ucwords(str_replace('-', ' ', $this->type()));
167
-        }
168
-        return $name;
169
-    }
170
-
171
-
172
-    /**
173
-     * Sets name
174
-     *
175
-     * @param string $name
176
-     * @throws EE_Error
177
-     * @throws InvalidArgumentException
178
-     * @throws InvalidDataTypeException
179
-     * @throws InvalidInterfaceException
180
-     * @throws ReflectionException
181
-     */
182
-    public function set_name($name)
183
-    {
184
-        $this->set('LIN_name', $name);
185
-    }
186
-
187
-
188
-    /**
189
-     * Gets desc
190
-     *
191
-     * @return string
192
-     * @throws EE_Error
193
-     * @throws InvalidArgumentException
194
-     * @throws InvalidDataTypeException
195
-     * @throws InvalidInterfaceException
196
-     * @throws ReflectionException
197
-     */
198
-    public function desc()
199
-    {
200
-        return $this->get('LIN_desc');
201
-    }
202
-
203
-
204
-    /**
205
-     * Sets desc
206
-     *
207
-     * @param string $desc
208
-     * @throws EE_Error
209
-     * @throws InvalidArgumentException
210
-     * @throws InvalidDataTypeException
211
-     * @throws InvalidInterfaceException
212
-     * @throws ReflectionException
213
-     */
214
-    public function set_desc($desc)
215
-    {
216
-        $this->set('LIN_desc', $desc);
217
-    }
218
-
219
-
220
-    /**
221
-     * Gets quantity
222
-     *
223
-     * @return int
224
-     * @throws EE_Error
225
-     * @throws InvalidArgumentException
226
-     * @throws InvalidDataTypeException
227
-     * @throws InvalidInterfaceException
228
-     * @throws ReflectionException
229
-     */
230
-    public function quantity(): int
231
-    {
232
-        return (int) $this->get('LIN_quantity');
233
-    }
234
-
235
-
236
-    /**
237
-     * Sets quantity
238
-     *
239
-     * @param int $quantity
240
-     * @throws EE_Error
241
-     * @throws InvalidArgumentException
242
-     * @throws InvalidDataTypeException
243
-     * @throws InvalidInterfaceException
244
-     * @throws ReflectionException
245
-     */
246
-    public function set_quantity($quantity)
247
-    {
248
-        $this->set('LIN_quantity', max($quantity, 0));
249
-    }
250
-
251
-
252
-    /**
253
-     * Gets item_id
254
-     *
255
-     * @return string
256
-     * @throws EE_Error
257
-     * @throws InvalidArgumentException
258
-     * @throws InvalidDataTypeException
259
-     * @throws InvalidInterfaceException
260
-     * @throws ReflectionException
261
-     */
262
-    public function OBJ_ID()
263
-    {
264
-        return $this->get('OBJ_ID');
265
-    }
266
-
267
-
268
-    /**
269
-     * Sets item_id
270
-     *
271
-     * @param string $item_id
272
-     * @throws EE_Error
273
-     * @throws InvalidArgumentException
274
-     * @throws InvalidDataTypeException
275
-     * @throws InvalidInterfaceException
276
-     * @throws ReflectionException
277
-     */
278
-    public function set_OBJ_ID($item_id)
279
-    {
280
-        $this->set('OBJ_ID', $item_id);
281
-    }
282
-
283
-
284
-    /**
285
-     * Gets item_type
286
-     *
287
-     * @return string
288
-     * @throws EE_Error
289
-     * @throws InvalidArgumentException
290
-     * @throws InvalidDataTypeException
291
-     * @throws InvalidInterfaceException
292
-     * @throws ReflectionException
293
-     */
294
-    public function OBJ_type()
295
-    {
296
-        return $this->get('OBJ_type');
297
-    }
298
-
299
-
300
-    /**
301
-     * Gets item_type
302
-     *
303
-     * @return string
304
-     * @throws EE_Error
305
-     * @throws InvalidArgumentException
306
-     * @throws InvalidDataTypeException
307
-     * @throws InvalidInterfaceException
308
-     * @throws ReflectionException
309
-     */
310
-    public function OBJ_type_i18n()
311
-    {
312
-        $obj_type = $this->OBJ_type();
313
-        switch ($obj_type) {
314
-            case EEM_Line_Item::OBJ_TYPE_EVENT:
315
-                $obj_type = esc_html__('Event', 'event_espresso');
316
-                break;
317
-            case EEM_Line_Item::OBJ_TYPE_PRICE:
318
-                $obj_type = esc_html__('Price', 'event_espresso');
319
-                break;
320
-            case EEM_Line_Item::OBJ_TYPE_PROMOTION:
321
-                $obj_type = esc_html__('Promotion', 'event_espresso');
322
-                break;
323
-            case EEM_Line_Item::OBJ_TYPE_TICKET:
324
-                $obj_type = esc_html__('Ticket', 'event_espresso');
325
-                break;
326
-            case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
327
-                $obj_type = esc_html__('Transaction', 'event_espresso');
328
-                break;
329
-        }
330
-        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
331
-    }
332
-
333
-
334
-    /**
335
-     * Sets item_type
336
-     *
337
-     * @param string $OBJ_type
338
-     * @throws EE_Error
339
-     * @throws InvalidArgumentException
340
-     * @throws InvalidDataTypeException
341
-     * @throws InvalidInterfaceException
342
-     * @throws ReflectionException
343
-     */
344
-    public function set_OBJ_type($OBJ_type)
345
-    {
346
-        $this->set('OBJ_type', $OBJ_type);
347
-    }
348
-
349
-
350
-    /**
351
-     * Gets unit_price
352
-     *
353
-     * @return float
354
-     * @throws EE_Error
355
-     * @throws InvalidArgumentException
356
-     * @throws InvalidDataTypeException
357
-     * @throws InvalidInterfaceException
358
-     * @throws ReflectionException
359
-     */
360
-    public function unit_price()
361
-    {
362
-        return $this->get('LIN_unit_price');
363
-    }
364
-
365
-
366
-    /**
367
-     * Sets unit_price
368
-     *
369
-     * @param float $unit_price
370
-     * @throws EE_Error
371
-     * @throws InvalidArgumentException
372
-     * @throws InvalidDataTypeException
373
-     * @throws InvalidInterfaceException
374
-     * @throws ReflectionException
375
-     */
376
-    public function set_unit_price($unit_price)
377
-    {
378
-        $this->set('LIN_unit_price', $unit_price);
379
-    }
380
-
381
-
382
-    /**
383
-     * Checks if this item is a percentage modifier or not
384
-     *
385
-     * @return boolean
386
-     * @throws EE_Error
387
-     * @throws InvalidArgumentException
388
-     * @throws InvalidDataTypeException
389
-     * @throws InvalidInterfaceException
390
-     * @throws ReflectionException
391
-     */
392
-    public function is_percent()
393
-    {
394
-        if ($this->is_tax_sub_total()) {
395
-            // tax subtotals HAVE a percent on them, that percentage only applies
396
-            // to taxable items, so its' an exception. Treat it like a flat line item
397
-            return false;
398
-        }
399
-        $unit_price = abs($this->get('LIN_unit_price'));
400
-        $percent = abs($this->get('LIN_percent'));
401
-        if ($unit_price < .001 && $percent) {
402
-            return true;
403
-        }
404
-        if ($unit_price >= .001 && ! $percent) {
405
-            return false;
406
-        }
407
-        if ($unit_price >= .001 && $percent) {
408
-            throw new EE_Error(
409
-                sprintf(
410
-                    esc_html__(
411
-                        'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
412
-                        'event_espresso'
413
-                    ),
414
-                    $unit_price,
415
-                    $percent
416
-                )
417
-            );
418
-        }
419
-        // if they're both 0, assume its not a percent item
420
-        return false;
421
-    }
422
-
423
-
424
-    /**
425
-     * Gets percent (between 100-.001)
426
-     *
427
-     * @return float
428
-     * @throws EE_Error
429
-     * @throws InvalidArgumentException
430
-     * @throws InvalidDataTypeException
431
-     * @throws InvalidInterfaceException
432
-     * @throws ReflectionException
433
-     */
434
-    public function percent()
435
-    {
436
-        return $this->get('LIN_percent');
437
-    }
438
-
439
-
440
-    /**
441
-     * @return string
442
-     * @throws EE_Error
443
-     * @throws ReflectionException
444
-     * @since $VID:$
445
-     */
446
-    public function prettyPercent(): string
447
-    {
448
-        return $this->get_pretty('LIN_percent');
449
-    }
450
-
451
-
452
-    /**
453
-     * Sets percent (between 100-0.01)
454
-     *
455
-     * @param float $percent
456
-     * @throws EE_Error
457
-     * @throws InvalidArgumentException
458
-     * @throws InvalidDataTypeException
459
-     * @throws InvalidInterfaceException
460
-     * @throws ReflectionException
461
-     */
462
-    public function set_percent($percent)
463
-    {
464
-        $this->set('LIN_percent', $percent);
465
-    }
466
-
467
-
468
-    /**
469
-     * Gets total
470
-     *
471
-     * @return float
472
-     * @throws EE_Error
473
-     * @throws InvalidArgumentException
474
-     * @throws InvalidDataTypeException
475
-     * @throws InvalidInterfaceException
476
-     * @throws ReflectionException
477
-     */
478
-    public function pretaxTotal(): float
479
-    {
480
-        return $this->get('LIN_pretax');
481
-    }
482
-
483
-
484
-    /**
485
-     * Sets total
486
-     *
487
-     * @param float $pretax_total
488
-     * @throws EE_Error
489
-     * @throws InvalidArgumentException
490
-     * @throws InvalidDataTypeException
491
-     * @throws InvalidInterfaceException
492
-     * @throws ReflectionException
493
-     */
494
-    public function setPretaxTotal(float $pretax_total)
495
-    {
496
-        $this->set('LIN_pretax', $pretax_total);
497
-    }
498
-
499
-
500
-    /**
501
-     * @return float
502
-     * @throws EE_Error
503
-     * @throws ReflectionException
504
-     * @since  $VID:$
505
-     */
506
-    public function totalWithTax(): float
507
-    {
508
-        return $this->get('LIN_total');
509
-    }
510
-
511
-
512
-    /**
513
-     * Sets total
514
-     *
515
-     * @param float $total
516
-     * @throws EE_Error
517
-     * @throws ReflectionException
518
-     * @since  $VID:$
519
-     */
520
-    public function setTotalWithTax(float $total)
521
-    {
522
-        $this->set('LIN_total', $total);
523
-    }
524
-
525
-
526
-    /**
527
-     * Gets total
528
-     *
529
-     * @return float
530
-     * @throws EE_Error
531
-     * @throws ReflectionException
532
-     * @deprecatd $VID:$
533
-     */
534
-    public function total(): float
535
-    {
536
-        return $this->totalWithTax();
537
-    }
538
-
539
-
540
-    /**
541
-     * Sets total
542
-     *
543
-     * @param float $total
544
-     * @throws EE_Error
545
-     * @throws ReflectionException
546
-     * @deprecatd $VID:$
547
-     */
548
-    public function set_total($total)
549
-    {
550
-        $this->setTotalWithTax($total);
551
-    }
552
-
553
-
554
-    /**
555
-     * Gets order
556
-     *
557
-     * @return int
558
-     * @throws EE_Error
559
-     * @throws InvalidArgumentException
560
-     * @throws InvalidDataTypeException
561
-     * @throws InvalidInterfaceException
562
-     * @throws ReflectionException
563
-     */
564
-    public function order()
565
-    {
566
-        return $this->get('LIN_order');
567
-    }
568
-
569
-
570
-    /**
571
-     * Sets order
572
-     *
573
-     * @param int $order
574
-     * @throws EE_Error
575
-     * @throws InvalidArgumentException
576
-     * @throws InvalidDataTypeException
577
-     * @throws InvalidInterfaceException
578
-     * @throws ReflectionException
579
-     */
580
-    public function set_order($order)
581
-    {
582
-        $this->set('LIN_order', $order);
583
-    }
584
-
585
-
586
-    /**
587
-     * Gets parent
588
-     *
589
-     * @return int
590
-     * @throws EE_Error
591
-     * @throws InvalidArgumentException
592
-     * @throws InvalidDataTypeException
593
-     * @throws InvalidInterfaceException
594
-     * @throws ReflectionException
595
-     */
596
-    public function parent_ID()
597
-    {
598
-        return $this->get('LIN_parent');
599
-    }
600
-
601
-
602
-    /**
603
-     * Sets parent
604
-     *
605
-     * @param int $parent
606
-     * @throws EE_Error
607
-     * @throws InvalidArgumentException
608
-     * @throws InvalidDataTypeException
609
-     * @throws InvalidInterfaceException
610
-     * @throws ReflectionException
611
-     */
612
-    public function set_parent_ID($parent)
613
-    {
614
-        $this->set('LIN_parent', $parent);
615
-    }
616
-
617
-
618
-    /**
619
-     * Gets type
620
-     *
621
-     * @return string
622
-     * @throws EE_Error
623
-     * @throws InvalidArgumentException
624
-     * @throws InvalidDataTypeException
625
-     * @throws InvalidInterfaceException
626
-     * @throws ReflectionException
627
-     */
628
-    public function type()
629
-    {
630
-        return $this->get('LIN_type');
631
-    }
632
-
633
-
634
-    /**
635
-     * Sets type
636
-     *
637
-     * @param string $type
638
-     * @throws EE_Error
639
-     * @throws InvalidArgumentException
640
-     * @throws InvalidDataTypeException
641
-     * @throws InvalidInterfaceException
642
-     * @throws ReflectionException
643
-     */
644
-    public function set_type($type)
645
-    {
646
-        $this->set('LIN_type', $type);
647
-    }
648
-
649
-
650
-    /**
651
-     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
652
-     * 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
653
-     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
654
-     * or indirectly by `EE_Line_item::add_child_line_item()`)
655
-     *
656
-     * @return EE_Base_Class|EE_Line_Item
657
-     * @throws EE_Error
658
-     * @throws InvalidArgumentException
659
-     * @throws InvalidDataTypeException
660
-     * @throws InvalidInterfaceException
661
-     * @throws ReflectionException
662
-     */
663
-    public function parent()
664
-    {
665
-        return $this->ID()
666
-            ? $this->get_model()->get_one_by_ID($this->parent_ID())
667
-            : $this->_parent;
668
-    }
669
-
670
-
671
-    /**
672
-     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
673
-     *
674
-     * @return EE_Line_Item[]
675
-     * @throws EE_Error
676
-     * @throws InvalidArgumentException
677
-     * @throws InvalidDataTypeException
678
-     * @throws InvalidInterfaceException
679
-     * @throws ReflectionException
680
-     */
681
-    public function children(array $query_params = []): array
682
-    {
683
-        if ($this->ID()) {
684
-            // ensure where params are an array
685
-            $query_params[0] = $query_params[0] ?? [];
686
-            // add defaults for line item parent and orderby
687
-            $query_params[0] += ['LIN_parent' => $this->ID()];
688
-            $query_params += ['order_by' => ['LIN_order' => 'ASC']];
689
-            return $this->get_model()->get_all($query_params);
690
-        }
691
-        if (! is_array($this->_children)) {
692
-            $this->_children = array();
693
-        }
694
-        return $this->_children;
695
-    }
696
-
697
-
698
-    /**
699
-     * Gets code
700
-     *
701
-     * @return string
702
-     * @throws EE_Error
703
-     * @throws InvalidArgumentException
704
-     * @throws InvalidDataTypeException
705
-     * @throws InvalidInterfaceException
706
-     * @throws ReflectionException
707
-     */
708
-    public function code()
709
-    {
710
-        return $this->get('LIN_code');
711
-    }
712
-
713
-
714
-    /**
715
-     * Sets code
716
-     *
717
-     * @param string $code
718
-     * @throws EE_Error
719
-     * @throws InvalidArgumentException
720
-     * @throws InvalidDataTypeException
721
-     * @throws InvalidInterfaceException
722
-     * @throws ReflectionException
723
-     */
724
-    public function set_code($code)
725
-    {
726
-        $this->set('LIN_code', $code);
727
-    }
728
-
729
-
730
-    /**
731
-     * Gets is_taxable
732
-     *
733
-     * @return boolean
734
-     * @throws EE_Error
735
-     * @throws InvalidArgumentException
736
-     * @throws InvalidDataTypeException
737
-     * @throws InvalidInterfaceException
738
-     * @throws ReflectionException
739
-     */
740
-    public function is_taxable()
741
-    {
742
-        return $this->get('LIN_is_taxable');
743
-    }
744
-
745
-
746
-    /**
747
-     * Sets is_taxable
748
-     *
749
-     * @param boolean $is_taxable
750
-     * @throws EE_Error
751
-     * @throws InvalidArgumentException
752
-     * @throws InvalidDataTypeException
753
-     * @throws InvalidInterfaceException
754
-     * @throws ReflectionException
755
-     */
756
-    public function set_is_taxable($is_taxable)
757
-    {
758
-        $this->set('LIN_is_taxable', $is_taxable);
759
-    }
760
-
761
-
762
-    /**
763
-     * Gets the object that this model-joins-to.
764
-     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
765
-     * EEM_Promotion_Object
766
-     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
767
-     *
768
-     * @return EE_Base_Class | NULL
769
-     * @throws EE_Error
770
-     * @throws InvalidArgumentException
771
-     * @throws InvalidDataTypeException
772
-     * @throws InvalidInterfaceException
773
-     * @throws ReflectionException
774
-     */
775
-    public function get_object()
776
-    {
777
-        $model_name_of_related_obj = $this->OBJ_type();
778
-        return $this->get_model()->has_relation($model_name_of_related_obj)
779
-            ? $this->get_first_related($model_name_of_related_obj)
780
-            : null;
781
-    }
782
-
783
-
784
-    /**
785
-     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
786
-     * (IE, if this line item is for a price or something else, will return NULL)
787
-     *
788
-     * @param array $query_params
789
-     * @return EE_Base_Class|EE_Ticket
790
-     * @throws EE_Error
791
-     * @throws InvalidArgumentException
792
-     * @throws InvalidDataTypeException
793
-     * @throws InvalidInterfaceException
794
-     * @throws ReflectionException
795
-     */
796
-    public function ticket($query_params = array())
797
-    {
798
-        // we're going to assume that when this method is called
799
-        // we always want to receive the attached ticket EVEN if that ticket is archived.
800
-        // This can be overridden via the incoming $query_params argument
801
-        $remove_defaults = array('default_where_conditions' => 'none');
802
-        $query_params = array_merge($remove_defaults, $query_params);
803
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
804
-    }
805
-
806
-
807
-    /**
808
-     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
809
-     *
810
-     * @return EE_Datetime | NULL
811
-     * @throws EE_Error
812
-     * @throws InvalidArgumentException
813
-     * @throws InvalidDataTypeException
814
-     * @throws InvalidInterfaceException
815
-     * @throws ReflectionException
816
-     */
817
-    public function get_ticket_datetime()
818
-    {
819
-        if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
820
-            $ticket = $this->ticket();
821
-            if ($ticket instanceof EE_Ticket) {
822
-                $datetime = $ticket->first_datetime();
823
-                if ($datetime instanceof EE_Datetime) {
824
-                    return $datetime;
825
-                }
826
-            }
827
-        }
828
-        return null;
829
-    }
830
-
831
-
832
-    /**
833
-     * Gets the event's name that's related to the ticket, if this is for
834
-     * a ticket
835
-     *
836
-     * @return string
837
-     * @throws EE_Error
838
-     * @throws InvalidArgumentException
839
-     * @throws InvalidDataTypeException
840
-     * @throws InvalidInterfaceException
841
-     * @throws ReflectionException
842
-     */
843
-    public function ticket_event_name()
844
-    {
845
-        $event_name = esc_html__('Unknown', 'event_espresso');
846
-        $event = $this->ticket_event();
847
-        if ($event instanceof EE_Event) {
848
-            $event_name = $event->name();
849
-        }
850
-        return $event_name;
851
-    }
852
-
853
-
854
-    /**
855
-     * Gets the event that's related to the ticket, if this line item represents a ticket.
856
-     *
857
-     * @return EE_Event|null
858
-     * @throws EE_Error
859
-     * @throws InvalidArgumentException
860
-     * @throws InvalidDataTypeException
861
-     * @throws InvalidInterfaceException
862
-     * @throws ReflectionException
863
-     */
864
-    public function ticket_event()
865
-    {
866
-        $event = null;
867
-        $ticket = $this->ticket();
868
-        if ($ticket instanceof EE_Ticket) {
869
-            $datetime = $ticket->first_datetime();
870
-            if ($datetime instanceof EE_Datetime) {
871
-                $event = $datetime->event();
872
-            }
873
-        }
874
-        return $event;
875
-    }
876
-
877
-
878
-    /**
879
-     * Gets the first datetime for this lien item, assuming it's for a ticket
880
-     *
881
-     * @param string $date_format
882
-     * @param string $time_format
883
-     * @return string
884
-     * @throws EE_Error
885
-     * @throws InvalidArgumentException
886
-     * @throws InvalidDataTypeException
887
-     * @throws InvalidInterfaceException
888
-     * @throws ReflectionException
889
-     */
890
-    public function ticket_datetime_start($date_format = '', $time_format = '')
891
-    {
892
-        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
893
-        $datetime = $this->get_ticket_datetime();
894
-        if ($datetime) {
895
-            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
896
-        }
897
-        return $first_datetime_string;
898
-    }
899
-
900
-
901
-    /**
902
-     * Adds the line item as a child to this line item. If there is another child line
903
-     * item with the same LIN_code, it is overwritten by this new one
904
-     *
905
-     * @param EE_Line_Item $line_item
906
-     * @param bool          $set_order
907
-     * @return bool success
908
-     * @throws EE_Error
909
-     * @throws InvalidArgumentException
910
-     * @throws InvalidDataTypeException
911
-     * @throws InvalidInterfaceException
912
-     * @throws ReflectionException
913
-     */
914
-    public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
915
-    {
916
-        // should we calculate the LIN_order for this line item ?
917
-        if ($set_order || $line_item->order() === null) {
918
-            $line_item->set_order(count($this->children()));
919
-        }
920
-        if ($this->ID()) {
921
-            // check for any duplicate line items (with the same code), if so, this replaces it
922
-            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
923
-            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
924
-                $this->delete_child_line_item($line_item_with_same_code->code());
925
-            }
926
-            $line_item->set_parent_ID($this->ID());
927
-            if ($this->TXN_ID()) {
928
-                $line_item->set_TXN_ID($this->TXN_ID());
929
-            }
930
-            return $line_item->save();
931
-        }
932
-        $this->_children[ $line_item->code() ] = $line_item;
933
-        if ($line_item->parent() !== $this) {
934
-            $line_item->set_parent($this);
935
-        }
936
-        return true;
937
-    }
938
-
939
-
940
-    /**
941
-     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
942
-     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
943
-     * However, if this line item is NOT saved to the DB, this just caches the parent on
944
-     * the EE_Line_Item::_parent property.
945
-     *
946
-     * @param EE_Line_Item $line_item
947
-     * @throws EE_Error
948
-     * @throws InvalidArgumentException
949
-     * @throws InvalidDataTypeException
950
-     * @throws InvalidInterfaceException
951
-     * @throws ReflectionException
952
-     */
953
-    public function set_parent($line_item)
954
-    {
955
-        if ($this->ID()) {
956
-            if (! $line_item->ID()) {
957
-                $line_item->save();
958
-            }
959
-            $this->set_parent_ID($line_item->ID());
960
-            $this->save();
961
-        } else {
962
-            $this->_parent = $line_item;
963
-            $this->set_parent_ID($line_item->ID());
964
-        }
965
-    }
966
-
967
-
968
-    /**
969
-     * Gets the child line item as specified by its code. Because this returns an object (by reference)
970
-     * you can modify this child line item and the parent (this object) can know about them
971
-     * because it also has a reference to that line item
972
-     *
973
-     * @param string $code
974
-     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
975
-     * @throws EE_Error
976
-     * @throws InvalidArgumentException
977
-     * @throws InvalidDataTypeException
978
-     * @throws InvalidInterfaceException
979
-     * @throws ReflectionException
980
-     */
981
-    public function get_child_line_item($code)
982
-    {
983
-        if ($this->ID()) {
984
-            return $this->get_model()->get_one(
985
-                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
986
-            );
987
-        }
988
-        return isset($this->_children[ $code ])
989
-            ? $this->_children[ $code ]
990
-            : null;
991
-    }
992
-
993
-
994
-    /**
995
-     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
996
-     * cached on it)
997
-     *
998
-     * @return int
999
-     * @throws EE_Error
1000
-     * @throws InvalidArgumentException
1001
-     * @throws InvalidDataTypeException
1002
-     * @throws InvalidInterfaceException
1003
-     * @throws ReflectionException
1004
-     */
1005
-    public function delete_children_line_items()
1006
-    {
1007
-        if ($this->ID()) {
1008
-            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
1009
-        }
1010
-        $count = count($this->_children);
1011
-        $this->_children = array();
1012
-        return $count;
1013
-    }
1014
-
1015
-
1016
-    /**
1017
-     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
1018
-     * HAS NOT been saved to the DB, removes the child line item with index $code.
1019
-     * Also searches through the child's children for a matching line item. However, once a line item has been found
1020
-     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
1021
-     * deleted)
1022
-     *
1023
-     * @param string $code
1024
-     * @param bool   $stop_search_once_found
1025
-     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
1026
-     *             the DB yet)
1027
-     * @throws EE_Error
1028
-     * @throws InvalidArgumentException
1029
-     * @throws InvalidDataTypeException
1030
-     * @throws InvalidInterfaceException
1031
-     * @throws ReflectionException
1032
-     */
1033
-    public function delete_child_line_item($code, $stop_search_once_found = true)
1034
-    {
1035
-        if ($this->ID()) {
1036
-            $items_deleted = 0;
1037
-            if ($this->code() === $code) {
1038
-                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
1039
-                $items_deleted += (int) $this->delete();
1040
-                if ($stop_search_once_found) {
1041
-                    return $items_deleted;
1042
-                }
1043
-            }
1044
-            foreach ($this->children() as $child_line_item) {
1045
-                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1046
-            }
1047
-            return $items_deleted;
1048
-        }
1049
-        if (isset($this->_children[ $code ])) {
1050
-            unset($this->_children[ $code ]);
1051
-            return 1;
1052
-        }
1053
-        return 0;
1054
-    }
1055
-
1056
-
1057
-    /**
1058
-     * If this line item is in the database, is of the type subtotal, and
1059
-     * has no children, why do we have it? It should be deleted so this function
1060
-     * does that
1061
-     *
1062
-     * @return boolean
1063
-     * @throws EE_Error
1064
-     * @throws InvalidArgumentException
1065
-     * @throws InvalidDataTypeException
1066
-     * @throws InvalidInterfaceException
1067
-     * @throws ReflectionException
1068
-     */
1069
-    public function delete_if_childless_subtotal()
1070
-    {
1071
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1072
-            return $this->delete();
1073
-        }
1074
-        return false;
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * Creates a code and returns a string. doesn't assign the code to this model object
1080
-     *
1081
-     * @return string
1082
-     * @throws EE_Error
1083
-     * @throws InvalidArgumentException
1084
-     * @throws InvalidDataTypeException
1085
-     * @throws InvalidInterfaceException
1086
-     * @throws ReflectionException
1087
-     */
1088
-    public function generate_code()
1089
-    {
1090
-        // each line item in the cart requires a unique identifier
1091
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1092
-    }
1093
-
1094
-
1095
-    /**
1096
-     * @return bool
1097
-     * @throws EE_Error
1098
-     * @throws InvalidArgumentException
1099
-     * @throws InvalidDataTypeException
1100
-     * @throws InvalidInterfaceException
1101
-     * @throws ReflectionException
1102
-     */
1103
-    public function isGlobalTax(): bool
1104
-    {
1105
-        return $this->type() === EEM_Line_Item::type_tax;
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * @return bool
1111
-     * @throws EE_Error
1112
-     * @throws InvalidArgumentException
1113
-     * @throws InvalidDataTypeException
1114
-     * @throws InvalidInterfaceException
1115
-     * @throws ReflectionException
1116
-     */
1117
-    public function isSubTax(): bool
1118
-    {
1119
-        return $this->type() === EEM_Line_Item::type_sub_tax;
1120
-    }
1121
-
1122
-
1123
-    /**
1124
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1125
-     *
1126
-     * @return array
1127
-     * @throws EE_Error
1128
-     * @throws InvalidArgumentException
1129
-     * @throws InvalidDataTypeException
1130
-     * @throws InvalidInterfaceException
1131
-     * @throws ReflectionException
1132
-     */
1133
-    public function getSubTaxes(): array
1134
-    {
1135
-        if (! $this->is_line_item()) {
1136
-            return [];
1137
-        }
1138
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1139
-    }
1140
-
1141
-
1142
-    /**
1143
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1144
-     *
1145
-     * @return bool
1146
-     * @throws EE_Error
1147
-     * @throws InvalidArgumentException
1148
-     * @throws InvalidDataTypeException
1149
-     * @throws InvalidInterfaceException
1150
-     * @throws ReflectionException
1151
-     */
1152
-    public function hasSubTaxes(): bool
1153
-    {
1154
-        if (! $this->is_line_item()) {
1155
-            return false;
1156
-        }
1157
-        $sub_taxes = $this->getSubTaxes();
1158
-        return ! empty($sub_taxes);
1159
-    }
1160
-
1161
-
1162
-    /**
1163
-     * @return bool
1164
-     * @throws EE_Error
1165
-     * @throws ReflectionException
1166
-     * @deprecated   $VID:$
1167
-     */
1168
-    public function is_tax(): bool
1169
-    {
1170
-        return $this->isGlobalTax();
1171
-    }
1172
-
1173
-
1174
-    /**
1175
-     * @return bool
1176
-     * @throws EE_Error
1177
-     * @throws InvalidArgumentException
1178
-     * @throws InvalidDataTypeException
1179
-     * @throws InvalidInterfaceException
1180
-     * @throws ReflectionException
1181
-     */
1182
-    public function is_tax_sub_total()
1183
-    {
1184
-        return $this->type() === EEM_Line_Item::type_tax_sub_total;
1185
-    }
1186
-
1187
-
1188
-    /**
1189
-     * @return bool
1190
-     * @throws EE_Error
1191
-     * @throws InvalidArgumentException
1192
-     * @throws InvalidDataTypeException
1193
-     * @throws InvalidInterfaceException
1194
-     * @throws ReflectionException
1195
-     */
1196
-    public function is_line_item()
1197
-    {
1198
-        return $this->type() === EEM_Line_Item::type_line_item;
1199
-    }
1200
-
1201
-
1202
-    /**
1203
-     * @return bool
1204
-     * @throws EE_Error
1205
-     * @throws InvalidArgumentException
1206
-     * @throws InvalidDataTypeException
1207
-     * @throws InvalidInterfaceException
1208
-     * @throws ReflectionException
1209
-     */
1210
-    public function is_sub_line_item()
1211
-    {
1212
-        return $this->type() === EEM_Line_Item::type_sub_line_item;
1213
-    }
1214
-
1215
-
1216
-    /**
1217
-     * @return bool
1218
-     * @throws EE_Error
1219
-     * @throws InvalidArgumentException
1220
-     * @throws InvalidDataTypeException
1221
-     * @throws InvalidInterfaceException
1222
-     * @throws ReflectionException
1223
-     */
1224
-    public function is_sub_total()
1225
-    {
1226
-        return $this->type() === EEM_Line_Item::type_sub_total;
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Whether or not this line item is a cancellation line item
1232
-     *
1233
-     * @return boolean
1234
-     * @throws EE_Error
1235
-     * @throws InvalidArgumentException
1236
-     * @throws InvalidDataTypeException
1237
-     * @throws InvalidInterfaceException
1238
-     * @throws ReflectionException
1239
-     */
1240
-    public function is_cancellation()
1241
-    {
1242
-        return EEM_Line_Item::type_cancellation === $this->type();
1243
-    }
1244
-
1245
-
1246
-    /**
1247
-     * @return bool
1248
-     * @throws EE_Error
1249
-     * @throws InvalidArgumentException
1250
-     * @throws InvalidDataTypeException
1251
-     * @throws InvalidInterfaceException
1252
-     * @throws ReflectionException
1253
-     */
1254
-    public function is_total()
1255
-    {
1256
-        return $this->type() === EEM_Line_Item::type_total;
1257
-    }
1258
-
1259
-
1260
-    /**
1261
-     * @return bool
1262
-     * @throws EE_Error
1263
-     * @throws InvalidArgumentException
1264
-     * @throws InvalidDataTypeException
1265
-     * @throws InvalidInterfaceException
1266
-     * @throws ReflectionException
1267
-     */
1268
-    public function is_cancelled()
1269
-    {
1270
-        return $this->type() === EEM_Line_Item::type_cancellation;
1271
-    }
1272
-
1273
-
1274
-    /**
1275
-     * @return string like '2, 004.00', formatted according to the localized currency
1276
-     * @throws EE_Error
1277
-     * @throws ReflectionException
1278
-     */
1279
-    public function unit_price_no_code(): string
1280
-    {
1281
-        return $this->prettyUnitPrice();
1282
-    }
1283
-
1284
-
1285
-    /**
1286
-     * @return string like '2, 004.00', formatted according to the localized currency
1287
-     * @throws EE_Error
1288
-     * @throws ReflectionException
1289
-     * @since $VID:$
1290
-     */
1291
-    public function prettyUnitPrice(): string
1292
-    {
1293
-        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1294
-    }
1295
-
1296
-
1297
-    /**
1298
-     * @return string like '2, 004.00', formatted according to the localized currency
1299
-     * @throws EE_Error
1300
-     * @throws ReflectionException
1301
-     */
1302
-    public function total_no_code(): string
1303
-    {
1304
-        return $this->prettyTotal();
1305
-    }
1306
-
1307
-
1308
-    /**
1309
-     * @return string like '2, 004.00', formatted according to the localized currency
1310
-     * @throws EE_Error
1311
-     * @throws ReflectionException
1312
-     * @since $VID:$
1313
-     */
1314
-    public function prettyTotal(): string
1315
-    {
1316
-        return $this->get_pretty('LIN_total', 'no_currency_code');
1317
-    }
1318
-
1319
-
1320
-    /**
1321
-     * Gets the final total on this item, taking taxes into account.
1322
-     * Has the side-effect of setting the sub-total as it was just calculated.
1323
-     * If this is used on a grand-total line item, also updates the transaction's
1324
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
1325
-     * want to change a persistable transaction with info from a non-persistent line item)
1326
-     *
1327
-     * @param bool $update_txn_status
1328
-     * @return float
1329
-     * @throws EE_Error
1330
-     * @throws InvalidArgumentException
1331
-     * @throws InvalidDataTypeException
1332
-     * @throws InvalidInterfaceException
1333
-     * @throws ReflectionException
1334
-     * @throws RuntimeException
1335
-     */
1336
-    public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1337
-    {
1338
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1339
-        return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1340
-    }
1341
-
1342
-
1343
-    /**
1344
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1345
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1346
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1347
-     * when this is called on the grand total
1348
-     *
1349
-     * @return float
1350
-     * @throws EE_Error
1351
-     * @throws InvalidArgumentException
1352
-     * @throws InvalidDataTypeException
1353
-     * @throws InvalidInterfaceException
1354
-     * @throws ReflectionException
1355
-     */
1356
-    public function recalculate_pre_tax_total(): float
1357
-    {
1358
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1359
-        [$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1360
-        return $total;
1361
-    }
1362
-
1363
-
1364
-    /**
1365
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1366
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1367
-     * and tax sub-total if already in the DB
1368
-     *
1369
-     * @return float
1370
-     * @throws EE_Error
1371
-     * @throws InvalidArgumentException
1372
-     * @throws InvalidDataTypeException
1373
-     * @throws InvalidInterfaceException
1374
-     * @throws ReflectionException
1375
-     */
1376
-    public function recalculate_taxes_and_tax_total(): float
1377
-    {
1378
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1379
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1380
-    }
1381
-
1382
-
1383
-    /**
1384
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1385
-     * recalculate_taxes_and_total
1386
-     *
1387
-     * @return float
1388
-     * @throws EE_Error
1389
-     * @throws InvalidArgumentException
1390
-     * @throws InvalidDataTypeException
1391
-     * @throws InvalidInterfaceException
1392
-     * @throws ReflectionException
1393
-     */
1394
-    public function get_total_tax()
1395
-    {
1396
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1397
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1398
-    }
1399
-
1400
-
1401
-    /**
1402
-     * Gets the total for all the items purchased only
1403
-     *
1404
-     * @return float
1405
-     * @throws EE_Error
1406
-     * @throws InvalidArgumentException
1407
-     * @throws InvalidDataTypeException
1408
-     * @throws InvalidInterfaceException
1409
-     * @throws ReflectionException
1410
-     */
1411
-    public function get_items_total()
1412
-    {
1413
-        // by default, let's make sure we're consistent with the existing line item
1414
-        if ($this->is_total()) {
1415
-            return $this->pretaxTotal();
1416
-        }
1417
-        $total = 0;
1418
-        foreach ($this->get_items() as $item) {
1419
-            if ($item instanceof EE_Line_Item) {
1420
-                $total += $item->pretaxTotal();
1421
-            }
1422
-        }
1423
-        return $total;
1424
-    }
1425
-
1426
-
1427
-    /**
1428
-     * Gets all the descendants (ie, children or children of children etc) that
1429
-     * are of the type 'tax'
1430
-     *
1431
-     * @return EE_Line_Item[]
1432
-     * @throws EE_Error
1433
-     */
1434
-    public function tax_descendants()
1435
-    {
1436
-        return EEH_Line_Item::get_tax_descendants($this);
1437
-    }
1438
-
1439
-
1440
-    /**
1441
-     * Gets all the real items purchased which are children of this item
1442
-     *
1443
-     * @return EE_Line_Item[]
1444
-     * @throws EE_Error
1445
-     */
1446
-    public function get_items()
1447
-    {
1448
-        return EEH_Line_Item::get_line_item_descendants($this);
1449
-    }
1450
-
1451
-
1452
-    /**
1453
-     * Returns the amount taxable among this line item's children (or if it has no children,
1454
-     * how much of it is taxable). Does not recalculate totals or subtotals.
1455
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1456
-     * but there is a "Taxable" discount), returns 0.
1457
-     *
1458
-     * @return float
1459
-     * @throws EE_Error
1460
-     * @throws InvalidArgumentException
1461
-     * @throws InvalidDataTypeException
1462
-     * @throws InvalidInterfaceException
1463
-     * @throws ReflectionException
1464
-     */
1465
-    public function taxable_total(): float
1466
-    {
1467
-        return $this->calculator->taxableAmountForGlobalTaxes($this);
1468
-    }
1469
-
1470
-
1471
-    /**
1472
-     * Gets the transaction for this line item
1473
-     *
1474
-     * @return EE_Base_Class|EE_Transaction
1475
-     * @throws EE_Error
1476
-     * @throws InvalidArgumentException
1477
-     * @throws InvalidDataTypeException
1478
-     * @throws InvalidInterfaceException
1479
-     * @throws ReflectionException
1480
-     */
1481
-    public function transaction()
1482
-    {
1483
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1484
-    }
1485
-
1486
-
1487
-    /**
1488
-     * Saves this line item to the DB, and recursively saves its descendants.
1489
-     * Because there currently is no proper parent-child relation on the model,
1490
-     * save_this_and_cached() will NOT save the descendants.
1491
-     * Also sets the transaction on this line item and all its descendants before saving
1492
-     *
1493
-     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1494
-     * @return int count of items saved
1495
-     * @throws EE_Error
1496
-     * @throws InvalidArgumentException
1497
-     * @throws InvalidDataTypeException
1498
-     * @throws InvalidInterfaceException
1499
-     * @throws ReflectionException
1500
-     */
1501
-    public function save_this_and_descendants_to_txn($txn_id = null)
1502
-    {
1503
-        $count = 0;
1504
-        if (! $txn_id) {
1505
-            $txn_id = $this->TXN_ID();
1506
-        }
1507
-        $this->set_TXN_ID($txn_id);
1508
-        $children = $this->children();
1509
-        $count += $this->save()
1510
-            ? 1
1511
-            : 0;
1512
-        foreach ($children as $child_line_item) {
1513
-            if ($child_line_item instanceof EE_Line_Item) {
1514
-                $child_line_item->set_parent_ID($this->ID());
1515
-                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1516
-            }
1517
-        }
1518
-        return $count;
1519
-    }
1520
-
1521
-
1522
-    /**
1523
-     * Saves this line item to the DB, and recursively saves its descendants.
1524
-     *
1525
-     * @return int count of items saved
1526
-     * @throws EE_Error
1527
-     * @throws InvalidArgumentException
1528
-     * @throws InvalidDataTypeException
1529
-     * @throws InvalidInterfaceException
1530
-     * @throws ReflectionException
1531
-     */
1532
-    public function save_this_and_descendants()
1533
-    {
1534
-        $count = 0;
1535
-        $children = $this->children();
1536
-        $count += $this->save()
1537
-            ? 1
1538
-            : 0;
1539
-        foreach ($children as $child_line_item) {
1540
-            if ($child_line_item instanceof EE_Line_Item) {
1541
-                $child_line_item->set_parent_ID($this->ID());
1542
-                $count += $child_line_item->save_this_and_descendants();
1543
-            }
1544
-        }
1545
-        return $count;
1546
-    }
1547
-
1548
-
1549
-    /**
1550
-     * returns the cancellation line item if this item was cancelled
1551
-     *
1552
-     * @return EE_Line_Item[]
1553
-     * @throws InvalidArgumentException
1554
-     * @throws InvalidInterfaceException
1555
-     * @throws InvalidDataTypeException
1556
-     * @throws ReflectionException
1557
-     * @throws EE_Error
1558
-     */
1559
-    public function get_cancellations()
1560
-    {
1561
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1562
-    }
1563
-
1564
-
1565
-    /**
1566
-     * If this item has an ID, then this saves it again to update the db
1567
-     *
1568
-     * @return int count of items saved
1569
-     * @throws EE_Error
1570
-     * @throws InvalidArgumentException
1571
-     * @throws InvalidDataTypeException
1572
-     * @throws InvalidInterfaceException
1573
-     * @throws ReflectionException
1574
-     */
1575
-    public function maybe_save()
1576
-    {
1577
-        if ($this->ID()) {
1578
-            return $this->save();
1579
-        }
1580
-        return false;
1581
-    }
1582
-
1583
-
1584
-    /**
1585
-     * clears the cached children and parent from the line item
1586
-     *
1587
-     * @return void
1588
-     */
1589
-    public function clear_related_line_item_cache()
1590
-    {
1591
-        $this->_children = array();
1592
-        $this->_parent = null;
1593
-    }
1594
-
1595
-
1596
-    /**
1597
-     * @param bool $raw
1598
-     * @return int
1599
-     * @throws EE_Error
1600
-     * @throws InvalidArgumentException
1601
-     * @throws InvalidDataTypeException
1602
-     * @throws InvalidInterfaceException
1603
-     * @throws ReflectionException
1604
-     */
1605
-    public function timestamp($raw = false)
1606
-    {
1607
-        return $raw
1608
-            ? $this->get_raw('LIN_timestamp')
1609
-            : $this->get('LIN_timestamp');
1610
-    }
1611
-
1612
-
1613
-
1614
-
1615
-    /************************* DEPRECATED *************************/
1616
-    /**
1617
-     * @deprecated 4.6.0
1618
-     * @param string $type one of the constants on EEM_Line_Item
1619
-     * @return EE_Line_Item[]
1620
-     * @throws EE_Error
1621
-     */
1622
-    protected function _get_descendants_of_type($type)
1623
-    {
1624
-        EE_Error::doing_it_wrong(
1625
-            'EE_Line_Item::_get_descendants_of_type()',
1626
-            sprintf(
1627
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1628
-                'EEH_Line_Item::get_descendants_of_type()'
1629
-            ),
1630
-            '4.6.0'
1631
-        );
1632
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1633
-    }
1634
-
1635
-
1636
-    /**
1637
-     * @deprecated 4.6.0
1638
-     * @param string $type like one of the EEM_Line_Item::type_*
1639
-     * @return EE_Line_Item
1640
-     * @throws EE_Error
1641
-     * @throws InvalidArgumentException
1642
-     * @throws InvalidDataTypeException
1643
-     * @throws InvalidInterfaceException
1644
-     * @throws ReflectionException
1645
-     */
1646
-    public function get_nearest_descendant_of_type(string $type): EE_Line_Item
1647
-    {
1648
-        EE_Error::doing_it_wrong(
1649
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1650
-            sprintf(
1651
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1652
-                'EEH_Line_Item::get_nearest_descendant_of_type()'
1653
-            ),
1654
-            '4.6.0'
1655
-        );
1656
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1657
-    }
18
+	/**
19
+	 * for children line items (currently not a normal relation)
20
+	 *
21
+	 * @type EE_Line_Item[]
22
+	 */
23
+	protected $_children = array();
24
+
25
+	/**
26
+	 * for the parent line item
27
+	 *
28
+	 * @var EE_Line_Item
29
+	 */
30
+	protected $_parent;
31
+
32
+	/**
33
+	 * @var LineItemCalculator
34
+	 */
35
+	protected $calculator;
36
+
37
+
38
+	/**
39
+	 * @param array  $props_n_values          incoming values
40
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
41
+	 *                                        used.)
42
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
43
+	 *                                        date_format and the second value is the time format
44
+	 * @return EE_Line_Item
45
+	 * @throws EE_Error
46
+	 * @throws InvalidArgumentException
47
+	 * @throws InvalidDataTypeException
48
+	 * @throws InvalidInterfaceException
49
+	 * @throws ReflectionException
50
+	 */
51
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
52
+	{
53
+		$has_object = parent::_check_for_object(
54
+			$props_n_values,
55
+			__CLASS__,
56
+			$timezone,
57
+			$date_formats
58
+		);
59
+		return $has_object
60
+			? $has_object
61
+			: new self($props_n_values, false, $timezone);
62
+	}
63
+
64
+
65
+	/**
66
+	 * @param array  $props_n_values  incoming values from the database
67
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
68
+	 *                                the website will be used.
69
+	 * @return EE_Line_Item
70
+	 * @throws EE_Error
71
+	 * @throws InvalidArgumentException
72
+	 * @throws InvalidDataTypeException
73
+	 * @throws InvalidInterfaceException
74
+	 * @throws ReflectionException
75
+	 */
76
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
77
+	{
78
+		return new self($props_n_values, true, $timezone);
79
+	}
80
+
81
+
82
+	/**
83
+	 * Adds some defaults if they're not specified
84
+	 *
85
+	 * @param array  $fieldValues
86
+	 * @param bool   $bydb
87
+	 * @param string $timezone
88
+	 * @throws EE_Error
89
+	 * @throws InvalidArgumentException
90
+	 * @throws InvalidDataTypeException
91
+	 * @throws InvalidInterfaceException
92
+	 * @throws ReflectionException
93
+	 */
94
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
95
+	{
96
+		$this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
97
+		parent::__construct($fieldValues, $bydb, $timezone);
98
+		if (! $this->get('LIN_code')) {
99
+			$this->set_code($this->generate_code());
100
+		}
101
+	}
102
+
103
+
104
+	/**
105
+	 * Gets ID
106
+	 *
107
+	 * @return int
108
+	 * @throws EE_Error
109
+	 * @throws InvalidArgumentException
110
+	 * @throws InvalidDataTypeException
111
+	 * @throws InvalidInterfaceException
112
+	 * @throws ReflectionException
113
+	 */
114
+	public function ID()
115
+	{
116
+		return $this->get('LIN_ID');
117
+	}
118
+
119
+
120
+	/**
121
+	 * Gets TXN_ID
122
+	 *
123
+	 * @return int
124
+	 * @throws EE_Error
125
+	 * @throws InvalidArgumentException
126
+	 * @throws InvalidDataTypeException
127
+	 * @throws InvalidInterfaceException
128
+	 * @throws ReflectionException
129
+	 */
130
+	public function TXN_ID()
131
+	{
132
+		return $this->get('TXN_ID');
133
+	}
134
+
135
+
136
+	/**
137
+	 * Sets TXN_ID
138
+	 *
139
+	 * @param int $TXN_ID
140
+	 * @throws EE_Error
141
+	 * @throws InvalidArgumentException
142
+	 * @throws InvalidDataTypeException
143
+	 * @throws InvalidInterfaceException
144
+	 * @throws ReflectionException
145
+	 */
146
+	public function set_TXN_ID($TXN_ID)
147
+	{
148
+		$this->set('TXN_ID', $TXN_ID);
149
+	}
150
+
151
+
152
+	/**
153
+	 * Gets name
154
+	 *
155
+	 * @return string
156
+	 * @throws EE_Error
157
+	 * @throws InvalidArgumentException
158
+	 * @throws InvalidDataTypeException
159
+	 * @throws InvalidInterfaceException
160
+	 * @throws ReflectionException
161
+	 */
162
+	public function name()
163
+	{
164
+		$name = $this->get('LIN_name');
165
+		if (! $name) {
166
+			$name = ucwords(str_replace('-', ' ', $this->type()));
167
+		}
168
+		return $name;
169
+	}
170
+
171
+
172
+	/**
173
+	 * Sets name
174
+	 *
175
+	 * @param string $name
176
+	 * @throws EE_Error
177
+	 * @throws InvalidArgumentException
178
+	 * @throws InvalidDataTypeException
179
+	 * @throws InvalidInterfaceException
180
+	 * @throws ReflectionException
181
+	 */
182
+	public function set_name($name)
183
+	{
184
+		$this->set('LIN_name', $name);
185
+	}
186
+
187
+
188
+	/**
189
+	 * Gets desc
190
+	 *
191
+	 * @return string
192
+	 * @throws EE_Error
193
+	 * @throws InvalidArgumentException
194
+	 * @throws InvalidDataTypeException
195
+	 * @throws InvalidInterfaceException
196
+	 * @throws ReflectionException
197
+	 */
198
+	public function desc()
199
+	{
200
+		return $this->get('LIN_desc');
201
+	}
202
+
203
+
204
+	/**
205
+	 * Sets desc
206
+	 *
207
+	 * @param string $desc
208
+	 * @throws EE_Error
209
+	 * @throws InvalidArgumentException
210
+	 * @throws InvalidDataTypeException
211
+	 * @throws InvalidInterfaceException
212
+	 * @throws ReflectionException
213
+	 */
214
+	public function set_desc($desc)
215
+	{
216
+		$this->set('LIN_desc', $desc);
217
+	}
218
+
219
+
220
+	/**
221
+	 * Gets quantity
222
+	 *
223
+	 * @return int
224
+	 * @throws EE_Error
225
+	 * @throws InvalidArgumentException
226
+	 * @throws InvalidDataTypeException
227
+	 * @throws InvalidInterfaceException
228
+	 * @throws ReflectionException
229
+	 */
230
+	public function quantity(): int
231
+	{
232
+		return (int) $this->get('LIN_quantity');
233
+	}
234
+
235
+
236
+	/**
237
+	 * Sets quantity
238
+	 *
239
+	 * @param int $quantity
240
+	 * @throws EE_Error
241
+	 * @throws InvalidArgumentException
242
+	 * @throws InvalidDataTypeException
243
+	 * @throws InvalidInterfaceException
244
+	 * @throws ReflectionException
245
+	 */
246
+	public function set_quantity($quantity)
247
+	{
248
+		$this->set('LIN_quantity', max($quantity, 0));
249
+	}
250
+
251
+
252
+	/**
253
+	 * Gets item_id
254
+	 *
255
+	 * @return string
256
+	 * @throws EE_Error
257
+	 * @throws InvalidArgumentException
258
+	 * @throws InvalidDataTypeException
259
+	 * @throws InvalidInterfaceException
260
+	 * @throws ReflectionException
261
+	 */
262
+	public function OBJ_ID()
263
+	{
264
+		return $this->get('OBJ_ID');
265
+	}
266
+
267
+
268
+	/**
269
+	 * Sets item_id
270
+	 *
271
+	 * @param string $item_id
272
+	 * @throws EE_Error
273
+	 * @throws InvalidArgumentException
274
+	 * @throws InvalidDataTypeException
275
+	 * @throws InvalidInterfaceException
276
+	 * @throws ReflectionException
277
+	 */
278
+	public function set_OBJ_ID($item_id)
279
+	{
280
+		$this->set('OBJ_ID', $item_id);
281
+	}
282
+
283
+
284
+	/**
285
+	 * Gets item_type
286
+	 *
287
+	 * @return string
288
+	 * @throws EE_Error
289
+	 * @throws InvalidArgumentException
290
+	 * @throws InvalidDataTypeException
291
+	 * @throws InvalidInterfaceException
292
+	 * @throws ReflectionException
293
+	 */
294
+	public function OBJ_type()
295
+	{
296
+		return $this->get('OBJ_type');
297
+	}
298
+
299
+
300
+	/**
301
+	 * Gets item_type
302
+	 *
303
+	 * @return string
304
+	 * @throws EE_Error
305
+	 * @throws InvalidArgumentException
306
+	 * @throws InvalidDataTypeException
307
+	 * @throws InvalidInterfaceException
308
+	 * @throws ReflectionException
309
+	 */
310
+	public function OBJ_type_i18n()
311
+	{
312
+		$obj_type = $this->OBJ_type();
313
+		switch ($obj_type) {
314
+			case EEM_Line_Item::OBJ_TYPE_EVENT:
315
+				$obj_type = esc_html__('Event', 'event_espresso');
316
+				break;
317
+			case EEM_Line_Item::OBJ_TYPE_PRICE:
318
+				$obj_type = esc_html__('Price', 'event_espresso');
319
+				break;
320
+			case EEM_Line_Item::OBJ_TYPE_PROMOTION:
321
+				$obj_type = esc_html__('Promotion', 'event_espresso');
322
+				break;
323
+			case EEM_Line_Item::OBJ_TYPE_TICKET:
324
+				$obj_type = esc_html__('Ticket', 'event_espresso');
325
+				break;
326
+			case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
327
+				$obj_type = esc_html__('Transaction', 'event_espresso');
328
+				break;
329
+		}
330
+		return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
331
+	}
332
+
333
+
334
+	/**
335
+	 * Sets item_type
336
+	 *
337
+	 * @param string $OBJ_type
338
+	 * @throws EE_Error
339
+	 * @throws InvalidArgumentException
340
+	 * @throws InvalidDataTypeException
341
+	 * @throws InvalidInterfaceException
342
+	 * @throws ReflectionException
343
+	 */
344
+	public function set_OBJ_type($OBJ_type)
345
+	{
346
+		$this->set('OBJ_type', $OBJ_type);
347
+	}
348
+
349
+
350
+	/**
351
+	 * Gets unit_price
352
+	 *
353
+	 * @return float
354
+	 * @throws EE_Error
355
+	 * @throws InvalidArgumentException
356
+	 * @throws InvalidDataTypeException
357
+	 * @throws InvalidInterfaceException
358
+	 * @throws ReflectionException
359
+	 */
360
+	public function unit_price()
361
+	{
362
+		return $this->get('LIN_unit_price');
363
+	}
364
+
365
+
366
+	/**
367
+	 * Sets unit_price
368
+	 *
369
+	 * @param float $unit_price
370
+	 * @throws EE_Error
371
+	 * @throws InvalidArgumentException
372
+	 * @throws InvalidDataTypeException
373
+	 * @throws InvalidInterfaceException
374
+	 * @throws ReflectionException
375
+	 */
376
+	public function set_unit_price($unit_price)
377
+	{
378
+		$this->set('LIN_unit_price', $unit_price);
379
+	}
380
+
381
+
382
+	/**
383
+	 * Checks if this item is a percentage modifier or not
384
+	 *
385
+	 * @return boolean
386
+	 * @throws EE_Error
387
+	 * @throws InvalidArgumentException
388
+	 * @throws InvalidDataTypeException
389
+	 * @throws InvalidInterfaceException
390
+	 * @throws ReflectionException
391
+	 */
392
+	public function is_percent()
393
+	{
394
+		if ($this->is_tax_sub_total()) {
395
+			// tax subtotals HAVE a percent on them, that percentage only applies
396
+			// to taxable items, so its' an exception. Treat it like a flat line item
397
+			return false;
398
+		}
399
+		$unit_price = abs($this->get('LIN_unit_price'));
400
+		$percent = abs($this->get('LIN_percent'));
401
+		if ($unit_price < .001 && $percent) {
402
+			return true;
403
+		}
404
+		if ($unit_price >= .001 && ! $percent) {
405
+			return false;
406
+		}
407
+		if ($unit_price >= .001 && $percent) {
408
+			throw new EE_Error(
409
+				sprintf(
410
+					esc_html__(
411
+						'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
412
+						'event_espresso'
413
+					),
414
+					$unit_price,
415
+					$percent
416
+				)
417
+			);
418
+		}
419
+		// if they're both 0, assume its not a percent item
420
+		return false;
421
+	}
422
+
423
+
424
+	/**
425
+	 * Gets percent (between 100-.001)
426
+	 *
427
+	 * @return float
428
+	 * @throws EE_Error
429
+	 * @throws InvalidArgumentException
430
+	 * @throws InvalidDataTypeException
431
+	 * @throws InvalidInterfaceException
432
+	 * @throws ReflectionException
433
+	 */
434
+	public function percent()
435
+	{
436
+		return $this->get('LIN_percent');
437
+	}
438
+
439
+
440
+	/**
441
+	 * @return string
442
+	 * @throws EE_Error
443
+	 * @throws ReflectionException
444
+	 * @since $VID:$
445
+	 */
446
+	public function prettyPercent(): string
447
+	{
448
+		return $this->get_pretty('LIN_percent');
449
+	}
450
+
451
+
452
+	/**
453
+	 * Sets percent (between 100-0.01)
454
+	 *
455
+	 * @param float $percent
456
+	 * @throws EE_Error
457
+	 * @throws InvalidArgumentException
458
+	 * @throws InvalidDataTypeException
459
+	 * @throws InvalidInterfaceException
460
+	 * @throws ReflectionException
461
+	 */
462
+	public function set_percent($percent)
463
+	{
464
+		$this->set('LIN_percent', $percent);
465
+	}
466
+
467
+
468
+	/**
469
+	 * Gets total
470
+	 *
471
+	 * @return float
472
+	 * @throws EE_Error
473
+	 * @throws InvalidArgumentException
474
+	 * @throws InvalidDataTypeException
475
+	 * @throws InvalidInterfaceException
476
+	 * @throws ReflectionException
477
+	 */
478
+	public function pretaxTotal(): float
479
+	{
480
+		return $this->get('LIN_pretax');
481
+	}
482
+
483
+
484
+	/**
485
+	 * Sets total
486
+	 *
487
+	 * @param float $pretax_total
488
+	 * @throws EE_Error
489
+	 * @throws InvalidArgumentException
490
+	 * @throws InvalidDataTypeException
491
+	 * @throws InvalidInterfaceException
492
+	 * @throws ReflectionException
493
+	 */
494
+	public function setPretaxTotal(float $pretax_total)
495
+	{
496
+		$this->set('LIN_pretax', $pretax_total);
497
+	}
498
+
499
+
500
+	/**
501
+	 * @return float
502
+	 * @throws EE_Error
503
+	 * @throws ReflectionException
504
+	 * @since  $VID:$
505
+	 */
506
+	public function totalWithTax(): float
507
+	{
508
+		return $this->get('LIN_total');
509
+	}
510
+
511
+
512
+	/**
513
+	 * Sets total
514
+	 *
515
+	 * @param float $total
516
+	 * @throws EE_Error
517
+	 * @throws ReflectionException
518
+	 * @since  $VID:$
519
+	 */
520
+	public function setTotalWithTax(float $total)
521
+	{
522
+		$this->set('LIN_total', $total);
523
+	}
524
+
525
+
526
+	/**
527
+	 * Gets total
528
+	 *
529
+	 * @return float
530
+	 * @throws EE_Error
531
+	 * @throws ReflectionException
532
+	 * @deprecatd $VID:$
533
+	 */
534
+	public function total(): float
535
+	{
536
+		return $this->totalWithTax();
537
+	}
538
+
539
+
540
+	/**
541
+	 * Sets total
542
+	 *
543
+	 * @param float $total
544
+	 * @throws EE_Error
545
+	 * @throws ReflectionException
546
+	 * @deprecatd $VID:$
547
+	 */
548
+	public function set_total($total)
549
+	{
550
+		$this->setTotalWithTax($total);
551
+	}
552
+
553
+
554
+	/**
555
+	 * Gets order
556
+	 *
557
+	 * @return int
558
+	 * @throws EE_Error
559
+	 * @throws InvalidArgumentException
560
+	 * @throws InvalidDataTypeException
561
+	 * @throws InvalidInterfaceException
562
+	 * @throws ReflectionException
563
+	 */
564
+	public function order()
565
+	{
566
+		return $this->get('LIN_order');
567
+	}
568
+
569
+
570
+	/**
571
+	 * Sets order
572
+	 *
573
+	 * @param int $order
574
+	 * @throws EE_Error
575
+	 * @throws InvalidArgumentException
576
+	 * @throws InvalidDataTypeException
577
+	 * @throws InvalidInterfaceException
578
+	 * @throws ReflectionException
579
+	 */
580
+	public function set_order($order)
581
+	{
582
+		$this->set('LIN_order', $order);
583
+	}
584
+
585
+
586
+	/**
587
+	 * Gets parent
588
+	 *
589
+	 * @return int
590
+	 * @throws EE_Error
591
+	 * @throws InvalidArgumentException
592
+	 * @throws InvalidDataTypeException
593
+	 * @throws InvalidInterfaceException
594
+	 * @throws ReflectionException
595
+	 */
596
+	public function parent_ID()
597
+	{
598
+		return $this->get('LIN_parent');
599
+	}
600
+
601
+
602
+	/**
603
+	 * Sets parent
604
+	 *
605
+	 * @param int $parent
606
+	 * @throws EE_Error
607
+	 * @throws InvalidArgumentException
608
+	 * @throws InvalidDataTypeException
609
+	 * @throws InvalidInterfaceException
610
+	 * @throws ReflectionException
611
+	 */
612
+	public function set_parent_ID($parent)
613
+	{
614
+		$this->set('LIN_parent', $parent);
615
+	}
616
+
617
+
618
+	/**
619
+	 * Gets type
620
+	 *
621
+	 * @return string
622
+	 * @throws EE_Error
623
+	 * @throws InvalidArgumentException
624
+	 * @throws InvalidDataTypeException
625
+	 * @throws InvalidInterfaceException
626
+	 * @throws ReflectionException
627
+	 */
628
+	public function type()
629
+	{
630
+		return $this->get('LIN_type');
631
+	}
632
+
633
+
634
+	/**
635
+	 * Sets type
636
+	 *
637
+	 * @param string $type
638
+	 * @throws EE_Error
639
+	 * @throws InvalidArgumentException
640
+	 * @throws InvalidDataTypeException
641
+	 * @throws InvalidInterfaceException
642
+	 * @throws ReflectionException
643
+	 */
644
+	public function set_type($type)
645
+	{
646
+		$this->set('LIN_type', $type);
647
+	}
648
+
649
+
650
+	/**
651
+	 * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
652
+	 * 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
653
+	 * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
654
+	 * or indirectly by `EE_Line_item::add_child_line_item()`)
655
+	 *
656
+	 * @return EE_Base_Class|EE_Line_Item
657
+	 * @throws EE_Error
658
+	 * @throws InvalidArgumentException
659
+	 * @throws InvalidDataTypeException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws ReflectionException
662
+	 */
663
+	public function parent()
664
+	{
665
+		return $this->ID()
666
+			? $this->get_model()->get_one_by_ID($this->parent_ID())
667
+			: $this->_parent;
668
+	}
669
+
670
+
671
+	/**
672
+	 * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
673
+	 *
674
+	 * @return EE_Line_Item[]
675
+	 * @throws EE_Error
676
+	 * @throws InvalidArgumentException
677
+	 * @throws InvalidDataTypeException
678
+	 * @throws InvalidInterfaceException
679
+	 * @throws ReflectionException
680
+	 */
681
+	public function children(array $query_params = []): array
682
+	{
683
+		if ($this->ID()) {
684
+			// ensure where params are an array
685
+			$query_params[0] = $query_params[0] ?? [];
686
+			// add defaults for line item parent and orderby
687
+			$query_params[0] += ['LIN_parent' => $this->ID()];
688
+			$query_params += ['order_by' => ['LIN_order' => 'ASC']];
689
+			return $this->get_model()->get_all($query_params);
690
+		}
691
+		if (! is_array($this->_children)) {
692
+			$this->_children = array();
693
+		}
694
+		return $this->_children;
695
+	}
696
+
697
+
698
+	/**
699
+	 * Gets code
700
+	 *
701
+	 * @return string
702
+	 * @throws EE_Error
703
+	 * @throws InvalidArgumentException
704
+	 * @throws InvalidDataTypeException
705
+	 * @throws InvalidInterfaceException
706
+	 * @throws ReflectionException
707
+	 */
708
+	public function code()
709
+	{
710
+		return $this->get('LIN_code');
711
+	}
712
+
713
+
714
+	/**
715
+	 * Sets code
716
+	 *
717
+	 * @param string $code
718
+	 * @throws EE_Error
719
+	 * @throws InvalidArgumentException
720
+	 * @throws InvalidDataTypeException
721
+	 * @throws InvalidInterfaceException
722
+	 * @throws ReflectionException
723
+	 */
724
+	public function set_code($code)
725
+	{
726
+		$this->set('LIN_code', $code);
727
+	}
728
+
729
+
730
+	/**
731
+	 * Gets is_taxable
732
+	 *
733
+	 * @return boolean
734
+	 * @throws EE_Error
735
+	 * @throws InvalidArgumentException
736
+	 * @throws InvalidDataTypeException
737
+	 * @throws InvalidInterfaceException
738
+	 * @throws ReflectionException
739
+	 */
740
+	public function is_taxable()
741
+	{
742
+		return $this->get('LIN_is_taxable');
743
+	}
744
+
745
+
746
+	/**
747
+	 * Sets is_taxable
748
+	 *
749
+	 * @param boolean $is_taxable
750
+	 * @throws EE_Error
751
+	 * @throws InvalidArgumentException
752
+	 * @throws InvalidDataTypeException
753
+	 * @throws InvalidInterfaceException
754
+	 * @throws ReflectionException
755
+	 */
756
+	public function set_is_taxable($is_taxable)
757
+	{
758
+		$this->set('LIN_is_taxable', $is_taxable);
759
+	}
760
+
761
+
762
+	/**
763
+	 * Gets the object that this model-joins-to.
764
+	 * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
765
+	 * EEM_Promotion_Object
766
+	 *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
767
+	 *
768
+	 * @return EE_Base_Class | NULL
769
+	 * @throws EE_Error
770
+	 * @throws InvalidArgumentException
771
+	 * @throws InvalidDataTypeException
772
+	 * @throws InvalidInterfaceException
773
+	 * @throws ReflectionException
774
+	 */
775
+	public function get_object()
776
+	{
777
+		$model_name_of_related_obj = $this->OBJ_type();
778
+		return $this->get_model()->has_relation($model_name_of_related_obj)
779
+			? $this->get_first_related($model_name_of_related_obj)
780
+			: null;
781
+	}
782
+
783
+
784
+	/**
785
+	 * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
786
+	 * (IE, if this line item is for a price or something else, will return NULL)
787
+	 *
788
+	 * @param array $query_params
789
+	 * @return EE_Base_Class|EE_Ticket
790
+	 * @throws EE_Error
791
+	 * @throws InvalidArgumentException
792
+	 * @throws InvalidDataTypeException
793
+	 * @throws InvalidInterfaceException
794
+	 * @throws ReflectionException
795
+	 */
796
+	public function ticket($query_params = array())
797
+	{
798
+		// we're going to assume that when this method is called
799
+		// we always want to receive the attached ticket EVEN if that ticket is archived.
800
+		// This can be overridden via the incoming $query_params argument
801
+		$remove_defaults = array('default_where_conditions' => 'none');
802
+		$query_params = array_merge($remove_defaults, $query_params);
803
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
804
+	}
805
+
806
+
807
+	/**
808
+	 * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
809
+	 *
810
+	 * @return EE_Datetime | NULL
811
+	 * @throws EE_Error
812
+	 * @throws InvalidArgumentException
813
+	 * @throws InvalidDataTypeException
814
+	 * @throws InvalidInterfaceException
815
+	 * @throws ReflectionException
816
+	 */
817
+	public function get_ticket_datetime()
818
+	{
819
+		if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
820
+			$ticket = $this->ticket();
821
+			if ($ticket instanceof EE_Ticket) {
822
+				$datetime = $ticket->first_datetime();
823
+				if ($datetime instanceof EE_Datetime) {
824
+					return $datetime;
825
+				}
826
+			}
827
+		}
828
+		return null;
829
+	}
830
+
831
+
832
+	/**
833
+	 * Gets the event's name that's related to the ticket, if this is for
834
+	 * a ticket
835
+	 *
836
+	 * @return string
837
+	 * @throws EE_Error
838
+	 * @throws InvalidArgumentException
839
+	 * @throws InvalidDataTypeException
840
+	 * @throws InvalidInterfaceException
841
+	 * @throws ReflectionException
842
+	 */
843
+	public function ticket_event_name()
844
+	{
845
+		$event_name = esc_html__('Unknown', 'event_espresso');
846
+		$event = $this->ticket_event();
847
+		if ($event instanceof EE_Event) {
848
+			$event_name = $event->name();
849
+		}
850
+		return $event_name;
851
+	}
852
+
853
+
854
+	/**
855
+	 * Gets the event that's related to the ticket, if this line item represents a ticket.
856
+	 *
857
+	 * @return EE_Event|null
858
+	 * @throws EE_Error
859
+	 * @throws InvalidArgumentException
860
+	 * @throws InvalidDataTypeException
861
+	 * @throws InvalidInterfaceException
862
+	 * @throws ReflectionException
863
+	 */
864
+	public function ticket_event()
865
+	{
866
+		$event = null;
867
+		$ticket = $this->ticket();
868
+		if ($ticket instanceof EE_Ticket) {
869
+			$datetime = $ticket->first_datetime();
870
+			if ($datetime instanceof EE_Datetime) {
871
+				$event = $datetime->event();
872
+			}
873
+		}
874
+		return $event;
875
+	}
876
+
877
+
878
+	/**
879
+	 * Gets the first datetime for this lien item, assuming it's for a ticket
880
+	 *
881
+	 * @param string $date_format
882
+	 * @param string $time_format
883
+	 * @return string
884
+	 * @throws EE_Error
885
+	 * @throws InvalidArgumentException
886
+	 * @throws InvalidDataTypeException
887
+	 * @throws InvalidInterfaceException
888
+	 * @throws ReflectionException
889
+	 */
890
+	public function ticket_datetime_start($date_format = '', $time_format = '')
891
+	{
892
+		$first_datetime_string = esc_html__('Unknown', 'event_espresso');
893
+		$datetime = $this->get_ticket_datetime();
894
+		if ($datetime) {
895
+			$first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
896
+		}
897
+		return $first_datetime_string;
898
+	}
899
+
900
+
901
+	/**
902
+	 * Adds the line item as a child to this line item. If there is another child line
903
+	 * item with the same LIN_code, it is overwritten by this new one
904
+	 *
905
+	 * @param EE_Line_Item $line_item
906
+	 * @param bool          $set_order
907
+	 * @return bool success
908
+	 * @throws EE_Error
909
+	 * @throws InvalidArgumentException
910
+	 * @throws InvalidDataTypeException
911
+	 * @throws InvalidInterfaceException
912
+	 * @throws ReflectionException
913
+	 */
914
+	public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
915
+	{
916
+		// should we calculate the LIN_order for this line item ?
917
+		if ($set_order || $line_item->order() === null) {
918
+			$line_item->set_order(count($this->children()));
919
+		}
920
+		if ($this->ID()) {
921
+			// check for any duplicate line items (with the same code), if so, this replaces it
922
+			$line_item_with_same_code = $this->get_child_line_item($line_item->code());
923
+			if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
924
+				$this->delete_child_line_item($line_item_with_same_code->code());
925
+			}
926
+			$line_item->set_parent_ID($this->ID());
927
+			if ($this->TXN_ID()) {
928
+				$line_item->set_TXN_ID($this->TXN_ID());
929
+			}
930
+			return $line_item->save();
931
+		}
932
+		$this->_children[ $line_item->code() ] = $line_item;
933
+		if ($line_item->parent() !== $this) {
934
+			$line_item->set_parent($this);
935
+		}
936
+		return true;
937
+	}
938
+
939
+
940
+	/**
941
+	 * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
942
+	 * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
943
+	 * However, if this line item is NOT saved to the DB, this just caches the parent on
944
+	 * the EE_Line_Item::_parent property.
945
+	 *
946
+	 * @param EE_Line_Item $line_item
947
+	 * @throws EE_Error
948
+	 * @throws InvalidArgumentException
949
+	 * @throws InvalidDataTypeException
950
+	 * @throws InvalidInterfaceException
951
+	 * @throws ReflectionException
952
+	 */
953
+	public function set_parent($line_item)
954
+	{
955
+		if ($this->ID()) {
956
+			if (! $line_item->ID()) {
957
+				$line_item->save();
958
+			}
959
+			$this->set_parent_ID($line_item->ID());
960
+			$this->save();
961
+		} else {
962
+			$this->_parent = $line_item;
963
+			$this->set_parent_ID($line_item->ID());
964
+		}
965
+	}
966
+
967
+
968
+	/**
969
+	 * Gets the child line item as specified by its code. Because this returns an object (by reference)
970
+	 * you can modify this child line item and the parent (this object) can know about them
971
+	 * because it also has a reference to that line item
972
+	 *
973
+	 * @param string $code
974
+	 * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
975
+	 * @throws EE_Error
976
+	 * @throws InvalidArgumentException
977
+	 * @throws InvalidDataTypeException
978
+	 * @throws InvalidInterfaceException
979
+	 * @throws ReflectionException
980
+	 */
981
+	public function get_child_line_item($code)
982
+	{
983
+		if ($this->ID()) {
984
+			return $this->get_model()->get_one(
985
+				array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
986
+			);
987
+		}
988
+		return isset($this->_children[ $code ])
989
+			? $this->_children[ $code ]
990
+			: null;
991
+	}
992
+
993
+
994
+	/**
995
+	 * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
996
+	 * cached on it)
997
+	 *
998
+	 * @return int
999
+	 * @throws EE_Error
1000
+	 * @throws InvalidArgumentException
1001
+	 * @throws InvalidDataTypeException
1002
+	 * @throws InvalidInterfaceException
1003
+	 * @throws ReflectionException
1004
+	 */
1005
+	public function delete_children_line_items()
1006
+	{
1007
+		if ($this->ID()) {
1008
+			return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
1009
+		}
1010
+		$count = count($this->_children);
1011
+		$this->_children = array();
1012
+		return $count;
1013
+	}
1014
+
1015
+
1016
+	/**
1017
+	 * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
1018
+	 * HAS NOT been saved to the DB, removes the child line item with index $code.
1019
+	 * Also searches through the child's children for a matching line item. However, once a line item has been found
1020
+	 * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
1021
+	 * deleted)
1022
+	 *
1023
+	 * @param string $code
1024
+	 * @param bool   $stop_search_once_found
1025
+	 * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
1026
+	 *             the DB yet)
1027
+	 * @throws EE_Error
1028
+	 * @throws InvalidArgumentException
1029
+	 * @throws InvalidDataTypeException
1030
+	 * @throws InvalidInterfaceException
1031
+	 * @throws ReflectionException
1032
+	 */
1033
+	public function delete_child_line_item($code, $stop_search_once_found = true)
1034
+	{
1035
+		if ($this->ID()) {
1036
+			$items_deleted = 0;
1037
+			if ($this->code() === $code) {
1038
+				$items_deleted += EEH_Line_Item::delete_all_child_items($this);
1039
+				$items_deleted += (int) $this->delete();
1040
+				if ($stop_search_once_found) {
1041
+					return $items_deleted;
1042
+				}
1043
+			}
1044
+			foreach ($this->children() as $child_line_item) {
1045
+				$items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1046
+			}
1047
+			return $items_deleted;
1048
+		}
1049
+		if (isset($this->_children[ $code ])) {
1050
+			unset($this->_children[ $code ]);
1051
+			return 1;
1052
+		}
1053
+		return 0;
1054
+	}
1055
+
1056
+
1057
+	/**
1058
+	 * If this line item is in the database, is of the type subtotal, and
1059
+	 * has no children, why do we have it? It should be deleted so this function
1060
+	 * does that
1061
+	 *
1062
+	 * @return boolean
1063
+	 * @throws EE_Error
1064
+	 * @throws InvalidArgumentException
1065
+	 * @throws InvalidDataTypeException
1066
+	 * @throws InvalidInterfaceException
1067
+	 * @throws ReflectionException
1068
+	 */
1069
+	public function delete_if_childless_subtotal()
1070
+	{
1071
+		if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1072
+			return $this->delete();
1073
+		}
1074
+		return false;
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * Creates a code and returns a string. doesn't assign the code to this model object
1080
+	 *
1081
+	 * @return string
1082
+	 * @throws EE_Error
1083
+	 * @throws InvalidArgumentException
1084
+	 * @throws InvalidDataTypeException
1085
+	 * @throws InvalidInterfaceException
1086
+	 * @throws ReflectionException
1087
+	 */
1088
+	public function generate_code()
1089
+	{
1090
+		// each line item in the cart requires a unique identifier
1091
+		return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1092
+	}
1093
+
1094
+
1095
+	/**
1096
+	 * @return bool
1097
+	 * @throws EE_Error
1098
+	 * @throws InvalidArgumentException
1099
+	 * @throws InvalidDataTypeException
1100
+	 * @throws InvalidInterfaceException
1101
+	 * @throws ReflectionException
1102
+	 */
1103
+	public function isGlobalTax(): bool
1104
+	{
1105
+		return $this->type() === EEM_Line_Item::type_tax;
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * @return bool
1111
+	 * @throws EE_Error
1112
+	 * @throws InvalidArgumentException
1113
+	 * @throws InvalidDataTypeException
1114
+	 * @throws InvalidInterfaceException
1115
+	 * @throws ReflectionException
1116
+	 */
1117
+	public function isSubTax(): bool
1118
+	{
1119
+		return $this->type() === EEM_Line_Item::type_sub_tax;
1120
+	}
1121
+
1122
+
1123
+	/**
1124
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1125
+	 *
1126
+	 * @return array
1127
+	 * @throws EE_Error
1128
+	 * @throws InvalidArgumentException
1129
+	 * @throws InvalidDataTypeException
1130
+	 * @throws InvalidInterfaceException
1131
+	 * @throws ReflectionException
1132
+	 */
1133
+	public function getSubTaxes(): array
1134
+	{
1135
+		if (! $this->is_line_item()) {
1136
+			return [];
1137
+		}
1138
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1139
+	}
1140
+
1141
+
1142
+	/**
1143
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1144
+	 *
1145
+	 * @return bool
1146
+	 * @throws EE_Error
1147
+	 * @throws InvalidArgumentException
1148
+	 * @throws InvalidDataTypeException
1149
+	 * @throws InvalidInterfaceException
1150
+	 * @throws ReflectionException
1151
+	 */
1152
+	public function hasSubTaxes(): bool
1153
+	{
1154
+		if (! $this->is_line_item()) {
1155
+			return false;
1156
+		}
1157
+		$sub_taxes = $this->getSubTaxes();
1158
+		return ! empty($sub_taxes);
1159
+	}
1160
+
1161
+
1162
+	/**
1163
+	 * @return bool
1164
+	 * @throws EE_Error
1165
+	 * @throws ReflectionException
1166
+	 * @deprecated   $VID:$
1167
+	 */
1168
+	public function is_tax(): bool
1169
+	{
1170
+		return $this->isGlobalTax();
1171
+	}
1172
+
1173
+
1174
+	/**
1175
+	 * @return bool
1176
+	 * @throws EE_Error
1177
+	 * @throws InvalidArgumentException
1178
+	 * @throws InvalidDataTypeException
1179
+	 * @throws InvalidInterfaceException
1180
+	 * @throws ReflectionException
1181
+	 */
1182
+	public function is_tax_sub_total()
1183
+	{
1184
+		return $this->type() === EEM_Line_Item::type_tax_sub_total;
1185
+	}
1186
+
1187
+
1188
+	/**
1189
+	 * @return bool
1190
+	 * @throws EE_Error
1191
+	 * @throws InvalidArgumentException
1192
+	 * @throws InvalidDataTypeException
1193
+	 * @throws InvalidInterfaceException
1194
+	 * @throws ReflectionException
1195
+	 */
1196
+	public function is_line_item()
1197
+	{
1198
+		return $this->type() === EEM_Line_Item::type_line_item;
1199
+	}
1200
+
1201
+
1202
+	/**
1203
+	 * @return bool
1204
+	 * @throws EE_Error
1205
+	 * @throws InvalidArgumentException
1206
+	 * @throws InvalidDataTypeException
1207
+	 * @throws InvalidInterfaceException
1208
+	 * @throws ReflectionException
1209
+	 */
1210
+	public function is_sub_line_item()
1211
+	{
1212
+		return $this->type() === EEM_Line_Item::type_sub_line_item;
1213
+	}
1214
+
1215
+
1216
+	/**
1217
+	 * @return bool
1218
+	 * @throws EE_Error
1219
+	 * @throws InvalidArgumentException
1220
+	 * @throws InvalidDataTypeException
1221
+	 * @throws InvalidInterfaceException
1222
+	 * @throws ReflectionException
1223
+	 */
1224
+	public function is_sub_total()
1225
+	{
1226
+		return $this->type() === EEM_Line_Item::type_sub_total;
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Whether or not this line item is a cancellation line item
1232
+	 *
1233
+	 * @return boolean
1234
+	 * @throws EE_Error
1235
+	 * @throws InvalidArgumentException
1236
+	 * @throws InvalidDataTypeException
1237
+	 * @throws InvalidInterfaceException
1238
+	 * @throws ReflectionException
1239
+	 */
1240
+	public function is_cancellation()
1241
+	{
1242
+		return EEM_Line_Item::type_cancellation === $this->type();
1243
+	}
1244
+
1245
+
1246
+	/**
1247
+	 * @return bool
1248
+	 * @throws EE_Error
1249
+	 * @throws InvalidArgumentException
1250
+	 * @throws InvalidDataTypeException
1251
+	 * @throws InvalidInterfaceException
1252
+	 * @throws ReflectionException
1253
+	 */
1254
+	public function is_total()
1255
+	{
1256
+		return $this->type() === EEM_Line_Item::type_total;
1257
+	}
1258
+
1259
+
1260
+	/**
1261
+	 * @return bool
1262
+	 * @throws EE_Error
1263
+	 * @throws InvalidArgumentException
1264
+	 * @throws InvalidDataTypeException
1265
+	 * @throws InvalidInterfaceException
1266
+	 * @throws ReflectionException
1267
+	 */
1268
+	public function is_cancelled()
1269
+	{
1270
+		return $this->type() === EEM_Line_Item::type_cancellation;
1271
+	}
1272
+
1273
+
1274
+	/**
1275
+	 * @return string like '2, 004.00', formatted according to the localized currency
1276
+	 * @throws EE_Error
1277
+	 * @throws ReflectionException
1278
+	 */
1279
+	public function unit_price_no_code(): string
1280
+	{
1281
+		return $this->prettyUnitPrice();
1282
+	}
1283
+
1284
+
1285
+	/**
1286
+	 * @return string like '2, 004.00', formatted according to the localized currency
1287
+	 * @throws EE_Error
1288
+	 * @throws ReflectionException
1289
+	 * @since $VID:$
1290
+	 */
1291
+	public function prettyUnitPrice(): string
1292
+	{
1293
+		return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1294
+	}
1295
+
1296
+
1297
+	/**
1298
+	 * @return string like '2, 004.00', formatted according to the localized currency
1299
+	 * @throws EE_Error
1300
+	 * @throws ReflectionException
1301
+	 */
1302
+	public function total_no_code(): string
1303
+	{
1304
+		return $this->prettyTotal();
1305
+	}
1306
+
1307
+
1308
+	/**
1309
+	 * @return string like '2, 004.00', formatted according to the localized currency
1310
+	 * @throws EE_Error
1311
+	 * @throws ReflectionException
1312
+	 * @since $VID:$
1313
+	 */
1314
+	public function prettyTotal(): string
1315
+	{
1316
+		return $this->get_pretty('LIN_total', 'no_currency_code');
1317
+	}
1318
+
1319
+
1320
+	/**
1321
+	 * Gets the final total on this item, taking taxes into account.
1322
+	 * Has the side-effect of setting the sub-total as it was just calculated.
1323
+	 * If this is used on a grand-total line item, also updates the transaction's
1324
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
1325
+	 * want to change a persistable transaction with info from a non-persistent line item)
1326
+	 *
1327
+	 * @param bool $update_txn_status
1328
+	 * @return float
1329
+	 * @throws EE_Error
1330
+	 * @throws InvalidArgumentException
1331
+	 * @throws InvalidDataTypeException
1332
+	 * @throws InvalidInterfaceException
1333
+	 * @throws ReflectionException
1334
+	 * @throws RuntimeException
1335
+	 */
1336
+	public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1337
+	{
1338
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1339
+		return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1340
+	}
1341
+
1342
+
1343
+	/**
1344
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1345
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1346
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1347
+	 * when this is called on the grand total
1348
+	 *
1349
+	 * @return float
1350
+	 * @throws EE_Error
1351
+	 * @throws InvalidArgumentException
1352
+	 * @throws InvalidDataTypeException
1353
+	 * @throws InvalidInterfaceException
1354
+	 * @throws ReflectionException
1355
+	 */
1356
+	public function recalculate_pre_tax_total(): float
1357
+	{
1358
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1359
+		[$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1360
+		return $total;
1361
+	}
1362
+
1363
+
1364
+	/**
1365
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1366
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1367
+	 * and tax sub-total if already in the DB
1368
+	 *
1369
+	 * @return float
1370
+	 * @throws EE_Error
1371
+	 * @throws InvalidArgumentException
1372
+	 * @throws InvalidDataTypeException
1373
+	 * @throws InvalidInterfaceException
1374
+	 * @throws ReflectionException
1375
+	 */
1376
+	public function recalculate_taxes_and_tax_total(): float
1377
+	{
1378
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1379
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1380
+	}
1381
+
1382
+
1383
+	/**
1384
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
1385
+	 * recalculate_taxes_and_total
1386
+	 *
1387
+	 * @return float
1388
+	 * @throws EE_Error
1389
+	 * @throws InvalidArgumentException
1390
+	 * @throws InvalidDataTypeException
1391
+	 * @throws InvalidInterfaceException
1392
+	 * @throws ReflectionException
1393
+	 */
1394
+	public function get_total_tax()
1395
+	{
1396
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1397
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1398
+	}
1399
+
1400
+
1401
+	/**
1402
+	 * Gets the total for all the items purchased only
1403
+	 *
1404
+	 * @return float
1405
+	 * @throws EE_Error
1406
+	 * @throws InvalidArgumentException
1407
+	 * @throws InvalidDataTypeException
1408
+	 * @throws InvalidInterfaceException
1409
+	 * @throws ReflectionException
1410
+	 */
1411
+	public function get_items_total()
1412
+	{
1413
+		// by default, let's make sure we're consistent with the existing line item
1414
+		if ($this->is_total()) {
1415
+			return $this->pretaxTotal();
1416
+		}
1417
+		$total = 0;
1418
+		foreach ($this->get_items() as $item) {
1419
+			if ($item instanceof EE_Line_Item) {
1420
+				$total += $item->pretaxTotal();
1421
+			}
1422
+		}
1423
+		return $total;
1424
+	}
1425
+
1426
+
1427
+	/**
1428
+	 * Gets all the descendants (ie, children or children of children etc) that
1429
+	 * are of the type 'tax'
1430
+	 *
1431
+	 * @return EE_Line_Item[]
1432
+	 * @throws EE_Error
1433
+	 */
1434
+	public function tax_descendants()
1435
+	{
1436
+		return EEH_Line_Item::get_tax_descendants($this);
1437
+	}
1438
+
1439
+
1440
+	/**
1441
+	 * Gets all the real items purchased which are children of this item
1442
+	 *
1443
+	 * @return EE_Line_Item[]
1444
+	 * @throws EE_Error
1445
+	 */
1446
+	public function get_items()
1447
+	{
1448
+		return EEH_Line_Item::get_line_item_descendants($this);
1449
+	}
1450
+
1451
+
1452
+	/**
1453
+	 * Returns the amount taxable among this line item's children (or if it has no children,
1454
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
1455
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
1456
+	 * but there is a "Taxable" discount), returns 0.
1457
+	 *
1458
+	 * @return float
1459
+	 * @throws EE_Error
1460
+	 * @throws InvalidArgumentException
1461
+	 * @throws InvalidDataTypeException
1462
+	 * @throws InvalidInterfaceException
1463
+	 * @throws ReflectionException
1464
+	 */
1465
+	public function taxable_total(): float
1466
+	{
1467
+		return $this->calculator->taxableAmountForGlobalTaxes($this);
1468
+	}
1469
+
1470
+
1471
+	/**
1472
+	 * Gets the transaction for this line item
1473
+	 *
1474
+	 * @return EE_Base_Class|EE_Transaction
1475
+	 * @throws EE_Error
1476
+	 * @throws InvalidArgumentException
1477
+	 * @throws InvalidDataTypeException
1478
+	 * @throws InvalidInterfaceException
1479
+	 * @throws ReflectionException
1480
+	 */
1481
+	public function transaction()
1482
+	{
1483
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1484
+	}
1485
+
1486
+
1487
+	/**
1488
+	 * Saves this line item to the DB, and recursively saves its descendants.
1489
+	 * Because there currently is no proper parent-child relation on the model,
1490
+	 * save_this_and_cached() will NOT save the descendants.
1491
+	 * Also sets the transaction on this line item and all its descendants before saving
1492
+	 *
1493
+	 * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1494
+	 * @return int count of items saved
1495
+	 * @throws EE_Error
1496
+	 * @throws InvalidArgumentException
1497
+	 * @throws InvalidDataTypeException
1498
+	 * @throws InvalidInterfaceException
1499
+	 * @throws ReflectionException
1500
+	 */
1501
+	public function save_this_and_descendants_to_txn($txn_id = null)
1502
+	{
1503
+		$count = 0;
1504
+		if (! $txn_id) {
1505
+			$txn_id = $this->TXN_ID();
1506
+		}
1507
+		$this->set_TXN_ID($txn_id);
1508
+		$children = $this->children();
1509
+		$count += $this->save()
1510
+			? 1
1511
+			: 0;
1512
+		foreach ($children as $child_line_item) {
1513
+			if ($child_line_item instanceof EE_Line_Item) {
1514
+				$child_line_item->set_parent_ID($this->ID());
1515
+				$count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1516
+			}
1517
+		}
1518
+		return $count;
1519
+	}
1520
+
1521
+
1522
+	/**
1523
+	 * Saves this line item to the DB, and recursively saves its descendants.
1524
+	 *
1525
+	 * @return int count of items saved
1526
+	 * @throws EE_Error
1527
+	 * @throws InvalidArgumentException
1528
+	 * @throws InvalidDataTypeException
1529
+	 * @throws InvalidInterfaceException
1530
+	 * @throws ReflectionException
1531
+	 */
1532
+	public function save_this_and_descendants()
1533
+	{
1534
+		$count = 0;
1535
+		$children = $this->children();
1536
+		$count += $this->save()
1537
+			? 1
1538
+			: 0;
1539
+		foreach ($children as $child_line_item) {
1540
+			if ($child_line_item instanceof EE_Line_Item) {
1541
+				$child_line_item->set_parent_ID($this->ID());
1542
+				$count += $child_line_item->save_this_and_descendants();
1543
+			}
1544
+		}
1545
+		return $count;
1546
+	}
1547
+
1548
+
1549
+	/**
1550
+	 * returns the cancellation line item if this item was cancelled
1551
+	 *
1552
+	 * @return EE_Line_Item[]
1553
+	 * @throws InvalidArgumentException
1554
+	 * @throws InvalidInterfaceException
1555
+	 * @throws InvalidDataTypeException
1556
+	 * @throws ReflectionException
1557
+	 * @throws EE_Error
1558
+	 */
1559
+	public function get_cancellations()
1560
+	{
1561
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1562
+	}
1563
+
1564
+
1565
+	/**
1566
+	 * If this item has an ID, then this saves it again to update the db
1567
+	 *
1568
+	 * @return int count of items saved
1569
+	 * @throws EE_Error
1570
+	 * @throws InvalidArgumentException
1571
+	 * @throws InvalidDataTypeException
1572
+	 * @throws InvalidInterfaceException
1573
+	 * @throws ReflectionException
1574
+	 */
1575
+	public function maybe_save()
1576
+	{
1577
+		if ($this->ID()) {
1578
+			return $this->save();
1579
+		}
1580
+		return false;
1581
+	}
1582
+
1583
+
1584
+	/**
1585
+	 * clears the cached children and parent from the line item
1586
+	 *
1587
+	 * @return void
1588
+	 */
1589
+	public function clear_related_line_item_cache()
1590
+	{
1591
+		$this->_children = array();
1592
+		$this->_parent = null;
1593
+	}
1594
+
1595
+
1596
+	/**
1597
+	 * @param bool $raw
1598
+	 * @return int
1599
+	 * @throws EE_Error
1600
+	 * @throws InvalidArgumentException
1601
+	 * @throws InvalidDataTypeException
1602
+	 * @throws InvalidInterfaceException
1603
+	 * @throws ReflectionException
1604
+	 */
1605
+	public function timestamp($raw = false)
1606
+	{
1607
+		return $raw
1608
+			? $this->get_raw('LIN_timestamp')
1609
+			: $this->get('LIN_timestamp');
1610
+	}
1611
+
1612
+
1613
+
1614
+
1615
+	/************************* DEPRECATED *************************/
1616
+	/**
1617
+	 * @deprecated 4.6.0
1618
+	 * @param string $type one of the constants on EEM_Line_Item
1619
+	 * @return EE_Line_Item[]
1620
+	 * @throws EE_Error
1621
+	 */
1622
+	protected function _get_descendants_of_type($type)
1623
+	{
1624
+		EE_Error::doing_it_wrong(
1625
+			'EE_Line_Item::_get_descendants_of_type()',
1626
+			sprintf(
1627
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1628
+				'EEH_Line_Item::get_descendants_of_type()'
1629
+			),
1630
+			'4.6.0'
1631
+		);
1632
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1633
+	}
1634
+
1635
+
1636
+	/**
1637
+	 * @deprecated 4.6.0
1638
+	 * @param string $type like one of the EEM_Line_Item::type_*
1639
+	 * @return EE_Line_Item
1640
+	 * @throws EE_Error
1641
+	 * @throws InvalidArgumentException
1642
+	 * @throws InvalidDataTypeException
1643
+	 * @throws InvalidInterfaceException
1644
+	 * @throws ReflectionException
1645
+	 */
1646
+	public function get_nearest_descendant_of_type(string $type): EE_Line_Item
1647
+	{
1648
+		EE_Error::doing_it_wrong(
1649
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1650
+			sprintf(
1651
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1652
+				'EEH_Line_Item::get_nearest_descendant_of_type()'
1653
+			),
1654
+			'4.6.0'
1655
+		);
1656
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1657
+	}
1658 1658
 }
Please login to merge, or discard this patch.