Completed
Branch BUG-10015-set-curl-ssl-version (7bdf86)
by
unknown
30:31 queued 13:32
created

EEH_Line_Item::visualize()   D

Complexity

Conditions 11
Paths 144

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 20
nc 144
nop 2
dl 0
loc 29
rs 4.9629
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php if (!defined('EVENT_ESPRESSO_VERSION')) { exit('No direct script access allowed'); }
2
/**
3
 *
4
 * EEH_Line_Item
5
 *
6
 * This should be the main class which is aware of the line item tree structure, and
7
 * should take care of common operations like inserting items into it, updating
8
 * items in it based on what the line items are for, and removed line items.
9
 * All this logic was originally contained in EE_Cart, but because there are
10
 * actually other places that need to modify the record of what was purchased
11
 * (eg when a PayPal IPN is received, if PayPal changes the taxes, we need to update the line items;
12
 * or admin-side cancellations etc).
13
 * Generally all these functions will first take the total line item and figure things out from there
14
 *
15
 * @package			Event Espresso
16
 * @subpackage
17
 * @author				Mike Nelson
18
 *
19
 */
20
class EEH_Line_Item {
21
22
	//other functions: cancel ticket purchase
23
	//delete ticket purchase
24
	//add promotion
25
26
27
28
	/**
29
	 * Adds a simple item ( unrelated to any other model object) to the total line item
30
	 * in the correct spot in the line item tree (also verifying it doesn't add a duplicate based on the LIN_code)
31
	 * beneath the pre-tax-total (alongside event subtotals).
32
	 * Automatically re-calculates the line item totals and updates the related transaction. But
33
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
34
	 * should probably change because of this).
35
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
36
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
37
	 *
38
	 * @param EE_Line_Item $parent_line_item
39
	 * @param string       $name
40
	 * @param float        $unit_price
41
	 * @param string       $description
42
	 * @param int          $quantity
43
	 * @param boolean      $taxable
44
	 * @param boolean      $code if set to a value, ensures there is only one line item with that code
45
	 * @return boolean success
46
	 * @throws \EE_Error
47
	 */
48
	public static function add_unrelated_item( EE_Line_Item $parent_line_item, $name, $unit_price, $description = '', $quantity = 1, $taxable = FALSE, $code = NULL  ){
49
		$items_subtotal = self::get_pre_tax_subtotal( $parent_line_item );
50
		$line_item = EE_Line_Item::new_instance(array(
51
			'LIN_name' => $name,
52
			'LIN_desc' => $description,
53
			'LIN_unit_price' => $unit_price,
54
			'LIN_quantity' => $quantity,
55
			'LIN_percent' => null,
56
			'LIN_is_taxable' => $taxable,
57
			'LIN_order' => $items_subtotal instanceof EE_Line_Item ? count( $items_subtotal->children() ) : 0,
58
			'LIN_total' => (float) $unit_price * (int) $quantity,
59
			'LIN_type'=>  EEM_Line_Item::type_line_item,
60
			'LIN_code' => $code,
61
		));
62
		$line_item = apply_filters(
63
			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
64
			$line_item,
65
			$parent_line_item
66
		);
67
		return self::add_item( $parent_line_item, $line_item );
68
	}
69
70
71
72
	/**
73
	 * Adds a simple item ( unrelated to any other model object) to the total line item,
74
	 * in the correct spot in the line item tree. Automatically
75
	 * re-calculates the line item totals and updates the related transaction. But
76
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
77
	 * should probably change because of this).
78
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
79
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
80
	 *
81
	 * @param EE_Line_Item $parent_line_item
82
	 * @param string       $name
83
	 * @param float        $percentage_amount
84
	 * @param string       $description
85
	 * @param boolean      $taxable
86
	 * @return boolean success
87
	 * @throws \EE_Error
88
	 */
89
	public static function add_percentage_based_item( EE_Line_Item $parent_line_item, $name, $percentage_amount, $description = '', $taxable = FALSE ){
90
		$line_item = EE_Line_Item::new_instance(array(
91
			'LIN_name' => $name,
92
			'LIN_desc' => $description,
93
			'LIN_unit_price' => 0,
94
			'LIN_percent' => $percentage_amount,
95
			'LIN_quantity' => NULL,
96
			'LIN_is_taxable' => $taxable,
97
			'LIN_total' => (float) ( $percentage_amount * ( $parent_line_item->total() / 100 ) ),
98
			'LIN_type'=>  EEM_Line_Item::type_line_item,
99
			'LIN_parent' => $parent_line_item->ID()
100
		));
101
		$line_item = apply_filters(
102
			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
103
			$line_item
104
		);
105
		return self::add_item( $parent_line_item, $line_item );
106
	}
107
108
109
110
	/**
111
	 * Returns the new line item created by adding a purchase of the ticket
112
	 * ensures that ticket line item is saved, and that cart total has been recalculated.
113
	 * If this ticket has already been purchased, just increments its count.
114
	 * Automatically re-calculates the line item totals and updates the related transaction. But
115
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
116
	 * should probably change because of this).
117
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
118
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
119
	 *
120
	 * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
121
	 * @param EE_Ticket $ticket
122
	 * @param int $qty
123
	 * @return \EE_Line_Item
124
	 * @throws \EE_Error
125
	 */
126
	public static function add_ticket_purchase( EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1 ){
127
		if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total() ) {
128
			throw new EE_Error( sprintf( __( 'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.', 'event_espresso' ), $ticket->ID(), $total_line_item->ID() ) );
129
		}
130
		// either increment the qty for an existing ticket
131
		$line_item = self::increment_ticket_qty_if_already_in_cart( $total_line_item, $ticket, $qty );
132
		// or add a new one
133
		if ( ! $line_item instanceof EE_Line_Item ) {
134
			$line_item = self::create_ticket_line_item( $total_line_item, $ticket, $qty );
135
		}
136
		$total_line_item->recalculate_total_including_taxes();
137
		return $line_item;
138
	}
139
140
141
142
	/**
143
	 * Returns the new line item created by adding a purchase of the ticket
144
	 * @param \EE_Line_Item $total_line_item
145
	 * @param EE_Ticket $ticket
146
	 * @param int $qty
147
	 * @return \EE_Line_Item
148
	 * @throws \EE_Error
149
	 */
150
	public static function increment_ticket_qty_if_already_in_cart( EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1 ) {
151
		$line_item = null;
152
		if ( $total_line_item instanceof EE_Line_Item && $total_line_item->is_total() ) {
153
			$ticket_line_items = EEH_Line_Item::get_ticket_line_items( $total_line_item );
154
			foreach ( (array)$ticket_line_items as $ticket_line_item ) {
155
				if (
156
					$ticket_line_item instanceof EE_Line_Item
157
					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
158
				) {
159
					$line_item = $ticket_line_item;
160
					break;
161
				}
162
			}
163
		}
164
		if ( $line_item instanceof EE_Line_Item ) {
165
			EEH_Line_Item::increment_quantity( $line_item, $qty );
166
			return $line_item;
167
		}
168
		return null;
169
	}
170
171
172
173
	/**
174
	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
175
	 * Does NOT save or recalculate other line items totals
176
	 *
177
	 * @param EE_Line_Item $line_item
178
	 * @param int          $qty
179
	 * @return void
180
	 * @throws \EE_Error
181
	 */
182 View Code Duplication
	public static function increment_quantity( EE_Line_Item $line_item, $qty = 1 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
		if( ! $line_item->is_percent() ) {
184
			$qty += $line_item->quantity();
185
			$line_item->set_quantity( $qty );
186
			$line_item->set_total( $line_item->unit_price() * $qty );
187
			$line_item->save();
188
		}
189
		foreach( $line_item->children() as $child ) {
190
			if( $child->is_sub_line_item() ) {
191
				EEH_Line_Item::update_quantity( $child, $qty );
192
			}
193
		}
194
	}
195
196
197
198
	/**
199
	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
200
	 * Does NOT save or recalculate other line items totals
201
	 *
202
	 * @param EE_Line_Item $line_item
203
	 * @param int          $qty
204
	 * @return void
205
	 * @throws \EE_Error
206
	 */
207
	public static function decrement_quantity( EE_Line_Item $line_item, $qty = 1 ) {
208
		if( ! $line_item->is_percent() ) {
209
			$qty = $line_item->quantity() - $qty;
210
			$qty = max( $qty, 0 );
211
			$line_item->set_quantity( $qty );
212
			$line_item->set_total( $line_item->unit_price() * $qty );
213
			$line_item->save();
214
		}
215
		foreach( $line_item->children() as $child ) {
216
			if( $child->is_sub_line_item() ) {
217
				EEH_Line_Item::update_quantity( $child, $qty );
218
			}
219
		}
220
	}
221
222
223
224
	/**
225
	 * Updates the line item and its children's quantities to the specified number.
226
	 * Does NOT save them or recalculate totals.
227
	 *
228
	 * @param EE_Line_Item $line_item
229
	 * @param int          $new_quantity
230
	 * @throws \EE_Error
231
	 */
232 View Code Duplication
	public static function update_quantity( EE_Line_Item $line_item, $new_quantity ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
233
		if( ! $line_item->is_percent() ) {
234
			$line_item->set_quantity( $new_quantity );
235
			$line_item->set_total( $line_item->unit_price() * $new_quantity );
236
			$line_item->save();
237
		}
238
		foreach( $line_item->children() as $child ) {
239
			if( $child->is_sub_line_item() ) {
240
				EEH_Line_Item::update_quantity( $child, $new_quantity );
241
			}
242
		}
243
	}
244
245
246
247
	/**
248
	 * Returns the new line item created by adding a purchase of the ticket
249
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
250
	 * @param EE_Ticket $ticket
251
	 * @param int $qty
252
	 * @return \EE_Line_Item
253
	 * @throws \EE_Error
254
	 */
255
	public static function create_ticket_line_item( EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1 ) {
256
		$datetimes = $ticket->datetimes();
257
		$first_datetime = reset( $datetimes );
258
		if( $first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event ) {
259
			$first_datetime_name = $first_datetime->event()->name();
260
		} else {
261
			$first_datetime_name = __( 'Event', 'event_espresso' );
262
		}
263
		$event = sprintf( _x( '(For %1$s)', '(For Event Name)', 'event_espresso' ), $first_datetime_name );
264
		// get event subtotal line
265
		$events_sub_total = self::get_event_line_item_for_ticket( $total_line_item, $ticket );
266
		// add $ticket to cart
267
		$line_item = EE_Line_Item::new_instance( array(
268
			'LIN_name'       	=> $ticket->name(),
269
			'LIN_desc'       		=> $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
270
			'LIN_unit_price' 	=> $ticket->price(),
271
			'LIN_quantity'   	=> $qty,
272
			'LIN_is_taxable' 	=> $ticket->taxable(),
273
			'LIN_order'      	=> count( $events_sub_total->children() ),
274
			'LIN_total'      		=> $ticket->price() * $qty,
275
			'LIN_type'       		=> EEM_Line_Item::type_line_item,
276
			'OBJ_ID'         		=> $ticket->ID(),
277
			'OBJ_type'       	=> 'Ticket'
278
		) );
279
		$line_item = apply_filters(
280
			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
281
			$line_item
282
		);
283
		$events_sub_total->add_child_line_item( $line_item );
284
		//now add the sub-line items
285
		$running_total_for_ticket = 0;
286
		foreach ( $ticket->prices( array( 'order_by' => array( 'PRC_order' => 'ASC' ) ) ) as $price ) {
287
			$sign = $price->is_discount() ? -1 : 1;
0 ignored issues
show
Documentation Bug introduced by
The method is_discount does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
288
			$price_total = $price->is_percent()
0 ignored issues
show
Documentation Bug introduced by
The method is_percent does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
289
				? $running_total_for_ticket * $price->amount() / 100
0 ignored issues
show
Documentation Bug introduced by
The method amount does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
290
				: $price->amount() * $qty;
0 ignored issues
show
Documentation Bug introduced by
The method amount does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
291
			$sub_line_item = EE_Line_Item::new_instance( array(
292
				'LIN_name'       	=> $price->name(),
293
				'LIN_desc'       		=> $price->desc(),
0 ignored issues
show
Documentation Bug introduced by
The method desc does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
294
				'LIN_quantity'   	=> $price->is_percent() ? null : $qty,
0 ignored issues
show
Documentation Bug introduced by
The method is_percent does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
295
				'LIN_is_taxable' 	=> false,
296
				'LIN_order'      	=> $price->order(),
0 ignored issues
show
Documentation Bug introduced by
The method order does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
297
				'LIN_total'      		=> $sign * $price_total,
298
				'LIN_type'       		=> EEM_Line_Item::type_sub_line_item,
299
				'OBJ_ID'         		=> $price->ID(),
300
				'OBJ_type'       	=> 'Price'
301
			) );
302
			$sub_line_item = apply_filters(
303
				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
304
				$sub_line_item
305
			);
306
			if ( $price->is_percent() ) {
0 ignored issues
show
Documentation Bug introduced by
The method is_percent does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
307
				$sub_line_item->set_percent( $sign * $price->amount() );
0 ignored issues
show
Documentation Bug introduced by
The method amount does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
308
			} else {
309
				$sub_line_item->set_unit_price( $sign * $price->amount() );
0 ignored issues
show
Documentation Bug introduced by
The method amount does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
310
			}
311
			$running_total_for_ticket += $price_total;
312
			$line_item->add_child_line_item( $sub_line_item );
313
		}
314
		return $line_item;
315
	}
316
317
318
319
	/**
320
	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
321
	 * re-calculates the line item totals and updates the related transaction. But
322
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
323
	 * should probably change because of this).
324
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
325
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
326
	 *
327
	 * @param EE_Line_Item $total_line_item
328
	 * @param EE_Line_Item $item to be added
329
	 * @return boolean
330
	 * @throws \EE_Error
331
	 */
332
	public static function add_item( EE_Line_Item $total_line_item, EE_Line_Item $item ){
333
		$pre_tax_subtotal = self::get_pre_tax_subtotal( $total_line_item );
334
		if ( $pre_tax_subtotal instanceof EE_Line_Item ){
335
			$success = $pre_tax_subtotal->add_child_line_item($item);
336
		}else{
337
			return FALSE;
338
		}
339
		$total_line_item->recalculate_total_including_taxes();
340
		return $success;
341
	}
342
343
344
345
	/**
346
	 * cancels an existing ticket line item,
347
	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
348
	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
349
	 *
350
	 * @param EE_Line_Item $ticket_line_item
351
	 * @param int          $qty
352
	 * @return bool success
353
	 * @throws \EE_Error
354
	 */
355
	public static function cancel_ticket_line_item( EE_Line_Item $ticket_line_item, $qty = 1 ) {
356
		// validate incoming line_item
357 View Code Duplication
		if ( $ticket_line_item->OBJ_type() !== 'Ticket' ) {
358
			throw new EE_Error(
359
				sprintf(
360
					__( 'The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso' ),
361
					$ticket_line_item->type()
362
				)
363
			);
364
		}
365
		if ( $ticket_line_item->quantity() < $qty ) {
366
			throw new EE_Error(
367
				sprintf(
368
					__( 'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.', 'event_espresso' ),
369
					$qty,
370
					$ticket_line_item->quantity()
371
				)
372
			);
373
		}
374
		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
375
		$ticket_line_item->set_quantity( $ticket_line_item->quantity() - $qty );
376
		foreach( $ticket_line_item->children() as $child_line_item ) {
377
			if(
378
				$child_line_item->is_sub_line_item()
379
				&& ! $child_line_item->is_percent()
380
				&& ! $child_line_item->is_cancellation()
381
			) {
382
				$child_line_item->set_quantity( $child_line_item->quantity() - $qty );
383
			}
384
		}
385
		// get cancellation sub line item
386
		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
387
			$ticket_line_item,
388
			EEM_Line_Item::type_cancellation
389
		);
390
		$cancellation_line_item = reset( $cancellation_line_item );
391
		// verify that this ticket was indeed previously cancelled
392
		if ( $cancellation_line_item instanceof EE_Line_Item ) {
393
			// increment cancelled quantity
394
			$cancellation_line_item->set_quantity( $cancellation_line_item->quantity() + $qty );
395
		} else {
396
			// create cancellation sub line item
397
			$cancellation_line_item = EE_Line_Item::new_instance( array(
398
				'LIN_name'       => __( 'Cancellation', 'event_espresso' ),
399
				'LIN_desc'       => sprintf(
400
					_x( 'Cancelled %1$s : %2$s', 'Cancelled Ticket Name : 2015-01-01 11:11', 'event_espresso' ),
401
					$ticket_line_item->name(),
402
					current_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) )
403
				),
404
				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
405
				'LIN_quantity'   => $qty,
406
				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
407
				'LIN_order'      => count( $ticket_line_item->children() ),
408
				'LIN_total'      => 0, // $ticket_line_item->unit_price()
409
				'LIN_type'       => EEM_Line_Item::type_cancellation,
410
			) );
411
			$ticket_line_item->add_child_line_item( $cancellation_line_item );
412
		}
413
		if ( $ticket_line_item->save_this_and_descendants() > 0 ) {
414
			EEH_Line_Item::get_grand_total_and_recalculate_everything( $ticket_line_item );
415
			return true;
416
		}
417
		return false;
418
	}
419
420
421
422
	/**
423
	 * reinstates (un-cancels?) a previously canceled ticket line item,
424
	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
425
	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
426
	 *
427
	 * @param EE_Line_Item $ticket_line_item
428
	 * @param int          $qty
429
	 * @return bool success
430
	 * @throws \EE_Error
431
	 */
432
	public static function reinstate_canceled_ticket_line_item( EE_Line_Item $ticket_line_item, $qty = 1 ) {
433
		// validate incoming line_item
434 View Code Duplication
		if ( $ticket_line_item->OBJ_type() !== 'Ticket' ) {
435
			throw new EE_Error(
436
				sprintf(
437
					__( 'The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso' ),
438
					$ticket_line_item->type()
439
				)
440
			);
441
		}
442
		// get cancellation sub line item
443
		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
444
			$ticket_line_item,
445
			EEM_Line_Item::type_cancellation
446
		);
447
		$cancellation_line_item = reset( $cancellation_line_item );
448
		// verify that this ticket was indeed previously cancelled
449
		if ( ! $cancellation_line_item instanceof EE_Line_Item ) {
450
			return false;
451
		}
452
		if ( $cancellation_line_item->quantity() > $qty ) {
453
			// decrement cancelled quantity
454
			$cancellation_line_item->set_quantity( $cancellation_line_item->quantity() - $qty );
455
		} else if ( $cancellation_line_item->quantity() == $qty ) {
456
			// decrement cancelled quantity in case anyone still has the object kicking around
457
			$cancellation_line_item->set_quantity( $cancellation_line_item->quantity() - $qty );
458
			// delete because quantity will end up as 0
459
			$cancellation_line_item->delete();
460
			// and attempt to destroy the object,
461
			// even though PHP won't actually destroy it until it needs the memory
462
			unset( $cancellation_line_item );
463
		} else {
464
			// what ?!?! negative quantity ?!?!
465
			throw new EE_Error(
466
				sprintf(
467
					__( 'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
468
						'event_espresso' ),
469
					$qty,
470
					$cancellation_line_item->quantity()
471
				)
472
			);
473
		}
474
		// increment ticket quantity
475
		$ticket_line_item->set_quantity( $ticket_line_item->quantity() + $qty );
476
		if ( $ticket_line_item->save_this_and_descendants() > 0 ) {
477
			EEH_Line_Item::get_grand_total_and_recalculate_everything( $ticket_line_item );
478
			return true;
479
		}
480
		return false;
481
	}
482
483
484
485
	/**
486
	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
487
	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
488
	 *
489
	 * @param EE_Line_Item $line_item
490
	 * @return \EE_Line_Item
491
	 */
492
	public static function get_grand_total_and_recalculate_everything( EE_Line_Item $line_item ){
493
		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item( $line_item );
494
		return $grand_total_line_item->recalculate_total_including_taxes();
495
	}
496
497
498
499
	/**
500
	 * Gets the line item which contains the subtotal of all the items
501
	 *
502
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
503
	 * @return \EE_Line_Item
504
	 * @throws \EE_Error
505
	 */
506
	public static function get_pre_tax_subtotal( EE_Line_Item $total_line_item ){
507
		$pre_tax_subtotal = $total_line_item->get_child_line_item( 'pre-tax-subtotal' );
508
		return $pre_tax_subtotal instanceof EE_Line_Item
509
			? $pre_tax_subtotal
510
			: self::create_pre_tax_subtotal( $total_line_item );
511
	}
512
513
514
515
	/**
516
	 * Gets the line item for the taxes subtotal
517
	 *
518
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
519
	 * @return \EE_Line_Item
520
	 * @throws \EE_Error
521
	 */
522
	public static function get_taxes_subtotal( EE_Line_Item $total_line_item ){
523
		$taxes = $total_line_item->get_child_line_item( 'taxes' );
524
		return $taxes ? $taxes : self::create_taxes_subtotal( $total_line_item );
525
	}
526
527
528
529
	/**
530
	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
531
	 *
532
	 * @param EE_Line_Item   $line_item
533
	 * @param EE_Transaction $transaction
534
	 * @return void
535
	 * @throws \EE_Error
536
	 */
537
	public static function set_TXN_ID( EE_Line_Item $line_item, $transaction = NULL ){
538
		if( $transaction ){
539
			/** @type EEM_Transaction $EEM_Transaction */
540
			$EEM_Transaction = EE_Registry::instance()->load_model( 'Transaction' );
541
			$TXN_ID = $EEM_Transaction->ensure_is_ID( $transaction );
542
			$line_item->set_TXN_ID( $TXN_ID );
543
		}
544
	}
545
546
547
548
	/**
549
	 * Creates a new default total line item for the transaction,
550
	 * and its tickets subtotal and taxes subtotal line items (and adds the
551
	 * existing taxes as children of the taxes subtotal line item)
552
	 *
553
	 * @param EE_Transaction $transaction
554
	 * @return \EE_Line_Item of type total
555
	 * @throws \EE_Error
556
	 */
557 View Code Duplication
	public static function create_total_line_item( $transaction = NULL ){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
558
		$total_line_item = EE_Line_Item::new_instance( array(
559
			'LIN_code'	=> 'total',
560
			'LIN_name'	=> __('Grand Total', 'event_espresso'),
561
			'LIN_type'	=> EEM_Line_Item::type_total,
562
			'OBJ_type'	=>'Transaction'
563
		));
564
		$total_line_item = apply_filters(
565
			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
566
			$total_line_item
567
		);
568
		self::set_TXN_ID( $total_line_item, $transaction );
569
		self::create_pre_tax_subtotal( $total_line_item, $transaction );
570
		self::create_taxes_subtotal( $total_line_item, $transaction );
571
		return $total_line_item;
572
	}
573
574
575
576
	/**
577
	 * Creates a default items subtotal line item
578
	 *
579
	 * @param EE_Line_Item   $total_line_item
580
	 * @param EE_Transaction $transaction
581
	 * @return EE_Line_Item
582
	 * @throws \EE_Error
583
	 */
584 View Code Duplication
	protected static function create_pre_tax_subtotal( EE_Line_Item $total_line_item, $transaction = NULL ){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
		$pre_tax_line_item = EE_Line_Item::new_instance( array(
586
			'LIN_code' 	=> 'pre-tax-subtotal',
587
			'LIN_name' 	=> __( 'Pre-Tax Subtotal', 'event_espresso' ),
588
			'LIN_type' 	=> EEM_Line_Item::type_sub_total
589
		) );
590
		$pre_tax_line_item = apply_filters(
591
			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
592
			$pre_tax_line_item
593
		);
594
		self::set_TXN_ID( $pre_tax_line_item, $transaction );
595
		$total_line_item->add_child_line_item( $pre_tax_line_item );
596
		self::create_event_subtotal( $pre_tax_line_item, $transaction );
597
		return $pre_tax_line_item;
598
	}
599
600
601
602
	/**
603
	 * Creates a line item for the taxes subtotal and finds all the tax prices
604
	 * and applies taxes to it
605
	 *
606
	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
607
	 * @param EE_Transaction $transaction
608
	 * @return EE_Line_Item
609
	 * @throws \EE_Error
610
	 */
611 View Code Duplication
	protected static function create_taxes_subtotal( EE_Line_Item $total_line_item, $transaction = NULL ){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
612
		$tax_line_item = EE_Line_Item::new_instance(array(
613
			'LIN_code'	=> 'taxes',
614
			'LIN_name' 	=> __('Taxes', 'event_espresso'),
615
			'LIN_type'	=> EEM_Line_Item::type_tax_sub_total,
616
			'LIN_order' => 1000,//this should always come last
617
		));
618
		$tax_line_item = apply_filters(
619
			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
620
			$tax_line_item
621
		);
622
		self::set_TXN_ID( $tax_line_item, $transaction );
623
		$total_line_item->add_child_line_item( $tax_line_item );
624
		//and lastly, add the actual taxes
625
		self::apply_taxes( $total_line_item );
626
		return $tax_line_item;
627
	}
628
629
630
631
	/**
632
	 * Creates a default items subtotal line item
633
	 *
634
	 * @param EE_Line_Item   $pre_tax_line_item
635
	 * @param EE_Transaction $transaction
636
	 * @param EE_Event       $event
637
	 * @return EE_Line_Item
638
	 * @throws \EE_Error
639
	 */
640
	public static function create_event_subtotal( EE_Line_Item $pre_tax_line_item, $transaction = NULL, $event = NULL ){
641
		$event_line_item = EE_Line_Item::new_instance(array(
642
			'LIN_code'	=> self::get_event_code( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 640 can be null; however, EEH_Line_Item::get_event_code() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
643
			'LIN_name' 	=> self::get_event_name( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 640 can be null; however, EEH_Line_Item::get_event_name() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
644
			'LIN_desc' 	=> self::get_event_desc( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 640 can be null; however, EEH_Line_Item::get_event_desc() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
645
			'LIN_type'	=> EEM_Line_Item::type_sub_total,
646
			'OBJ_type' 	=> 'Event',
647
			'OBJ_ID' 		=>  $event instanceof EE_Event ? $event->ID() : 0
648
		));
649
		$event_line_item = apply_filters(
650
			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
651
			$event_line_item
652
		);
653
		self::set_TXN_ID( $event_line_item, $transaction );
654
		$pre_tax_line_item->add_child_line_item( $event_line_item );
655
		return $event_line_item;
656
	}
657
658
659
660
	/**
661
	 * Gets what the event ticket's code SHOULD be
662
	 *
663
	 * @param EE_Event $event
664
	 * @return string
665
	 * @throws \EE_Error
666
	 */
667
	public static function get_event_code( $event ) {
668
		return 'event-' . ( $event instanceof EE_Event ? $event->ID() : '0' );
669
	}
670
671
	/**
672
	 * Gets the event name
673
	 * @param EE_Event $event
674
	 * @return string
675
	 */
676
	public static function get_event_name( $event ) {
677
		return $event instanceof EE_Event ? $event->name() : __( 'Event', 'event_espresso' );
678
	}
679
680
	/**
681
	 * Gets the event excerpt
682
	 * @param EE_Event $event
683
	 * @return string
684
	 */
685
	public static function get_event_desc( $event ) {
686
		return $event instanceof EE_Event ? $event->short_description() : '';
687
	}
688
689
	/**
690
	  * Given the grand total line item and a ticket, finds the event sub-total
691
	  * line item the ticket's purchase should be added onto
692
	  *
693
	  * @access public
694
	  * @param EE_Line_Item $grand_total the grand total line item
695
	  * @param EE_Ticket $ticket
696
	  * @throws \EE_Error
697
	  * @return EE_Line_Item
698
	  */
699
	public static function get_event_line_item_for_ticket( EE_Line_Item $grand_total, EE_Ticket $ticket ) {
700
		$first_datetime = $ticket->first_datetime();
701
		if ( ! $first_datetime instanceof EE_Datetime ) {
702
			throw new EE_Error(
703
				sprintf( __( 'The supplied ticket (ID %d) has no datetimes', 'event_espresso' ), $ticket->ID() )
704
			);
705
		}
706
		$event = $first_datetime->event();
707 View Code Duplication
		if ( ! $event instanceof EE_Event ) {
708
			throw new EE_Error(
709
				sprintf(
710
					__( 'The supplied ticket (ID %d) has no event data associated with it.', 'event_espresso' ),
711
					$ticket->ID()
712
				)
713
			);
714
		}
715
		$events_sub_total = EEH_Line_Item::get_event_line_item( $grand_total, $event );
716
		if ( ! $events_sub_total instanceof EE_Line_Item ) {
717
			throw new EE_Error(
718
				sprintf(
719
					__( 'There is no events sub-total for ticket %s on total line item %d', 'event_espresso' ),
720
					$ticket->ID(),
721
					$grand_total->ID()
722
				)
723
			);
724
		}
725
		return $events_sub_total;
726
	}
727
728
729
730
	/**
731
	 * Gets the event line item
732
	 *
733
	 * @param EE_Line_Item $grand_total
734
	 * @param EE_Event     $event
735
	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
736
	 * @throws \EE_Error
737
	 */
738
	public static function get_event_line_item( EE_Line_Item $grand_total, $event ) {
739
		/** @type EE_Event $event */
740
		$event = EEM_Event::instance()->ensure_is_obj( $event, true );
741
		$event_line_item = NULL;
742
		$found = false;
743
		foreach ( EEH_Line_Item::get_event_subtotals( $grand_total ) as $event_line_item ) {
744
			// default event subtotal, we should only ever find this the first time this method is called
745
			if ( ! $event_line_item->OBJ_ID() ) {
746
				// let's use this! but first... set the event details
747
				EEH_Line_Item::set_event_subtotal_details( $event_line_item, $event );
748
				$found = true;
749
				break;
750
			} else if ( $event_line_item->OBJ_ID() === $event->ID() ) {
751
				// found existing line item for this event in the cart, so break out of loop and use this one
752
				$found = true;
753
				break;
754
			}
755
		}
756
		if ( ! $found ) {
757
			//there is no event sub-total yet, so add it
758
			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal( $grand_total );
759
			// create a new "event" subtotal below that
760
			$event_line_item = EEH_Line_Item::create_event_subtotal( $pre_tax_subtotal, null, $event );
761
			// and set the event details
762
			EEH_Line_Item::set_event_subtotal_details( $event_line_item, $event );
763
		}
764
		return $event_line_item;
765
	}
766
767
768
769
	/**
770
	 * Creates a default items subtotal line item
771
	 *
772
	 * @param EE_Line_Item   $event_line_item
773
	 * @param EE_Event       $event
774
	 * @param EE_Transaction $transaction
775
	 * @return EE_Line_Item
776
	 * @throws \EE_Error
777
	 */
778
	public static function set_event_subtotal_details(
779
		EE_Line_Item $event_line_item,
780
		EE_Event $event,
781
		$transaction = null
782
	) {
783
		if ( $event instanceof EE_Event ) {
784
			$event_line_item->set_code( self::get_event_code( $event ) );
785
			$event_line_item->set_name( self::get_event_name( $event ) );
786
			$event_line_item->set_desc( self::get_event_desc( $event ) );
0 ignored issues
show
Bug introduced by
It seems like self::get_event_desc($event) targeting EEH_Line_Item::get_event_desc() can also be of type boolean; however, EE_Line_Item::set_desc() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
787
			$event_line_item->set_OBJ_ID( $event->ID() );
788
		}
789
		self::set_TXN_ID( $event_line_item, $transaction );
790
	}
791
792
793
794
	/**
795
	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
796
	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
797
	 * any old taxes are removed
798
	 *
799
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
800
	 * @throws \EE_Error
801
	 */
802
	public static function apply_taxes( EE_Line_Item $total_line_item ){
803
		/** @type EEM_Price $EEM_Price */
804
		$EEM_Price = EE_Registry::instance()->load_model( 'Price' );
805
		// get array of taxes via Price Model
806
		$ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
807
		ksort( $ordered_taxes );
808
		$taxes_line_item = self::get_taxes_subtotal( $total_line_item );
809
		//just to be safe, remove its old tax line items
810
		$taxes_line_item->delete_children_line_items();
811
		//loop thru taxes
812
		foreach ( $ordered_taxes as $order => $taxes ) {
813
			foreach ( $taxes as $tax ) {
814
				if ( $tax instanceof EE_Price ) {
815
					$tax_line_item = EE_Line_Item::new_instance(
816
						array(
817
							'LIN_name'       => $tax->name(),
818
							'LIN_desc'       => $tax->desc(),
819
							'LIN_percent'    => $tax->amount(),
820
							'LIN_is_taxable' => false,
821
							'LIN_order'      => $order,
822
							'LIN_total'      => 0,
823
							'LIN_type'       => EEM_Line_Item::type_tax,
824
							'OBJ_type'       => 'Price',
825
							'OBJ_ID'         => $tax->ID()
826
						)
827
					);
828
					$tax_line_item = apply_filters(
829
						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
830
						$tax_line_item
831
					);
832
					$taxes_line_item->add_child_line_item( $tax_line_item );
833
				}
834
			}
835
		}
836
		$total_line_item->recalculate_total_including_taxes();
837
	}
838
839
840
841
	/**
842
	 * Ensures that taxes have been applied to the order, if not applies them.
843
	 * Returns the total amount of tax
844
	 *
845
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
846
	 * @return float
847
	 * @throws \EE_Error
848
	 */
849
	public static function ensure_taxes_applied( $total_line_item ){
850
		$taxes_subtotal = self::get_taxes_subtotal( $total_line_item );
851
		if( ! $taxes_subtotal->children()){
852
			self::apply_taxes( $total_line_item );
853
		}
854
		return $taxes_subtotal->total();
855
	}
856
857
858
859
	/**
860
	 * Deletes ALL children of the passed line item
861
	 *
862
	 * @param EE_Line_Item $parent_line_item
863
	 * @return bool
864
	 * @throws \EE_Error
865
	 */
866
	public static function delete_all_child_items( EE_Line_Item $parent_line_item ) {
867
		$deleted = 0;
868
		foreach ( $parent_line_item->children() as $child_line_item ) {
869
			if ( $child_line_item instanceof EE_Line_Item ) {
870
				$deleted += EEH_Line_Item::delete_all_child_items( $child_line_item );
871
				if ( $child_line_item->ID() ) {
872
					$child_line_item->delete();
873
					unset( $child_line_item );
874
				} else {
875
					$parent_line_item->delete_child_line_item( $child_line_item->code() );
876
				}
877
				$deleted++;
878
			}
879
		}
880
		return $deleted;
881
	}
882
883
884
885
	/**
886
	 * Deletes the line items as indicated by the line item code(s) provided,
887
	 * regardless of where they're found in the line item tree. Automatically
888
	 * re-calculates the line item totals and updates the related transaction. But
889
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
890
	 * should probably change because of this).
891
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
892
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
893
	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
894
	 * @param array|bool|string $line_item_codes
895
	 * @return int number of items successfully removed
896
	 */
897
	public static function delete_items( EE_Line_Item $total_line_item, $line_item_codes = FALSE ) {
898
899
		if( $total_line_item->type() !== EEM_Line_Item::type_total ){
900
			EE_Error::doing_it_wrong(
901
				'EEH_Line_Item::delete_items',
902
				__(
903
					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
904
					'event_espresso'
905
				),
906
				'4.6.18'
907
			);
908
		}
909
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, '' );
910
911
		// check if only a single line_item_id was passed
912
		if ( ! empty( $line_item_codes ) && ! is_array( $line_item_codes )) {
913
			// place single line_item_id in an array to appear as multiple line_item_ids
914
			$line_item_codes = array ( $line_item_codes );
915
		}
916
		$removals = 0;
917
		// cycle thru line_item_ids
918
		foreach ( $line_item_codes as $line_item_id ) {
0 ignored issues
show
Bug introduced by
The expression $line_item_codes of type array|boolean|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
919
			$removals += $total_line_item->delete_child_line_item($line_item_id);
920
		}
921
922
		if ( $removals > 0 ) {
923
			$total_line_item->recalculate_taxes_and_tax_total();
924
			return $removals;
925
		} else {
926
			return FALSE;
927
		}
928
	}
929
930
931
932
	/**
933
	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
934
	 * tax and updates the total line item accordingly
935
	 *
936
	 * @param EE_Line_Item $total_line_item
937
	 * @param float        $amount
938
	 * @param string       $name
939
	 * @param string       $description
940
	 * @param string       $code
941
	 * @param boolean      $add_to_existing_line_item
942
	 *                          if true, and a duplicate line item with the same code is found,
943
	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
944
	 * @return EE_Line_Item the new tax line item created
945
	 * @throws \EE_Error
946
	 */
947
	public static function set_total_tax_to(
948
		EE_Line_Item $total_line_item,
949
		$amount,
950
		$name = null,
951
		$description = null,
952
		$code = null,
953
		$add_to_existing_line_item = false
954
	) {
955
		$tax_subtotal = self::get_taxes_subtotal( $total_line_item );
956
            $taxable_total = $total_line_item->taxable_total();
957
958
            if( $add_to_existing_line_item ) {
959
                $new_tax = $tax_subtotal->get_child_line_item( $code );
960
	            EEM_Line_Item::instance()->delete(
961
		            array( array( 'LIN_code' => array( '!=', $code ), 'LIN_parent' => $tax_subtotal->ID() ) )
962
	            );
963
            } else {
964
                $new_tax = null;
965
                $tax_subtotal->delete_children_line_items();
966
            }
967
            if( $new_tax ) {
968
                $new_tax->set_total( $new_tax->total() + $amount );
969
                $new_tax->set_percent( $taxable_total ? $new_tax->total() / $taxable_total * 100 : 0 );
970
            } else {
971
                //no existing tax item. Create it
972
				$new_tax = EE_Line_Item::new_instance( array(
973
					'TXN_ID'      => $total_line_item->TXN_ID(),
974
					'LIN_name'    => $name ? $name : __( 'Tax', 'event_espresso' ),
975
					'LIN_desc'    => $description ? $description : '',
976
					'LIN_percent' => $taxable_total ? ( $amount / $taxable_total * 100 ) : 0,
977
					'LIN_total'   => $amount,
978
					'LIN_parent'  => $tax_subtotal->ID(),
979
					'LIN_type'    => EEM_Line_Item::type_tax,
980
					'LIN_code'    => $code
981
				) );
982
			}
983
984
            $new_tax = apply_filters(
985
				'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
986
				$new_tax,
987
				$total_line_item
988
            );
989
            $new_tax->save();
990
            $tax_subtotal->set_total( $new_tax->total() );
991
            $tax_subtotal->save();
992
            $total_line_item->recalculate_total_including_taxes();
993
            return $new_tax;
994
	}
995
996
997
998
	/**
999
	 * Makes all the line items which are children of $line_item taxable (or not).
1000
	 * Does NOT save the line items
1001
	 * @param EE_Line_Item $line_item
1002
	 * @param string $code_substring_for_whitelist if this string is part of the line item's code
1003
	 *  it will be whitelisted (ie, except from becoming taxable)
1004
	 * @param boolean $taxable
1005
	 */
1006
	public static function set_line_items_taxable(
1007
		EE_Line_Item $line_item,
1008
		$taxable = true,
1009
		$code_substring_for_whitelist = null
1010
	) {
1011
		$whitelisted = false;
1012
		if( $code_substring_for_whitelist !== null ) {
1013
			$whitelisted = strpos( $line_item->code(), $code_substring_for_whitelist ) !== false ? true : false;
1014
		}
1015
		if( ! $whitelisted && $line_item->is_line_item() ) {
1016
			$line_item->set_is_taxable( $taxable );
1017
		}
1018
		foreach( $line_item->children() as $child_line_item ) {
1019
			EEH_Line_Item::set_line_items_taxable( $child_line_item, $taxable, $code_substring_for_whitelist );
1020
		}
1021
	}
1022
1023
1024
1025
	/**
1026
	 * Gets all descendants that are event subtotals
1027
	 *
1028
	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1029
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1030
	 * @return EE_Line_Item[]
1031
	 */
1032
	public static function get_event_subtotals( EE_Line_Item $parent_line_item ) {
1033
		return self::get_subtotals_of_object_type( $parent_line_item, 'Event' );
1034
	}
1035
1036
1037
1038
	/**
1039
	 * Gets all descendants subtotals that match the supplied object type
1040
	 *
1041
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1042
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1043
	 * @param string $obj_type
1044
	 * @return EE_Line_Item[]
1045
	 */
1046
	public static function get_subtotals_of_object_type( EE_Line_Item $parent_line_item, $obj_type = '' ) {
1047
		return self::_get_descendants_by_type_and_object_type(
1048
			$parent_line_item,
1049
			EEM_Line_Item::type_sub_total,
1050
			$obj_type
1051
		);
1052
	}
1053
1054
1055
1056
	/**
1057
	 * Gets all descendants that are tickets
1058
	 *
1059
	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1060
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1061
	 * @return EE_Line_Item[]
1062
	 */
1063
	public static function get_ticket_line_items( EE_Line_Item $parent_line_item ) {
1064
		return self::get_line_items_of_object_type( $parent_line_item, 'Ticket' );
1065
	}
1066
1067
1068
1069
	/**
1070
	 * Gets all descendants subtotals that match the supplied object type
1071
	 *
1072
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1073
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1074
	 * @param string $obj_type
1075
	 * @return EE_Line_Item[]
1076
	 */
1077
	public static function get_line_items_of_object_type( EE_Line_Item $parent_line_item, $obj_type = '' ) {
1078
		return self::_get_descendants_by_type_and_object_type( $parent_line_item, EEM_Line_Item::type_line_item, $obj_type );
1079
	}
1080
1081
1082
1083
	/**
1084
	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1085
	 * @uses  EEH_Line_Item::get_descendants_of_type()
1086
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1087
	 * @return EE_Line_Item[]
1088
	 */
1089
	public static function get_tax_descendants( EE_Line_Item $parent_line_item ) {
1090
		return EEH_Line_Item::get_descendants_of_type( $parent_line_item, EEM_Line_Item::type_tax );
1091
	}
1092
1093
1094
1095
	/**
1096
	 * Gets all the real items purchased which are children of this item
1097
	 * @uses  EEH_Line_Item::get_descendants_of_type()
1098
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1099
	 * @return EE_Line_Item[]
1100
	 */
1101
	public static function get_line_item_descendants( EE_Line_Item $parent_line_item ) {
1102
		return EEH_Line_Item::get_descendants_of_type( $parent_line_item, EEM_Line_Item::type_line_item );
1103
	}
1104
1105
1106
1107
	/**
1108
	 * Gets all descendants of supplied line item that match the supplied line item type
1109
	 *
1110
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1111
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1112
	 * @param string $line_item_type one of the EEM_Line_Item constants
1113
	 * @return EE_Line_Item[]
1114
	 */
1115
	public static function get_descendants_of_type( EE_Line_Item $parent_line_item, $line_item_type ) {
1116
		return self::_get_descendants_by_type_and_object_type( $parent_line_item, $line_item_type, NULL );
1117
	}
1118
1119
1120
1121
	/**
1122
	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1123
	 *
1124
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1125
	 * @param string $line_item_type one of the EEM_Line_Item constants
1126
	 * @param string | NULL $obj_type object model class name (minus prefix) or NULL to ignore object type when searching
1127
	 * @return EE_Line_Item[]
1128
	 */
1129
	protected static function _get_descendants_by_type_and_object_type(
1130
		EE_Line_Item $parent_line_item,
1131
		$line_item_type,
1132
		$obj_type = null
1133
	) {
1134
		$objects = array();
1135
		foreach ( $parent_line_item->children() as $child_line_item ) {
1136
			if ( $child_line_item instanceof EE_Line_Item ) {
1137
				if (
1138
					$child_line_item->type() === $line_item_type
1139
				    && (
1140
						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1141
					)
1142
				) {
1143
					$objects[] = $child_line_item;
1144
				} else {
1145
					//go-through-all-its children looking for more matches
1146
					$objects = array_merge(
1147
						$objects,
1148
						self::_get_descendants_by_type_and_object_type(
1149
							$child_line_item,
1150
							$line_item_type,
1151
							$obj_type
1152
						)
1153
					);
1154
				}
1155
			}
1156
		}
1157
		return $objects;
1158
	}
1159
1160
1161
1162
	/**
1163
	 * Gets all descendants subtotals that match the supplied object type
1164
	 *
1165
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1166
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1167
	 * @param string $OBJ_type object type (like Event)
1168
	 * @param array $OBJ_IDs array of OBJ_IDs
1169
	 * @return EE_Line_Item[]
1170
	 */
1171
	public static function get_line_items_by_object_type_and_IDs(
1172
		EE_Line_Item $parent_line_item,
1173
		$OBJ_type = '',
1174
		$OBJ_IDs = array()
1175
	) {
1176
		return self::_get_descendants_by_object_type_and_object_ID( $parent_line_item, $OBJ_type, $OBJ_IDs );
1177
	}
1178
1179
1180
1181
	/**
1182
	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1183
	 *
1184
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1185
	 * @param string $OBJ_type object type (like Event)
1186
	 * @param array $OBJ_IDs array of OBJ_IDs
1187
	 * @return EE_Line_Item[]
1188
	 */
1189
	protected static function _get_descendants_by_object_type_and_object_ID(
1190
		EE_Line_Item $parent_line_item,
1191
		$OBJ_type,
1192
		$OBJ_IDs
1193
	) {
1194
		$objects = array();
1195
		foreach ( $parent_line_item->children() as $child_line_item ) {
1196
			if ( $child_line_item instanceof EE_Line_Item ) {
1197
				if (
1198
					is_array( $OBJ_IDs )
1199
					&& $child_line_item->OBJ_type() === $OBJ_type
1200
					&& in_array( $child_line_item->OBJ_ID(), $OBJ_IDs )
1201
				) {
1202
					$objects[] = $child_line_item;
1203
				} else {
1204
					//go-through-all-its children looking for more matches
1205
					$objects = array_merge(
1206
						$objects,
1207
						self::_get_descendants_by_object_type_and_object_ID(
1208
							$child_line_item,
1209
							$OBJ_type,
1210
							$OBJ_IDs
1211
						)
1212
					);
1213
				}
1214
			}
1215
		}
1216
		return $objects;
1217
	}
1218
1219
1220
1221
	/**
1222
	 * Uses a breadth-first-search in order to find the nearest descendant of
1223
	 * the specified type and returns it, else NULL
1224
	 *
1225
	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1226
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1227
	 * @param string $type like one of the EEM_Line_Item::type_*
1228
	 * @return EE_Line_Item
1229
	 */
1230
	public static function get_nearest_descendant_of_type( EE_Line_Item $parent_line_item, $type ) {
1231
		return self::_get_nearest_descendant( $parent_line_item, 'LIN_type' , $type );
1232
	}
1233
1234
1235
1236
	/**
1237
	 * Uses a breadth-first-search in order to find the nearest descendant
1238
	 * having the specified LIN_code and returns it, else NULL
1239
	 *
1240
	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1241
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1242
	 * @param string $code any value used for LIN_code
1243
	 * @return EE_Line_Item
1244
	 */
1245
	public static function get_nearest_descendant_having_code( EE_Line_Item $parent_line_item, $code ) {
1246
		return self::_get_nearest_descendant( $parent_line_item, 'LIN_code' , $code );
1247
	}
1248
1249
1250
1251
	/**
1252
	 * Uses a breadth-first-search in order to find the nearest descendant
1253
	 * having the specified LIN_code and returns it, else NULL
1254
	 *
1255
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1256
	 * @param string $search_field  name of EE_Line_Item property
1257
	 * @param string $value any value stored in $search_field
1258
	 * @return EE_Line_Item
1259
	 */
1260
	protected static function _get_nearest_descendant( EE_Line_Item $parent_line_item, $search_field, $value ) {
1261
		foreach( $parent_line_item->children() as $child ){
1262
			if ( $child->get( $search_field ) == $value ){
1263
				return $child;
1264
			}
1265
		}
1266
		foreach( $parent_line_item->children() as $child ){
1267
			$descendant_found = self::_get_nearest_descendant( $child, $search_field, $value );
1268
			if ( $descendant_found ){
1269
				return $descendant_found;
1270
			}
1271
		}
1272
		return NULL;
1273
	}
1274
1275
1276
1277
	/**
1278
	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1279
	 * else recursively walks up the line item tree until a parent of type total is found,
1280
	 *
1281
	 * @param EE_Line_Item $line_item
1282
	 * @return \EE_Line_Item
1283
	 * @throws \EE_Error
1284
	 */
1285
	public static function find_transaction_grand_total_for_line_item( EE_Line_Item $line_item ){
1286
		if ( $line_item->TXN_ID() ) {
1287
			$total_line_item = $line_item->transaction()->total_line_item( false );
1288
			if ( $total_line_item instanceof EE_Line_Item ) {
1289
				return $total_line_item;
1290
			}
1291
		} else {
1292
			$line_item_parent = $line_item->parent();
1293
			if ( $line_item_parent instanceof EE_Line_Item ) {
1294
				if ( $line_item_parent->is_total() ) {
1295
					return $line_item_parent;
1296
				}
1297
				return EEH_Line_Item::find_transaction_grand_total_for_line_item( $line_item_parent );
1298
			}
1299
		}
1300
		throw new EE_Error(
1301
			sprintf(
1302
				__( 'A valid grand total for line item %1$d was not found.', 'event_espresso' ),
1303
				$line_item->ID()
1304
			)
1305
		);
1306
	}
1307
1308
1309
1310
1311
	/**
1312
	 * Prints out a representation of the line item tree
1313
	 *
1314
	 * @param EE_Line_Item $line_item
1315
	 * @param int          $indentation
1316
	 * @return void
1317
	 * @throws \EE_Error
1318
	 */
1319
	public static function visualize( EE_Line_Item $line_item, $indentation = 0 ){
1320
		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1321
		if ( ! $indentation ) {
1322
			echo defined( 'EE_TESTS_DIR' ) ? "\n" : '<br />';
1323
		}
1324
		for( $i = 0; $i < $indentation; $i++ ){
1325
			echo ". ";
1326
		}
1327
		$breakdown = '';
1328
		if ( $line_item->is_line_item()){
1329
			if ( $line_item->is_percent() ) {
1330
				$breakdown = "{$line_item->percent()}%";
1331
			} else {
1332
				$breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1333
			}
1334
		}
1335
		echo $line_item->name() . " ( ID:{$line_item->ID()} ) : {$line_item->type()} " . '$' . "{$line_item->total()}";
1336
		if ( $breakdown ) {
1337
			echo " ( {$breakdown} )";
1338
		}
1339
		if( $line_item->is_taxable() ){
1340
			echo "  * taxable";
1341
		}
1342
		if( $line_item->children() ){
1343
			foreach($line_item->children() as $child){
1344
				self::visualize($child, $indentation + 1);
1345
			}
1346
		}
1347
	}
1348
1349
1350
1351
	/**
1352
	 * Calculates the registration's final price, taking into account that they
1353
	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1354
	 * and receive a portion of any transaction-wide discounts.
1355
	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1356
	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1357
	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1358
	 * and brent's final price should be $5.50.
1359
	 *
1360
	 * In order to do this, we basically need to traverse the line item tree calculating
1361
	 * the running totals (just as if we were recalculating the total), but when we identify
1362
	 * regular line items, we need to keep track of their share of the grand total.
1363
	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1364
	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1365
	 * when there are non-taxable items; otherwise they would be the same)
1366
	 *
1367
	 * @param EE_Line_Item $line_item
1368
	 * @param array $billable_ticket_quantities 		array of EE_Ticket IDs and their corresponding quantity that
1369
	 *                                          									can be included in price calculations at this moment
1370
	 * @return array 		keys are line items for tickets IDs and values are their share of the running total,
1371
	 *                                          plus the key 'total', and 'taxable' which also has keys of all the ticket IDs. Eg
1372
	 *                                          array(
1373
	 *                                          12 => 4.3
1374
	 *                                          23 => 8.0
1375
	 *                                          'total' => 16.6,
1376
	 *                                          'taxable' => array(
1377
	 *                                          12 => 10,
1378
	 *                                          23 => 4
1379
	 *                                          ).
1380
	 *                                          So to find which registrations have which final price, we need to find which line item
1381
	 *                                          is theirs, which can be done with
1382
	 *                                          `EEM_Line_Item::instance()->get_line_item_for_registration( $registration );`
1383
	 */
1384
	public static function calculate_reg_final_prices_per_line_item( EE_Line_Item $line_item, $billable_ticket_quantities = array() ) {
1385
		//init running grand total if not already
1386
		if ( ! isset( $running_totals[ 'total' ] ) ) {
0 ignored issues
show
Bug introduced by
The variable $running_totals seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
1387
			$running_totals[ 'total' ] = 0;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$running_totals was never initialized. Although not strictly required by PHP, it is generally a good practice to add $running_totals = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1388
		}
1389
		if( ! isset( $running_totals[ 'taxable' ] ) ) {
1390
			$running_totals[ 'taxable' ] = array( 'total' => 0 );
1391
		}
1392
		foreach ( $line_item->children() as $child_line_item ) {
1393
			switch ( $child_line_item->type() ) {
1394
1395
				case EEM_Line_Item::type_sub_total :
1396
					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item( $child_line_item, $billable_ticket_quantities );
1397
					//combine arrays but preserve numeric keys
1398
					$running_totals = array_replace_recursive( $running_totals_from_subtotal, $running_totals );
1399
					$running_totals[ 'total' ] += $running_totals_from_subtotal[ 'total' ];
1400
					$running_totals[ 'taxable'][ 'total' ] += $running_totals_from_subtotal[ 'taxable' ][ 'total' ];
1401
					break;
1402
1403
				case EEM_Line_Item::type_tax_sub_total :
1404
1405
					//find how much the taxes percentage is
1406
					if ( $child_line_item->percent() !== 0 ) {
1407
						$tax_percent_decimal = $child_line_item->percent() / 100;
1408
					} else {
1409
						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1410
					}
1411
					//and apply to all the taxable totals, and add to the pretax totals
1412
					foreach ( $running_totals as $line_item_id => $this_running_total ) {
1413
						//"total" and "taxable" array key is an exception
1414
						if ( $line_item_id === 'taxable' ) {
1415
							continue;
1416
						}
1417
						$taxable_total = $running_totals[ 'taxable' ][ $line_item_id ];
1418
						$running_totals[ $line_item_id ] += ( $taxable_total * $tax_percent_decimal );
1419
					}
1420
					break;
1421
1422
				case EEM_Line_Item::type_line_item :
1423
1424
					// ticket line items or ????
1425
					if ( $child_line_item->OBJ_type() === 'Ticket' ) {
1426
						// kk it's a ticket
1427
						if ( isset( $running_totals[ $child_line_item->ID() ] ) ) {
1428
							//huh? that shouldn't happen.
1429
							$running_totals[ 'total' ] += $child_line_item->total();
1430
						} else {
1431
							//its not in our running totals yet. great.
1432
							if ( $child_line_item->is_taxable() ) {
1433
								$taxable_amount = $child_line_item->unit_price();
1434
							} else {
1435
								$taxable_amount = 0;
1436
							}
1437
							// are we only calculating totals for some tickets?
1438
							if ( isset( $billable_ticket_quantities[ $child_line_item->OBJ_ID() ] ) ) {
1439
								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1440
								$running_totals[ $child_line_item->ID() ] = $quantity
1441
									? $child_line_item->unit_price()
1442
									: 0;
1443
								$running_totals[ 'taxable' ][ $child_line_item->ID() ] = $quantity
1444
									? $taxable_amount
1445
									: 0;
1446
							} else {
1447
								$quantity = $child_line_item->quantity();
1448
								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1449
								$running_totals[ 'taxable' ][ $child_line_item->ID() ] = $taxable_amount;
1450
							}
1451
							$running_totals[ 'taxable' ][ 'total' ] += $taxable_amount * $quantity;
1452
							$running_totals[ 'total' ] += $child_line_item->unit_price() * $quantity;
1453
						}
1454
					} else {
1455
						// it's some other type of item added to the cart
1456
						// it should affect the running totals
1457
						// basically we want to convert it into a PERCENT modifier. Because
1458
						// more clearly affect all registration's final price equally
1459
						$line_items_percent_of_running_total = $running_totals[ 'total' ] > 0
1460
							? ( $child_line_item->total() / $running_totals[ 'total' ] ) + 1
1461
							: 1;
1462
						foreach ( $running_totals as $line_item_id => $this_running_total ) {
1463
							//the "taxable" array key is an exception
1464
							if ( $line_item_id === 'taxable' ) {
1465
								continue;
1466
							}
1467
							// update the running totals
1468
							// yes this actually even works for the running grand total!
1469
							$running_totals[ $line_item_id ] =
1470
								$line_items_percent_of_running_total * $this_running_total;
1471
1472
							if ( $child_line_item->is_taxable() ) {
1473
								$running_totals[ 'taxable' ][ $line_item_id ] =
1474
									$line_items_percent_of_running_total * $running_totals[ 'taxable' ][ $line_item_id ];
1475
							}
1476
						}
1477
					}
1478
					break;
1479
			}
1480
		}
1481
		return $running_totals;
1482
	}
1483
1484
1485
1486
	/**
1487
	 * @param \EE_Line_Item $total_line_item
1488
	 * @param \EE_Line_Item $ticket_line_item
1489
	 * @return float | null
1490
	 * @throws \OutOfRangeException
1491
	 */
1492
	public static function calculate_final_price_for_ticket_line_item( \EE_Line_Item $total_line_item, \EE_Line_Item $ticket_line_item ) {
1493
		static $final_prices_per_ticket_line_item = array();
1494
		if ( empty( $final_prices_per_ticket_line_item ) ) {
1495
			$final_prices_per_ticket_line_item = \EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1496
				$total_line_item
1497
			);
1498
		}
1499
		//ok now find this new registration's final price
1500
		if ( isset( $final_prices_per_ticket_line_item[ $ticket_line_item->ID() ] ) ) {
1501
			return $final_prices_per_ticket_line_item[ $ticket_line_item->ID() ];
1502
		}
1503
		$message = sprintf(
1504
			__(
1505
				'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1506
				'event_espresso'
1507
			),
1508
			$ticket_line_item->ID()
1509
		);
1510
		if ( WP_DEBUG ) {
1511
			throw new \OutOfRangeException( $message );
1512
		} else {
1513
			EE_Log::instance()->log( __CLASS__, __FUNCTION__, $message );
1514
		}
1515
		return null;
1516
	}
1517
1518
1519
1520
	/**
1521
	 * Creates a duplicate of the line item tree, except only includes billable items
1522
	 * and the portion of line items attributed to billable things
1523
	 *
1524
	 * @param EE_Line_Item      $line_item
1525
	 * @param EE_Registration[] $registrations
1526
	 * @return \EE_Line_Item
1527
	 * @throws \EE_Error
1528
	 */
1529
	public static function billable_line_item_tree( EE_Line_Item $line_item, $registrations ) {
1530
		$copy_li = EEH_Line_Item::billable_line_item( $line_item, $registrations );
1531
		foreach ( $line_item->children() as $child_li ) {
1532
			$copy_li->add_child_line_item( EEH_Line_Item::billable_line_item_tree( $child_li, $registrations ) );
1533
		}
1534
		//if this is the grand total line item, make sure the totals all add up
1535
		//(we could have duplicated this logic AS we copied the line items, but
1536
		//it seems DRYer this way)
1537
		if ( $copy_li->type() === EEM_Line_Item::type_total ) {
1538
			$copy_li->recalculate_total_including_taxes();
1539
		}
1540
		return $copy_li;
1541
	}
1542
1543
1544
1545
	/**
1546
	 * Creates a new, unsaved line item from $line_item that factors in the
1547
	 * number of billable registrations on $registrations.
1548
	 *
1549
	 * @param EE_Line_Item      $line_item
1550
	 * @return EE_Line_Item
1551
	 * @throws \EE_Error
1552
	 * @param EE_Registration[] $registrations
1553
	 */
1554
	public static function billable_line_item( EE_Line_Item $line_item, $registrations ) {
1555
		$new_li_fields = $line_item->model_field_array();
1556
		if ( $line_item->type() === EEM_Line_Item::type_line_item &&
1557
			$line_item->OBJ_type() === 'Ticket'
1558
		) {
1559
			$count = 0;
1560
			foreach ( $registrations as $registration ) {
1561
				if ( $line_item->OBJ_ID() === $registration->ticket_ID() &&
1562
					in_array( $registration->status_ID(), EEM_Registration::reg_statuses_that_allow_payment() )
1563
				) {
1564
					$count++;
1565
				}
1566
			}
1567
			$new_li_fields[ 'LIN_quantity' ] = $count;
1568
		}
1569
		//don't set the total. We'll leave that up to the code that calculates it
1570
		unset( $new_li_fields[ 'LIN_ID' ], $new_li_fields[ 'LIN_parent' ], $new_li_fields[ 'LIN_total' ] );
1571
		return EE_Line_Item::new_instance( $new_li_fields );
1572
	}
1573
1574
1575
1576
	/**
1577
	 * Returns a modified line item tree where all the subtotals which have a total of 0
1578
	 * are removed, and line items with a quantity of 0
1579
	 *
1580
	 * @param EE_Line_Item $line_item |null
1581
	 * @return \EE_Line_Item|null
1582
	 * @throws \EE_Error
1583
	 */
1584
	public static function non_empty_line_items( EE_Line_Item $line_item ) {
1585
		$copied_li = EEH_Line_Item::non_empty_line_item( $line_item );
1586
		if ( $copied_li === null ) {
1587
			return null;
1588
		}
1589
		//if this is an event subtotal, we want to only include it if it
1590
		//has a non-zero total and at least one ticket line item child
1591
		$ticket_children = 0;
1592
		foreach ( $line_item->children() as $child_li ) {
1593
			$child_li_copy = EEH_Line_Item::non_empty_line_items( $child_li );
1594
			if ( $child_li_copy !== null ) {
1595
				$copied_li->add_child_line_item( $child_li_copy );
1596
				if ( $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1597
					$child_li_copy->OBJ_type() === 'Ticket'
1598
				) {
1599
					$ticket_children++;
1600
				}
1601
			}
1602
		}
1603
		//if this is an event subtotal with NO ticket children
1604
		//we basically want to ignore it
1605
		if (
1606
			$ticket_children === 0
1607
			&& $line_item->type() === EEM_Line_Item::type_sub_total
1608
			&& $line_item->OBJ_type() === 'Event'
1609
			&& $line_item->total() === 0
1610
		) {
1611
			return null;
1612
		}
1613
		return $copied_li;
1614
	}
1615
1616
1617
1618
	/**
1619
	 * Creates a new, unsaved line item, but if it's a ticket line item
1620
	 * with a total of 0, or a subtotal of 0, returns null instead
1621
	 *
1622
	 * @param EE_Line_Item $line_item
1623
	 * @return EE_Line_Item
1624
	 * @throws \EE_Error
1625
	 */
1626
	public static function non_empty_line_item( EE_Line_Item $line_item ) {
1627 View Code Duplication
		if ( $line_item->type() === EEM_Line_Item::type_line_item &&
1628
			$line_item->OBJ_type() === 'Ticket' &&
1629
			$line_item->quantity() === 0
1630
		) {
1631
			return null;
1632
		}
1633
		$new_li_fields = $line_item->model_field_array();
1634
		//don't set the total. We'll leave that up to the code that calculates it
1635
		unset( $new_li_fields[ 'LIN_ID' ], $new_li_fields[ 'LIN_parent' ] );
1636
		return EE_Line_Item::new_instance( $new_li_fields );
1637
	}
1638
1639
1640
1641
	/**************************************** @DEPRECATED METHODS ****************************************/
1642
	/**
1643
	 * @deprecated
1644
	 * @param EE_Line_Item $total_line_item
1645
	 * @return \EE_Line_Item
1646
	 * @throws \EE_Error
1647
	 */
1648
	public static function get_items_subtotal( EE_Line_Item $total_line_item ){
1649
		EE_Error::doing_it_wrong( 'EEH_Line_Item::get_items_subtotal()', __('Method replaced with EEH_Line_Item::get_pre_tax_subtotal()', 'event_espresso'), '4.6.0' );
1650
		return self::get_pre_tax_subtotal( $total_line_item );
1651
	}
1652
1653
1654
1655
	/**
1656
	 * @deprecated
1657
	 * @param EE_Transaction $transaction
1658
	 * @return \EE_Line_Item
1659
	 * @throws \EE_Error
1660
	 */
1661
	public static function create_default_total_line_item( $transaction = NULL) {
1662
		EE_Error::doing_it_wrong( 'EEH_Line_Item::create_default_total_line_item()', __('Method replaced with EEH_Line_Item::create_total_line_item()', 'event_espresso'), '4.6.0' );
1663
		return self::create_total_line_item( $transaction );
1664
	}
1665
1666
1667
1668
	/**
1669
	 * @deprecated
1670
	 * @param EE_Line_Item   $total_line_item
1671
	 * @param EE_Transaction $transaction
1672
	 * @return \EE_Line_Item
1673
	 * @throws \EE_Error
1674
	 */
1675
	public static function create_default_tickets_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1676
		EE_Error::doing_it_wrong( 'EEH_Line_Item::create_default_tickets_subtotal()', __('Method replaced with EEH_Line_Item::create_pre_tax_subtotal()', 'event_espresso'), '4.6.0' );
1677
		return self::create_pre_tax_subtotal( $total_line_item, $transaction );
1678
	}
1679
1680
1681
1682
	/**
1683
	 * @deprecated
1684
	 * @param EE_Line_Item   $total_line_item
1685
	 * @param EE_Transaction $transaction
1686
	 * @return \EE_Line_Item
1687
	 * @throws \EE_Error
1688
	 */
1689
	public static function create_default_taxes_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1690
		EE_Error::doing_it_wrong( 'EEH_Line_Item::create_default_taxes_subtotal()', __('Method replaced with EEH_Line_Item::create_taxes_subtotal()', 'event_espresso'), '4.6.0' );
1691
		return self::create_taxes_subtotal( $total_line_item, $transaction );
1692
	}
1693
1694
1695
1696
	/**
1697
	 * @deprecated
1698
	 * @param EE_Line_Item   $total_line_item
1699
	 * @param EE_Transaction $transaction
1700
	 * @return \EE_Line_Item
1701
	 * @throws \EE_Error
1702
	 */
1703
	public static function create_default_event_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1704
		EE_Error::doing_it_wrong( 'EEH_Line_Item::create_default_event_subtotal()', __('Method replaced with EEH_Line_Item::create_event_subtotal()', 'event_espresso'), '4.6.0' );
1705
		return self::create_event_subtotal( $total_line_item, $transaction );
1706
	}
1707
1708
1709
1710
}
1711
// End of file EEH_Line_Item.helper.php
1712