Completed
Branch BUG-10381-asset-loading (4420e7)
by
unknown
126:47 queued 112:44
created

EE_Line_Item::timestamp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php if ( !defined( 'EVENT_ESPRESSO_VERSION' ) ) {
2
	exit( 'No direct script access allowed' );
3
}
4
/**
5
 * Event Espresso
6
 *
7
 * Event Registration and Management Plugin for WordPress
8
 *
9
 * @ package        Event Espresso
10
 * @ author        Event Espresso
11
 * @ copyright    (c) 2008-2011 Event Espresso  All Rights Reserved.
12
 * @ license        {@link http://eventespresso.com/support/terms-conditions/}   * see Plugin Licensing *
13
 * @ link                {@link http://www.eventespresso.com}
14
 * @ since            4.0
15
 *
16
 */
17
18
19
20
/**
21
 * EE_Line_Item class
22
 * see EEM_Line_Item for description
23
 *
24
 * @package            Event Espresso
25
 * @subpackage         includes/classes/EE_Checkin.class.php
26
 * @author             Michael Nelson
27
 */
28
class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item {
29
30
	/**
31
	 * for children line items (currently not a normal relation)
32
	 * @type EE_Line_Item[]
33
	 */
34
	protected $_children;
35
36
	/**
37
	 * for the parent line item
38
	 * @var EE_Line_Item
39
	 */
40
	protected $_parent;
41
42
43
44
	/**
45
	 *
46
	 * @param array $props_n_values  incoming values
47
	 * @param string $timezone  incoming timezone (if not set the timezone set for the website will be
48
	 *                          		used.)
49
	 * @param array $date_formats  incoming date_formats in an array where the first value is the
50
	 *                             		    date_format and the second value is the time format
51
	 * @return EE_Line_Item
52
	 */
53
	public static function new_instance( $props_n_values = array(), $timezone = null, $date_formats = array() ) {
54
		$has_object = parent::_check_for_object( $props_n_values, __CLASS__, $timezone, $date_formats );
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_check_for_object() instead of new_instance()). Are you sure this is correct? If so, you might want to change this to $this->_check_for_object().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
Bug introduced by
It seems like $timezone defined by parameter $timezone on line 53 can also be of type string; however, EE_Base_Class::_check_for_object() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
55
		return $has_object ? $has_object : new self( $props_n_values, false, $timezone, $date_formats );
0 ignored issues
show
Unused Code introduced by
The call to EE_Line_Item::__construct() has too many arguments starting with $date_formats.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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