Completed
Branch FET-10766-extract-activation-d... (a650cc)
by
unknown
116:57 queued 106:13
created

EE_Line_Item::name()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
6
defined('EVENT_ESPRESSO_VERSION') || exit('No direct script access allowed');
7
8
9
10
/**
11
 * EE_Line_Item class
12
 * see EEM_Line_Item for description
13
 *
14
 * @package            Event Espresso
15
 * @subpackage         includes/classes/EE_Line_Item.class.php
16
 * @author             Michael Nelson
17
 */
18
class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
19
{
20
21
    /**
22
     * for children line items (currently not a normal relation)
23
     *
24
     * @type EE_Line_Item[]
25
     */
26
    protected $_children = array();
27
28
    /**
29
     * for the parent line item
30
     *
31
     * @var EE_Line_Item
32
     */
33
    protected $_parent;
34
35
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
     */
47 View Code Duplication
    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
48
    {
49
        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
50
        return $has_object
51
            ? $has_object
52
            : new self($props_n_values, false, $timezone);
53
    }
54
55
56
57
    /**
58
     * @param array  $props_n_values  incoming values from the database
59
     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
     *                                the website will be used.
61
     * @return EE_Line_Item
62
     * @throws EE_Error
63
     */
64
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
65
    {
66
        return new self($props_n_values, true, $timezone);
67
    }
68
69
70
71
    /**
72
     * Adds some defaults if they're not specified
73
     *
74
     * @param array  $fieldValues
75
     * @param bool   $bydb
76
     * @param string $timezone
77
     * @throws EE_Error
78
     */
79
    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
80
    {
81
        parent::__construct($fieldValues, $bydb, $timezone);
82
        if (! $this->get('LIN_code')) {
83
            $this->set_code($this->generate_code());
84
        }
85
    }
86
87
88
89
    /**
90
     * Gets ID
91
     *
92
     * @return int
93
     * @throws EE_Error
94
     */
95
    public function ID()
96
    {
97
        return $this->get('LIN_ID');
98
    }
99
100
101
102
    /**
103
     * Gets TXN_ID
104
     *
105
     * @return int
106
     * @throws EE_Error
107
     */
108
    public function TXN_ID()
109
    {
110
        return $this->get('TXN_ID');
111
    }
112
113
114
115
    /**
116
     * Sets TXN_ID
117
     *
118
     * @param int $TXN_ID
119
     * @throws EE_Error
120
     */
121
    public function set_TXN_ID($TXN_ID)
122
    {
123
        $this->set('TXN_ID', $TXN_ID);
124
    }
125
126
127
128
    /**
129
     * Gets name
130
     *
131
     * @return string
132
     * @throws EE_Error
133
     */
134
    public function name()
135
    {
136
        $name = $this->get('LIN_name');
137
        if (! $name) {
138
            $name = ucwords(str_replace('-', ' ', $this->type()));
139
        }
140
        return $name;
141
    }
142
143
144
145
    /**
146
     * Sets name
147
     *
148
     * @param string $name
149
     * @throws EE_Error
150
     */
151
    public function set_name($name)
152
    {
153
        $this->set('LIN_name', $name);
154
    }
155
156
157
158
    /**
159
     * Gets desc
160
     *
161
     * @return string
162
     * @throws EE_Error
163
     */
164
    public function desc()
165
    {
166
        return $this->get('LIN_desc');
167
    }
168
169
170
171
    /**
172
     * Sets desc
173
     *
174
     * @param string $desc
175
     * @throws EE_Error
176
     */
177
    public function set_desc($desc)
178
    {
179
        $this->set('LIN_desc', $desc);
180
    }
181
182
183
184
    /**
185
     * Gets quantity
186
     *
187
     * @return int
188
     * @throws EE_Error
189
     */
190
    public function quantity()
191
    {
192
        return $this->get('LIN_quantity');
193
    }
194
195
196
197
    /**
198
     * Sets quantity
199
     *
200
     * @param int $quantity
201
     * @throws EE_Error
202
     */
203
    public function set_quantity($quantity)
204
    {
205
        $this->set('LIN_quantity', max($quantity, 0));
206
    }
207
208
209
210
    /**
211
     * Gets item_id
212
     *
213
     * @return string
214
     * @throws EE_Error
215
     */
216
    public function OBJ_ID()
217
    {
218
        return $this->get('OBJ_ID');
219
    }
220
221
222
223
    /**
224
     * Sets item_id
225
     *
226
     * @param string $item_id
227
     * @throws EE_Error
228
     */
229
    public function set_OBJ_ID($item_id)
230
    {
231
        $this->set('OBJ_ID', $item_id);
232
    }
233
234
235
236
    /**
237
     * Gets item_type
238
     *
239
     * @return string
240
     * @throws EE_Error
241
     */
242
    public function OBJ_type()
243
    {
244
        return $this->get('OBJ_type');
245
    }
246
247
248
249
    /**
250
     * Gets item_type
251
     *
252
     * @return string
253
     * @throws EE_Error
254
     */
255
    public function OBJ_type_i18n()
256
    {
257
        $obj_type = $this->OBJ_type();
258
        switch ($obj_type) {
259
            case 'Event':
260
                $obj_type = __('Event', 'event_espresso');
261
                break;
262
            case 'Price':
263
                $obj_type = __('Price', 'event_espresso');
264
                break;
265
            case 'Promotion':
266
                $obj_type = __('Promotion', 'event_espresso');
267
                break;
268
            case 'Ticket':
269
                $obj_type = __('Ticket', 'event_espresso');
270
                break;
271
            case 'Transaction':
272
                $obj_type = __('Transaction', 'event_espresso');
273
                break;
274
        }
275
        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
276
    }
277
278
279
280
    /**
281
     * Sets item_type
282
     *
283
     * @param string $OBJ_type
284
     * @throws EE_Error
285
     */
286
    public function set_OBJ_type($OBJ_type)
287
    {
288
        $this->set('OBJ_type', $OBJ_type);
289
    }
290
291
292
293
    /**
294
     * Gets unit_price
295
     *
296
     * @return float
297
     * @throws EE_Error
298
     */
299
    public function unit_price()
300
    {
301
        return $this->get('LIN_unit_price');
302
    }
303
304
305
306
    /**
307
     * Sets unit_price
308
     *
309
     * @param float $unit_price
310
     * @throws EE_Error
311
     */
312
    public function set_unit_price($unit_price)
313
    {
314
        $this->set('LIN_unit_price', $unit_price);
315
    }
316
317
318
319
    /**
320
     * Checks if this item is a percentage modifier or not
321
     *
322
     * @return boolean
323
     * @throws EE_Error
324
     */
325
    public function is_percent()
326
    {
327
        if ($this->is_tax_sub_total()) {
328
            //tax subtotals HAVE a percent on them, that percentage only applies
329
            //to taxable items, so its' an exception. Treat it like a flat line item
330
            return false;
331
        }
332
        $unit_price = abs($this->get('LIN_unit_price'));
333
        $percent = abs($this->get('LIN_percent'));
334
        if ($unit_price < .001 && $percent) {
335
            return true;
336
        }
337
        if ($unit_price >= .001 && ! $percent) {
338
            return false;
339
        }
340
        if ($unit_price >= .001 && $percent) {
341
            throw new EE_Error(
342
                sprintf(
343
                    esc_html__('A Line Item can not have a unit price of (%s) AND a percent (%s)!', 'event_espresso'),
344
                    $unit_price, $percent
345
                )
346
            );
347
        }
348
        // if they're both 0, assume its not a percent item
349
        return false;
350
    }
351
352
353
354
    /**
355
     * Gets percent (between 100-.001)
356
     *
357
     * @return float
358
     * @throws EE_Error
359
     */
360
    public function percent()
361
    {
362
        return $this->get('LIN_percent');
363
    }
364
365
366
367
    /**
368
     * Sets percent (between 100-0.01)
369
     *
370
     * @param float $percent
371
     * @throws EE_Error
372
     */
373
    public function set_percent($percent)
374
    {
375
        $this->set('LIN_percent', $percent);
376
    }
377
378
379
380
    /**
381
     * Gets total
382
     *
383
     * @return float
384
     * @throws EE_Error
385
     */
386
    public function total()
387
    {
388
        return $this->get('LIN_total');
389
    }
390
391
392
393
    /**
394
     * Sets total
395
     *
396
     * @param float $total
397
     * @throws EE_Error
398
     */
399
    public function set_total($total)
400
    {
401
        $this->set('LIN_total', $total);
402
    }
403
404
405
406
    /**
407
     * Gets order
408
     *
409
     * @return int
410
     * @throws EE_Error
411
     */
412
    public function order()
413
    {
414
        return $this->get('LIN_order');
415
    }
416
417
418
419
    /**
420
     * Sets order
421
     *
422
     * @param int $order
423
     * @throws EE_Error
424
     */
425
    public function set_order($order)
426
    {
427
        $this->set('LIN_order', $order);
428
    }
429
430
431
432
    /**
433
     * Gets parent
434
     *
435
     * @return int
436
     * @throws EE_Error
437
     */
438
    public function parent_ID()
439
    {
440
        return $this->get('LIN_parent');
441
    }
442
443
444
445
    /**
446
     * Sets parent
447
     *
448
     * @param int $parent
449
     * @throws EE_Error
450
     */
451
    public function set_parent_ID($parent)
452
    {
453
        $this->set('LIN_parent', $parent);
454
    }
455
456
457
458
    /**
459
     * Gets type
460
     *
461
     * @return string
462
     * @throws EE_Error
463
     */
464
    public function type()
465
    {
466
        return $this->get('LIN_type');
467
    }
468
469
470
471
    /**
472
     * Sets type
473
     *
474
     * @param string $type
475
     * @throws EE_Error
476
     */
477
    public function set_type($type)
478
    {
479
        $this->set('LIN_type', $type);
480
    }
481
482
483
484
    /**
485
     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
486
     * 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
487
     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
488
     * or indirectly by `EE_Line_item::add_child_line_item()`)
489
     *
490
     * @return EE_Base_Class|EE_Line_Item
491
     * @throws EE_Error
492
     */
493
    public function parent()
494
    {
495
        return $this->ID()
496
            ? $this->get_model()->get_one_by_ID($this->parent_ID())
497
            : $this->_parent;
498
    }
499
500
501
502
    /**
503
     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
504
     *
505
     * @return EE_Base_Class[]|EE_Line_Item[]
506
     * @throws EE_Error
507
     */
508
    public function children()
509
    {
510
        if ($this->ID()) {
511
            return $this->get_model()->get_all(
512
                array(
513
                    array('LIN_parent' => $this->ID()),
514
                    'order_by' => array('LIN_order' => 'ASC'),
515
                )
516
            );
517
        }
518
        if (! is_array($this->_children)) {
519
            $this->_children = array();
520
        }
521
        return $this->_children;
522
    }
523
524
525
526
    /**
527
     * Gets code
528
     *
529
     * @return string
530
     * @throws EE_Error
531
     */
532
    public function code()
533
    {
534
        return $this->get('LIN_code');
535
    }
536
537
538
539
    /**
540
     * Sets code
541
     *
542
     * @param string $code
543
     * @throws EE_Error
544
     */
545
    public function set_code($code)
546
    {
547
        $this->set('LIN_code', $code);
548
    }
549
550
551
552
    /**
553
     * Gets is_taxable
554
     *
555
     * @return boolean
556
     * @throws EE_Error
557
     */
558
    public function is_taxable()
559
    {
560
        return $this->get('LIN_is_taxable');
561
    }
562
563
564
565
    /**
566
     * Sets is_taxable
567
     *
568
     * @param boolean $is_taxable
569
     * @throws EE_Error
570
     */
571
    public function set_is_taxable($is_taxable)
572
    {
573
        $this->set('LIN_is_taxable', $is_taxable);
574
    }
575
576
577
578
    /**
579
     * Gets the object that this model-joins-to.
580
     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
581
     * EEM_Promotion_Object
582
     *
583
     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
584
     *
585
     * @return EE_Base_Class | NULL
586
     * @throws EE_Error
587
     */
588
    public function get_object()
589
    {
590
        $model_name_of_related_obj = $this->OBJ_type();
591
        return $this->get_model()->has_relation($model_name_of_related_obj)
592
            ? $this->get_first_related($model_name_of_related_obj)
593
            : null;
594
    }
595
596
597
598
    /**
599
     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
600
     * (IE, if this line item is for a price or something else, will return NULL)
601
     *
602
     * @param array $query_params
603
     * @return EE_Base_Class|EE_Ticket
604
     * @throws EE_Error
605
     */
606
    public function ticket($query_params = array())
607
    {
608
        //we're going to assume that when this method is called we always want to receive the attached ticket EVEN if that ticket is archived.  This can be overridden via the incoming $query_params argument
609
        $remove_defaults = array('default_where_conditions' => 'none');
610
        $query_params = array_merge($remove_defaults, $query_params);
611
        return $this->get_first_related('Ticket', $query_params);
612
    }
613
614
615
616
    /**
617
     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
618
     *
619
     * @return EE_Datetime | NULL
620
     * @throws EE_Error
621
     */
622
    public function get_ticket_datetime()
623
    {
624 View Code Duplication
        if ($this->OBJ_type() === 'Ticket') {
625
            $ticket = $this->ticket();
626
            if ($ticket instanceof EE_Ticket) {
627
                $datetime = $ticket->first_datetime();
628
                if ($datetime instanceof EE_Datetime) {
629
                    return $datetime;
630
                }
631
            }
632
        }
633
        return null;
634
    }
635
636
637
638
    /**
639
     * Gets the event's name that's related to the ticket, if this is for
640
     * a ticket
641
     *
642
     * @return string
643
     * @throws EE_Error
644
     */
645
    public function ticket_event_name()
646
    {
647
        $event_name = esc_html__('Unknown', 'event_espresso');
648
        $event = $this->ticket_event();
649
        if ($event instanceof EE_Event) {
650
            $event_name = $event->name();
651
        }
652
        return $event_name;
653
    }
654
655
656
    /**
657
     * Gets the event that's related to the ticket, if this line item represents a ticket.
658
     *
659
     * @return EE_Event|null
660
     * @throws EE_Error
661
     */
662
    public function ticket_event()
663
    {
664
        $event = null;
665
        $ticket = $this->ticket();
666 View Code Duplication
        if ($ticket instanceof EE_Ticket) {
667
            $datetime = $ticket->first_datetime();
668
            if ($datetime instanceof EE_Datetime) {
669
                $event = $datetime->event();
670
            }
671
        }
672
        return $event;
673
    }
674
675
676
677
    /**
678
     * Gets the first datetime for this lien item, assuming it's for a ticket
679
     *
680
     * @param string $date_format
681
     * @param string $time_format
682
     * @return string
683
     * @throws EE_Error
684
     */
685
    public function ticket_datetime_start($date_format = '', $time_format = '')
686
    {
687
        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
688
        $datetime = $this->get_ticket_datetime();
689
        if ($datetime) {
690
            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
691
        }
692
        return $first_datetime_string;
693
    }
694
695
696
697
    /**
698
     * Adds the line item as a child to this line item. If there is another child line
699
     * item with the same LIN_code, it is overwritten by this new one
700
     *
701
     * @param EEI_Line_Item $line_item
702
     * @param bool          $set_order
703
     * @return bool success
704
     * @throws EE_Error
705
     */
706
    public function add_child_line_item(EEI_Line_Item $line_item, $set_order = true)
707
    {
708
        // should we calculate the LIN_order for this line item ?
709
        if ($set_order || $line_item->order() === null) {
710
            $line_item->set_order(count($this->children()));
711
        }
712
        if ($this->ID()) {
713
            //check for any duplicate line items (with the same code), if so, this replaces it
714
            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
715
            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
716
                $this->delete_child_line_item($line_item_with_same_code->code());
717
            }
718
            $line_item->set_parent_ID($this->ID());
719
            if ($this->TXN_ID()) {
720
                $line_item->set_TXN_ID($this->TXN_ID());
721
            }
722
            return $line_item->save();
723
        }
724
        $this->_children[$line_item->code()] = $line_item;
725
        if ($line_item->parent() !== $this) {
726
            $line_item->set_parent($this);
727
        }
728
        return true;
729
    }
730
731
732
    /**
733
     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
734
     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
735
     * However, if this line item is NOT saved to the DB, this just caches the parent on
736
     * the EE_Line_Item::_parent property.
737
     *
738
     * @param EE_Line_Item $line_item
739
     * @throws EE_Error
740
     */
741
    public function set_parent($line_item)
742
    {
743
        if ($this->ID()) {
744
            if (! $line_item->ID()) {
745
                $line_item->save();
746
            }
747
            $this->set_parent_ID($line_item->ID());
748
            $this->save();
749
        } else {
750
            $this->_parent = $line_item;
751
            $this->set_parent_ID($line_item->ID());
752
        }
753
    }
754
755
756
757
    /**
758
     * Gets the child line item as specified by its code. Because this returns an object (by reference)
759
     * you can modify this child line item and the parent (this object) can know about them
760
     * because it also has a reference to that line item
761
     *
762
     * @param string $code
763
     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
764
     * @throws EE_Error
765
     */
766
    public function get_child_line_item($code)
767
    {
768
        if ($this->ID()) {
769
            return $this->get_model()->get_one(
770
                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
771
            );
772
        }
773
        return isset($this->_children[$code])
774
            ? $this->_children[$code]
775
            : null;
776
    }
777
778
779
780
    /**
781
     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
782
     * cached on it)
783
     *
784
     * @return int
785
     * @throws EE_Error
786
     */
787
    public function delete_children_line_items()
788
    {
789
        if ($this->ID()) {
790
            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
791
        }
792
        $count = count($this->_children);
793
        $this->_children = array();
794
        return $count;
795
    }
796
797
798
799
    /**
800
     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
801
     * HAS NOT been saved to the DB, removes the child line item with index $code.
802
     * Also searches through the child's children for a matching line item. However, once a line item has been found
803
     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
804
     * deleted)
805
     *
806
     * @param string $code
807
     * @param bool   $stop_search_once_found
808
     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
809
     *             the DB yet)
810
     * @throws EE_Error
811
     */
812
    public function delete_child_line_item($code, $stop_search_once_found = true)
813
    {
814
        if ($this->ID()) {
815
            $items_deleted = 0;
816
            if ($this->code() === $code) {
817
                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
818
                $items_deleted += (int)$this->delete();
819
                if ($stop_search_once_found) {
820
                    return $items_deleted;
821
                }
822
            }
823
            foreach ($this->children() as $child_line_item) {
824
                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
825
            }
826
            return $items_deleted;
827
        }
828
        if (isset($this->_children[$code])) {
829
            unset($this->_children[$code]);
830
            return 1;
831
        }
832
        return 0;
833
    }
834
835
836
    /**
837
     * If this line item is in the database, is of the type subtotal, and
838
     * has no children, why do we have it? It should be deleted so this function
839
     * does that
840
     *
841
     * @return boolean
842
     * @throws EE_Error
843
     */
844
    public function delete_if_childless_subtotal()
845
    {
846
        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
847
            return $this->delete();
848
        }
849
        return false;
850
    }
851
852
853
854
    /**
855
     * Creates a code and returns a string. doesn't assign the code to this model object
856
     *
857
     * @return string
858
     * @throws EE_Error
859
     */
860
    public function generate_code()
861
    {
862
        // each line item in the cart requires a unique identifier
863
        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
864
    }
865
866
867
868
    /**
869
     * @return bool
870
     * @throws EE_Error
871
     */
872
    public function is_tax()
873
    {
874
        return $this->type() === EEM_Line_Item::type_tax;
875
    }
876
877
878
879
    /**
880
     * @return bool
881
     * @throws EE_Error
882
     */
883
    public function is_tax_sub_total()
884
    {
885
        return $this->type() === EEM_Line_Item::type_tax_sub_total;
886
    }
887
888
889
890
    /**
891
     * @return bool
892
     * @throws EE_Error
893
     */
894
    public function is_line_item()
895
    {
896
        return $this->type() === EEM_Line_Item::type_line_item;
897
    }
898
899
900
901
    /**
902
     * @return bool
903
     * @throws EE_Error
904
     */
905
    public function is_sub_line_item()
906
    {
907
        return $this->type() === EEM_Line_Item::type_sub_line_item;
908
    }
909
910
911
912
    /**
913
     * @return bool
914
     * @throws EE_Error
915
     */
916
    public function is_sub_total()
917
    {
918
        return $this->type() === EEM_Line_Item::type_sub_total;
919
    }
920
921
922
923
    /**
924
     * Whether or not this line item is a cancellation line item
925
     *
926
     * @return boolean
927
     * @throws EE_Error
928
     */
929
    public function is_cancellation()
930
    {
931
        return EEM_Line_Item::type_cancellation === $this->type();
932
    }
933
934
935
936
    /**
937
     * @return bool
938
     * @throws EE_Error
939
     */
940
    public function is_total()
941
    {
942
        return $this->type() === EEM_Line_Item::type_total;
943
    }
944
945
946
947
    /**
948
     * @return bool
949
     * @throws EE_Error
950
     */
951
    public function is_cancelled()
952
    {
953
        return $this->type() === EEM_Line_Item::type_cancellation;
954
    }
955
956
957
958
    /**
959
     * @return string like '2, 004.00', formatted according to the localized currency
960
     * @throws EE_Error
961
     */
962
    public function unit_price_no_code()
963
    {
964
        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
965
    }
966
967
968
969
    /**
970
     * @return string like '2, 004.00', formatted according to the localized currency
971
     * @throws EE_Error
972
     */
973
    public function total_no_code()
974
    {
975
        return $this->get_pretty('LIN_total', 'no_currency_code');
976
    }
977
978
979
980
    /**
981
     * Gets the final total on this item, taking taxes into account.
982
     * Has the side-effect of setting the sub-total as it was just calculated.
983
     * If this is used on a grand-total line item, also updates the transaction's
984
     * TXN_total (provided this line item is allowed to persist, otherwise we don't
985
     * want to change a persistable transaction with info from a non-persistent line item)
986
     *
987
     * @return float
988
     * @throws EE_Error
989
     * @throws InvalidArgumentException
990
     * @throws InvalidInterfaceException
991
     * @throws InvalidDataTypeException
992
     */
993
    public function recalculate_total_including_taxes()
994
    {
995
        $pre_tax_total = $this->recalculate_pre_tax_total();
996
        $tax_total = $this->recalculate_taxes_and_tax_total();
997
        $total = $pre_tax_total + $tax_total;
998
        // no negative totals plz
999
        $total = max($total, 0);
1000
        $this->set_total($total);
1001
        //only update the related transaction's total
1002
        //if we intend to save this line item and its a grand total
1003
        if (
1004
            $this->allow_persist() && $this->type() === EEM_Line_Item::type_total
1005
            && $this->transaction()
1006
               instanceof
1007
               EE_Transaction
1008
        ) {
1009
            $this->transaction()->set_total($total);
1010
            if ($this->transaction()->ID()) {
1011
                $this->transaction()->save();
1012
            }
1013
        }
1014
        $this->maybe_save();
1015
        return $total;
1016
    }
1017
1018
1019
    /**
1020
     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1021
     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1022
     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1023
     * when this is called on the grand total
1024
     *
1025
     * @return float
1026
     * @throws InvalidArgumentException
1027
     * @throws InvalidInterfaceException
1028
     * @throws InvalidDataTypeException
1029
     * @throws EE_Error
1030
     */
1031
    public function recalculate_pre_tax_total()
1032
    {
1033
        $total = 0;
1034
        $my_children = $this->children();
1035
        $has_children = ! empty($my_children);
1036
        if ($has_children && $this->is_line_item()) {
1037
            $total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
1038
        } elseif (! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
1039
            $total = $this->unit_price() * $this->quantity();
1040
        } elseif ($this->is_sub_total() || $this->is_total()) {
1041
            $total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
1042
        } elseif ($this->is_tax_sub_total() || $this->is_tax() || $this->is_cancelled()) {
1043
            // completely ignore tax totals, tax sub-totals, and cancelled line items, when calculating the pre-tax-total
1044
            return 0;
1045
        }
1046
        // ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
1047
        if (
1048
            ! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()
1049
        ) {
1050
            if ($this->OBJ_type() !== 'Event') {
1051
                $this->set_quantity(1);
1052
            }
1053
            if (! $this->is_percent()) {
1054
                $this->set_unit_price($total);
1055
            }
1056
        }
1057
        //we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1058
        //so it ought to be
1059
        if (! $this->is_total()) {
1060
            $this->set_total($total);
1061
            //if not a percent line item, make sure we keep the unit price in sync
1062
            if (
1063
                $has_children
1064
                && $this->is_line_item()
1065
                && ! $this->is_percent()
1066
            ) {
1067
                if ($this->quantity() === 0) {
1068
                    $new_unit_price = 0;
1069
                } else {
1070
                    $new_unit_price = $this->total() / $this->quantity();
1071
                }
1072
                $this->set_unit_price($new_unit_price);
1073
            }
1074
            $this->maybe_save();
1075
        }
1076
        return $total;
1077
    }
1078
1079
1080
1081
    /**
1082
     * Calculates the pretax total when this line item is a subtotal or total line item.
1083
     * Basically does a sum-then-round approach (ie, any percent line item that are children
1084
     * will calculate their total based on the un-rounded total we're working with so far, and
1085
     * THEN round the result; instead of rounding as we go like with sub-line-items)
1086
     *
1087
     * @param float          $calculated_total_so_far
1088
     * @param EE_Line_Item[] $my_children
1089
     * @return float
1090
     * @throws InvalidArgumentException
1091
     * @throws InvalidInterfaceException
1092
     * @throws InvalidDataTypeException
1093
     * @throws EE_Error
1094
     */
1095
    protected function _recalculate_pretax_total_for_subtotal($calculated_total_so_far, $my_children = null)
1096
    {
1097
        if ($my_children === null) {
1098
            $my_children = $this->children();
1099
        }
1100
        //get the total of all its children
1101
        foreach ($my_children as $child_line_item) {
1102
            if ($child_line_item instanceof EE_Line_Item && ! $child_line_item->is_cancellation()) {
1103
                // percentage line items are based on total so far
1104
                if ($child_line_item->is_percent()) {
1105
                    //round as we go so that the line items add up ok
1106
                    $percent_total = round(
1107
                        $calculated_total_so_far * $child_line_item->percent() / 100,
1108
                        EE_Registry::instance()->CFG->currency->dec_plc
1109
                    );
1110
                    $child_line_item->set_total($percent_total);
1111
                    //so far all percent line items should have a quantity of 1
1112
                    //(ie, no double percent discounts. Although that might be requested someday)
1113
                    $child_line_item->set_quantity(1);
1114
                    $child_line_item->maybe_save();
1115
                    $calculated_total_so_far += $percent_total;
1116
                } else {
1117
                    //verify flat sub-line-item quantities match their parent
1118
                    if ($child_line_item->is_sub_line_item()) {
1119
                        $child_line_item->set_quantity($this->quantity());
1120
                    }
1121
                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1122
                }
1123
            }
1124
        }
1125
        if ($this->is_sub_total()) {
1126
            // no negative totals plz
1127
            $calculated_total_so_far = max($calculated_total_so_far, 0);
1128
        }
1129
        return $calculated_total_so_far;
1130
    }
1131
1132
1133
1134
    /**
1135
     * Calculates the pretax total for a normal line item, in a round-then-sum approach
1136
     * (where each sub-line-item is applied to the base price for the line item
1137
     * and the result is immediately rounded, rather than summing all the sub-line-items
1138
     * then rounding, like we do when recalculating pretax totals on totals and subtotals).
1139
     *
1140
     * @param float          $calculated_total_so_far
1141
     * @param EE_Line_Item[] $my_children
1142
     * @return float
1143
     * @throws InvalidArgumentException
1144
     * @throws InvalidInterfaceException
1145
     * @throws InvalidDataTypeException
1146
     * @throws EE_Error
1147
     */
1148
    protected function _recalculate_pretax_total_for_line_item($calculated_total_so_far, $my_children = null)
1149
    {
1150
        if ($my_children === null) {
1151
            $my_children = $this->children();
1152
        }
1153
        //we need to keep track of the running total for a single item,
1154
        //because we need to round as we go
1155
        $unit_price_for_total = 0;
1156
        $quantity_for_total = 1;
1157
        //get the total of all its children
1158
        foreach ($my_children as $child_line_item) {
1159
            if ($child_line_item instanceof EE_Line_Item && ! $child_line_item->is_cancellation()) {
1160
                if ($child_line_item->is_percent()) {
1161
                    //it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1162
                    //not total multiplied by percent, because that ignores rounding along-the-way
1163
                    $percent_unit_price = round(
1164
                        $unit_price_for_total * $child_line_item->percent() / 100,
1165
                        EE_Registry::instance()->CFG->currency->dec_plc
1166
                    );
1167
                    $percent_total = $percent_unit_price * $quantity_for_total;
1168
                    $child_line_item->set_total($percent_total);
1169
                    //so far all percent line items should have a quantity of 1
1170
                    //(ie, no double percent discounts. Although that might be requested someday)
1171
                    $child_line_item->set_quantity(1);
1172
                    $child_line_item->maybe_save();
1173
                    $calculated_total_so_far += $percent_total;
1174
                    $unit_price_for_total += $percent_unit_price;
1175
                } else {
1176
                    //verify flat sub-line-item quantities match their parent
1177
                    if ($child_line_item->is_sub_line_item()) {
1178
                        $child_line_item->set_quantity($this->quantity());
1179
                    }
1180
                    $quantity_for_total = $child_line_item->quantity();
1181
                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1182
                    $unit_price_for_total += $child_line_item->unit_price();
1183
                }
1184
            }
1185
        }
1186
        return $calculated_total_so_far;
1187
    }
1188
1189
1190
1191
    /**
1192
     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1193
     * the totals on each tax calculated, and returns the final tax total
1194
     *
1195
     * @return float
1196
     * @throws EE_Error
1197
     */
1198
    public function recalculate_taxes_and_tax_total()
1199
    {
1200
        //get all taxes
1201
        $taxes = $this->tax_descendants();
1202
        //calculate the pretax total
1203
        $taxable_total = $this->taxable_total();
1204
        $tax_total = 0;
1205
        foreach ($taxes as $tax) {
1206
            $total_on_this_tax = $taxable_total * $tax->percent() / 100;
1207
            //remember the total on this line item
1208
            $tax->set_total($total_on_this_tax);
1209
            $tax_total += $tax->total();
1210
        }
1211
        $this->_recalculate_tax_sub_total();
1212
        return $tax_total;
1213
    }
1214
1215
1216
1217
    /**
1218
     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
1219
     *
1220
     * @return void
1221
     * @throws EE_Error
1222
     */
1223
    private function _recalculate_tax_sub_total()
1224
    {
1225
        if ($this->is_tax_sub_total()) {
1226
            $total = 0;
1227
            $total_percent = 0;
1228
            //simply loop through all its children (which should be taxes) and sum their total
1229
            foreach ($this->children() as $child_tax) {
1230
                if ($child_tax instanceof EE_Line_Item) {
1231
                    $total += $child_tax->total();
1232
                    $total_percent += $child_tax->percent();
1233
                }
1234
            }
1235
            $this->set_total($total);
1236
            $this->set_percent($total_percent);
1237
        } elseif ($this->is_total()) {
1238
            foreach ($this->children() as $maybe_tax_subtotal) {
1239
                if ($maybe_tax_subtotal instanceof EE_Line_Item) {
1240
                    $maybe_tax_subtotal->_recalculate_tax_sub_total();
1241
                }
1242
            }
1243
        }
1244
    }
1245
1246
1247
1248
    /**
1249
     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1250
     * recalculate_taxes_and_total
1251
     *
1252
     * @return float
1253
     * @throws EE_Error
1254
     */
1255
    public function get_total_tax()
1256
    {
1257
        $this->_recalculate_tax_sub_total();
1258
        $total = 0;
1259
        foreach ($this->tax_descendants() as $tax_line_item) {
1260
            if ($tax_line_item instanceof EE_Line_Item) {
1261
                $total += $tax_line_item->total();
1262
            }
1263
        }
1264
        return $total;
1265
    }
1266
1267
1268
    /**
1269
     * Gets the total for all the items purchased only
1270
     *
1271
     * @return float
1272
     * @throws EE_Error
1273
     */
1274
    public function get_items_total()
1275
    {
1276
        //by default, let's make sure we're consistent with the existing line item
1277
        if ($this->is_total()) {
1278
            $pretax_subtotal_li = EEH_Line_Item::get_pre_tax_subtotal($this);
1279
            if ($pretax_subtotal_li instanceof EE_Line_Item) {
1280
                return $pretax_subtotal_li->total();
1281
            }
1282
        }
1283
        $total = 0;
1284
        foreach ($this->get_items() as $item) {
1285
            if ($item instanceof EE_Line_Item) {
1286
                $total += $item->total();
1287
            }
1288
        }
1289
        return $total;
1290
    }
1291
1292
1293
1294
    /**
1295
     * Gets all the descendants (ie, children or children of children etc) that
1296
     * are of the type 'tax'
1297
     *
1298
     * @return EE_Line_Item[]
1299
     */
1300
    public function tax_descendants()
1301
    {
1302
        return EEH_Line_Item::get_tax_descendants($this);
1303
    }
1304
1305
1306
1307
    /**
1308
     * Gets all the real items purchased which are children of this item
1309
     *
1310
     * @return EE_Line_Item[]
1311
     */
1312
    public function get_items()
1313
    {
1314
        return EEH_Line_Item::get_line_item_descendants($this);
1315
    }
1316
1317
1318
1319
    /**
1320
     * Returns the amount taxable among this line item's children (or if it has no children,
1321
     * how much of it is taxable). Does not recalculate totals or subtotals.
1322
     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1323
     * but there is a "Taxable" discount), returns 0.
1324
     *
1325
     * @return float
1326
     * @throws EE_Error
1327
     */
1328
    public function taxable_total()
1329
    {
1330
        $total = 0;
1331
        if ($this->children()) {
1332
            foreach ($this->children() as $child_line_item) {
1333
                if ($child_line_item->type() === EEM_Line_Item::type_line_item && $child_line_item->is_taxable()) {
1334
                    //if it's a percent item, only take into account the percent
1335
                    //that's taxable too (the taxable total so far)
1336
                    if ($child_line_item->is_percent()) {
1337
                        $total += ($total * $child_line_item->percent() / 100);
1338
                    } else {
1339
                        $total += $child_line_item->total();
1340
                    }
1341
                } elseif ($child_line_item->type() === EEM_Line_Item::type_sub_total) {
1342
                    $total += $child_line_item->taxable_total();
1343
                }
1344
            }
1345
        }
1346
        return max($total, 0);
1347
    }
1348
1349
1350
1351
    /**
1352
     * Gets the transaction for this line item
1353
     *
1354
     * @return EE_Base_Class|EE_Transaction
1355
     * @throws EE_Error
1356
     */
1357
    public function transaction()
1358
    {
1359
        return $this->get_first_related('Transaction');
1360
    }
1361
1362
1363
1364
    /**
1365
     * Saves this line item to the DB, and recursively saves its descendants.
1366
     * Because there currently is no proper parent-child relation on the model,
1367
     * save_this_and_cached() will NOT save the descendants.
1368
     * Also sets the transaction on this line item and all its descendants before saving
1369
     *
1370
     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1371
     * @return int count of items saved
1372
     * @throws EE_Error
1373
     */
1374
    public function save_this_and_descendants_to_txn($txn_id = null)
1375
    {
1376
        $count = 0;
1377
        if (! $txn_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $txn_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1378
            $txn_id = $this->TXN_ID();
1379
        }
1380
        $this->set_TXN_ID($txn_id);
1381
        $children = $this->children();
1382
        $count += $this->save()
1383
            ? 1
1384
            : 0;
1385
        foreach ($children as $child_line_item) {
1386
            if ($child_line_item instanceof EE_Line_Item) {
1387
                $child_line_item->set_parent_ID($this->ID());
1388
                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1389
            }
1390
        }
1391
        return $count;
1392
    }
1393
1394
1395
1396
    /**
1397
     * Saves this line item to the DB, and recursively saves its descendants.
1398
     *
1399
     * @return int count of items saved
1400
     * @throws EE_Error
1401
     */
1402
    public function save_this_and_descendants()
1403
    {
1404
        $count = 0;
1405
        $children = $this->children();
1406
        $count += $this->save()
1407
            ? 1
1408
            : 0;
1409
        foreach ($children as $child_line_item) {
1410
            if ($child_line_item instanceof EE_Line_Item) {
1411
                $child_line_item->set_parent_ID($this->ID());
1412
                $count += $child_line_item->save_this_and_descendants();
1413
            }
1414
        }
1415
        return $count;
1416
    }
1417
1418
1419
1420
    /**
1421
     * returns the cancellation line item if this item was cancelled
1422
     *
1423
     * @return EE_Line_Item[]
1424
     * @throws InvalidArgumentException
1425
     * @throws InvalidInterfaceException
1426
     * @throws InvalidDataTypeException
1427
     * @throws ReflectionException
1428
     * @throws EE_Error
1429
     */
1430
    public function get_cancellations()
1431
    {
1432
        EE_Registry::instance()->load_helper('Line_Item');
1433
        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1434
    }
1435
1436
1437
1438
    /**
1439
     * If this item has an ID, then this saves it again to update the db
1440
     *
1441
     * @return int count of items saved
1442
     * @throws EE_Error
1443
     */
1444
    public function maybe_save()
1445
    {
1446
        if ($this->ID()) {
1447
            return $this->save();
1448
        }
1449
        return false;
1450
    }
1451
1452
1453
    /**
1454
     * clears the cached children and parent from the line item
1455
     *
1456
     * @return void
1457
     */
1458
    public function clear_related_line_item_cache()
1459
    {
1460
        $this->_children = array();
1461
        $this->_parent = null;
1462
    }
1463
1464
1465
1466
    /**
1467
     * @param bool $raw
1468
     * @return int
1469
     * @throws EE_Error
1470
     */
1471
    public function timestamp($raw = false)
1472
    {
1473
        return $raw
1474
            ? $this->get_raw('LIN_timestamp')
1475
            : $this->get('LIN_timestamp');
1476
    }
1477
1478
1479
1480
1481
    /************************* DEPRECATED *************************/
1482
    /**
1483
     * @deprecated 4.6.0
1484
     * @param string $type one of the constants on EEM_Line_Item
1485
     * @return EE_Line_Item[]
1486
     */
1487
    protected function _get_descendants_of_type($type)
1488
    {
1489
        EE_Error::doing_it_wrong(
1490
            'EE_Line_Item::_get_descendants_of_type()',
1491
            __('Method replaced with EEH_Line_Item::get_descendants_of_type()', 'event_espresso'), '4.6.0'
1492
        );
1493
        return EEH_Line_Item::get_descendants_of_type($this, $type);
1494
    }
1495
1496
1497
1498
    /**
1499
     * @deprecated 4.6.0
1500
     * @param string $type like one of the EEM_Line_Item::type_*
1501
     * @return EE_Line_Item
1502
     */
1503
    public function get_nearest_descendant_of_type($type)
1504
    {
1505
        EE_Error::doing_it_wrong(
1506
            'EE_Line_Item::get_nearest_descendant_of_type()',
1507
            __('Method replaced with EEH_Line_Item::get_nearest_descendant_of_type()', 'event_espresso'), '4.6.0'
1508
        );
1509
        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1510
    }
1511
1512
1513
1514
}
1515