Completed
Branch BUG-9961-paypal-pro-items (52e78c)
by
unknown
49:07 queued 32:43
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 View Code Duplication
		if ( $ticket_line_item->save_this_and_descendants() > 0 ) {
414
			// decrement parent line item quantity
415
			$event_line_item = $ticket_line_item->parent();
416
			if ( $event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event' ) {
417
				$event_line_item->set_quantity( $event_line_item->quantity() - $qty );
418
				$event_line_item->save();
419
			}
420
			EEH_Line_Item::get_grand_total_and_recalculate_everything( $ticket_line_item );
421
			return true;
422
		}
423
		return false;
424
	}
425
426
427
428
	/**
429
	 * reinstates (un-cancels?) a previously canceled ticket line item,
430
	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
431
	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
432
	 *
433
	 * @param EE_Line_Item $ticket_line_item
434
	 * @param int          $qty
435
	 * @return bool success
436
	 * @throws \EE_Error
437
	 */
438
	public static function reinstate_canceled_ticket_line_item( EE_Line_Item $ticket_line_item, $qty = 1 ) {
439
		// validate incoming line_item
440 View Code Duplication
		if ( $ticket_line_item->OBJ_type() !== 'Ticket' ) {
441
			throw new EE_Error(
442
				sprintf(
443
					__( 'The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso' ),
444
					$ticket_line_item->type()
445
				)
446
			);
447
		}
448
		// get cancellation sub line item
449
		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
450
			$ticket_line_item,
451
			EEM_Line_Item::type_cancellation
452
		);
453
		$cancellation_line_item = reset( $cancellation_line_item );
454
		// verify that this ticket was indeed previously cancelled
455
		if ( ! $cancellation_line_item instanceof EE_Line_Item ) {
456
			return false;
457
		}
458
		if ( $cancellation_line_item->quantity() > $qty ) {
459
			// decrement cancelled quantity
460
			$cancellation_line_item->set_quantity( $cancellation_line_item->quantity() - $qty );
461
		} else if ( $cancellation_line_item->quantity() == $qty ) {
462
			// decrement cancelled quantity in case anyone still has the object kicking around
463
			$cancellation_line_item->set_quantity( $cancellation_line_item->quantity() - $qty );
464
			// delete because quantity will end up as 0
465
			$cancellation_line_item->delete();
466
			// and attempt to destroy the object,
467
			// even though PHP won't actually destroy it until it needs the memory
468
			unset( $cancellation_line_item );
469
		} else {
470
			// what ?!?! negative quantity ?!?!
471
			throw new EE_Error(
472
				sprintf(
473
					__( 'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
474
						'event_espresso' ),
475
					$qty,
476
					$cancellation_line_item->quantity()
477
				)
478
			);
479
		}
480
		// increment ticket quantity
481
		$ticket_line_item->set_quantity( $ticket_line_item->quantity() + $qty );
482 View Code Duplication
		if ( $ticket_line_item->save_this_and_descendants() > 0 ) {
483
			// increment parent line item quantity
484
			$event_line_item = $ticket_line_item->parent();
485
			if ( $event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event' ) {
486
				$event_line_item->set_quantity( $event_line_item->quantity() + $qty );
487
			}
488
			EEH_Line_Item::get_grand_total_and_recalculate_everything( $ticket_line_item );
489
			return true;
490
		}
491
		return false;
492
	}
493
494
495
496
	/**
497
	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
498
	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
499
	 *
500
	 * @param EE_Line_Item $line_item
501
	 * @return \EE_Line_Item
502
	 */
503
	public static function get_grand_total_and_recalculate_everything( EE_Line_Item $line_item ){
504
		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item( $line_item );
505
		return $grand_total_line_item->recalculate_total_including_taxes();
506
	}
507
508
509
510
	/**
511
	 * Gets the line item which contains the subtotal of all the items
512
	 *
513
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
514
	 * @return \EE_Line_Item
515
	 * @throws \EE_Error
516
	 */
517
	public static function get_pre_tax_subtotal( EE_Line_Item $total_line_item ){
518
		$pre_tax_subtotal = $total_line_item->get_child_line_item( 'pre-tax-subtotal' );
519
		return $pre_tax_subtotal instanceof EE_Line_Item
520
			? $pre_tax_subtotal
521
			: self::create_pre_tax_subtotal( $total_line_item );
522
	}
523
524
525
526
	/**
527
	 * Gets the line item for the taxes subtotal
528
	 *
529
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
530
	 * @return \EE_Line_Item
531
	 * @throws \EE_Error
532
	 */
533
	public static function get_taxes_subtotal( EE_Line_Item $total_line_item ){
534
		$taxes = $total_line_item->get_child_line_item( 'taxes' );
535
		return $taxes ? $taxes : self::create_taxes_subtotal( $total_line_item );
536
	}
537
538
539
540
	/**
541
	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
542
	 *
543
	 * @param EE_Line_Item   $line_item
544
	 * @param EE_Transaction $transaction
545
	 * @return void
546
	 * @throws \EE_Error
547
	 */
548
	public static function set_TXN_ID( EE_Line_Item $line_item, $transaction = NULL ){
549
		if( $transaction ){
550
			/** @type EEM_Transaction $EEM_Transaction */
551
			$EEM_Transaction = EE_Registry::instance()->load_model( 'Transaction' );
552
			$TXN_ID = $EEM_Transaction->ensure_is_ID( $transaction );
553
			$line_item->set_TXN_ID( $TXN_ID );
554
		}
555
	}
556
557
558
559
	/**
560
	 * Creates a new default total line item for the transaction,
561
	 * and its tickets subtotal and taxes subtotal line items (and adds the
562
	 * existing taxes as children of the taxes subtotal line item)
563
	 *
564
	 * @param EE_Transaction $transaction
565
	 * @return \EE_Line_Item of type total
566
	 * @throws \EE_Error
567
	 */
568 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...
569
		$total_line_item = EE_Line_Item::new_instance( array(
570
			'LIN_code'	=> 'total',
571
			'LIN_name'	=> __('Grand Total', 'event_espresso'),
572
			'LIN_type'	=> EEM_Line_Item::type_total,
573
			'OBJ_type'	=>'Transaction'
574
		));
575
		$total_line_item = apply_filters(
576
			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
577
			$total_line_item
578
		);
579
		self::set_TXN_ID( $total_line_item, $transaction );
580
		self::create_pre_tax_subtotal( $total_line_item, $transaction );
581
		self::create_taxes_subtotal( $total_line_item, $transaction );
582
		return $total_line_item;
583
	}
584
585
586
587
	/**
588
	 * Creates a default items subtotal line item
589
	 *
590
	 * @param EE_Line_Item   $total_line_item
591
	 * @param EE_Transaction $transaction
592
	 * @return EE_Line_Item
593
	 * @throws \EE_Error
594
	 */
595 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...
596
		$pre_tax_line_item = EE_Line_Item::new_instance( array(
597
			'LIN_code' 	=> 'pre-tax-subtotal',
598
			'LIN_name' 	=> __( 'Pre-Tax Subtotal', 'event_espresso' ),
599
			'LIN_type' 	=> EEM_Line_Item::type_sub_total
600
		) );
601
		$pre_tax_line_item = apply_filters(
602
			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
603
			$pre_tax_line_item
604
		);
605
		self::set_TXN_ID( $pre_tax_line_item, $transaction );
606
		$total_line_item->add_child_line_item( $pre_tax_line_item );
607
		self::create_event_subtotal( $pre_tax_line_item, $transaction );
608
		return $pre_tax_line_item;
609
	}
610
611
612
613
	/**
614
	 * Creates a line item for the taxes subtotal and finds all the tax prices
615
	 * and applies taxes to it
616
	 *
617
	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
618
	 * @param EE_Transaction $transaction
619
	 * @return EE_Line_Item
620
	 * @throws \EE_Error
621
	 */
622 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...
623
		$tax_line_item = EE_Line_Item::new_instance(array(
624
			'LIN_code'	=> 'taxes',
625
			'LIN_name' 	=> __('Taxes', 'event_espresso'),
626
			'LIN_type'	=> EEM_Line_Item::type_tax_sub_total,
627
			'LIN_order' => 1000,//this should always come last
628
		));
629
		$tax_line_item = apply_filters(
630
			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
631
			$tax_line_item
632
		);
633
		self::set_TXN_ID( $tax_line_item, $transaction );
634
		$total_line_item->add_child_line_item( $tax_line_item );
635
		//and lastly, add the actual taxes
636
		self::apply_taxes( $total_line_item );
637
		return $tax_line_item;
638
	}
639
640
641
642
	/**
643
	 * Creates a default items subtotal line item
644
	 *
645
	 * @param EE_Line_Item   $pre_tax_line_item
646
	 * @param EE_Transaction $transaction
647
	 * @param EE_Event       $event
648
	 * @return EE_Line_Item
649
	 * @throws \EE_Error
650
	 */
651
	public static function create_event_subtotal( EE_Line_Item $pre_tax_line_item, $transaction = NULL, $event = NULL ){
652
		$event_line_item = EE_Line_Item::new_instance(array(
653
			'LIN_code'	=> self::get_event_code( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 651 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...
654
			'LIN_name' 	=> self::get_event_name( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 651 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...
655
			'LIN_desc' 	=> self::get_event_desc( $event ),
0 ignored issues
show
Bug introduced by
It seems like $event defined by parameter $event on line 651 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...
656
			'LIN_type'	=> EEM_Line_Item::type_sub_total,
657
			'OBJ_type' 	=> 'Event',
658
			'OBJ_ID' 		=>  $event instanceof EE_Event ? $event->ID() : 0
659
		));
660
		$event_line_item = apply_filters(
661
			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
662
			$event_line_item
663
		);
664
		self::set_TXN_ID( $event_line_item, $transaction );
665
		$pre_tax_line_item->add_child_line_item( $event_line_item );
666
		return $event_line_item;
667
	}
668
669
670
671
	/**
672
	 * Gets what the event ticket's code SHOULD be
673
	 *
674
	 * @param EE_Event $event
675
	 * @return string
676
	 * @throws \EE_Error
677
	 */
678
	public static function get_event_code( $event ) {
679
		return 'event-' . ( $event instanceof EE_Event ? $event->ID() : '0' );
680
	}
681
682
	/**
683
	 * Gets the event name
684
	 * @param EE_Event $event
685
	 * @return string
686
	 */
687
	public static function get_event_name( $event ) {
688
		return $event instanceof EE_Event ? $event->name() : __( 'Event', 'event_espresso' );
689
	}
690
691
	/**
692
	 * Gets the event excerpt
693
	 * @param EE_Event $event
694
	 * @return string
695
	 */
696
	public static function get_event_desc( $event ) {
697
		return $event instanceof EE_Event ? $event->short_description() : '';
698
	}
699
700
	/**
701
	  * Given the grand total line item and a ticket, finds the event sub-total
702
	  * line item the ticket's purchase should be added onto
703
	  *
704
	  * @access public
705
	  * @param EE_Line_Item $grand_total the grand total line item
706
	  * @param EE_Ticket $ticket
707
	  * @throws \EE_Error
708
	  * @return EE_Line_Item
709
	  */
710
	public static function get_event_line_item_for_ticket( EE_Line_Item $grand_total, EE_Ticket $ticket ) {
711
		$first_datetime = $ticket->first_datetime();
712
		if ( ! $first_datetime instanceof EE_Datetime ) {
713
			throw new EE_Error(
714
				sprintf( __( 'The supplied ticket (ID %d) has no datetimes', 'event_espresso' ), $ticket->ID() )
715
			);
716
		}
717
		$event = $first_datetime->event();
718 View Code Duplication
		if ( ! $event instanceof EE_Event ) {
719
			throw new EE_Error(
720
				sprintf(
721
					__( 'The supplied ticket (ID %d) has no event data associated with it.', 'event_espresso' ),
722
					$ticket->ID()
723
				)
724
			);
725
		}
726
		$events_sub_total = EEH_Line_Item::get_event_line_item( $grand_total, $event );
727
		if ( ! $events_sub_total instanceof EE_Line_Item ) {
728
			throw new EE_Error(
729
				sprintf(
730
					__( 'There is no events sub-total for ticket %s on total line item %d', 'event_espresso' ),
731
					$ticket->ID(),
732
					$grand_total->ID()
733
				)
734
			);
735
		}
736
		return $events_sub_total;
737
	}
738
739
740
741
	/**
742
	 * Gets the event line item
743
	 *
744
	 * @param EE_Line_Item $grand_total
745
	 * @param EE_Event     $event
746
	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
747
	 * @throws \EE_Error
748
	 */
749
	public static function get_event_line_item( EE_Line_Item $grand_total, $event ) {
750
		/** @type EE_Event $event */
751
		$event = EEM_Event::instance()->ensure_is_obj( $event, true );
752
		$event_line_item = NULL;
753
		$found = false;
754
		foreach ( EEH_Line_Item::get_event_subtotals( $grand_total ) as $event_line_item ) {
755
			// default event subtotal, we should only ever find this the first time this method is called
756
			if ( ! $event_line_item->OBJ_ID() ) {
757
				// let's use this! but first... set the event details
758
				EEH_Line_Item::set_event_subtotal_details( $event_line_item, $event );
759
				$found = true;
760
				break;
761
			} else if ( $event_line_item->OBJ_ID() === $event->ID() ) {
762
				// found existing line item for this event in the cart, so break out of loop and use this one
763
				$found = true;
764
				break;
765
			}
766
		}
767
		if ( ! $found ) {
768
			//there is no event sub-total yet, so add it
769
			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal( $grand_total );
770
			// create a new "event" subtotal below that
771
			$event_line_item = EEH_Line_Item::create_event_subtotal( $pre_tax_subtotal, null, $event );
772
			// and set the event details
773
			EEH_Line_Item::set_event_subtotal_details( $event_line_item, $event );
774
		}
775
		return $event_line_item;
776
	}
777
778
779
780
	/**
781
	 * Creates a default items subtotal line item
782
	 *
783
	 * @param EE_Line_Item   $event_line_item
784
	 * @param EE_Event       $event
785
	 * @param EE_Transaction $transaction
786
	 * @return EE_Line_Item
787
	 * @throws \EE_Error
788
	 */
789
	public static function set_event_subtotal_details(
790
		EE_Line_Item $event_line_item,
791
		EE_Event $event,
792
		$transaction = null
793
	) {
794
		if ( $event instanceof EE_Event ) {
795
			$event_line_item->set_code( self::get_event_code( $event ) );
796
			$event_line_item->set_name( self::get_event_name( $event ) );
797
			$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...
798
			$event_line_item->set_OBJ_ID( $event->ID() );
799
		}
800
		self::set_TXN_ID( $event_line_item, $transaction );
801
	}
802
803
804
805
	/**
806
	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
807
	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
808
	 * any old taxes are removed
809
	 *
810
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
811
	 * @throws \EE_Error
812
	 */
813
	public static function apply_taxes( EE_Line_Item $total_line_item ){
814
		/** @type EEM_Price $EEM_Price */
815
		$EEM_Price = EE_Registry::instance()->load_model( 'Price' );
816
		// get array of taxes via Price Model
817
		$ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
818
		ksort( $ordered_taxes );
819
		$taxes_line_item = self::get_taxes_subtotal( $total_line_item );
820
		//just to be safe, remove its old tax line items
821
		$taxes_line_item->delete_children_line_items();
822
		//loop thru taxes
823
		foreach ( $ordered_taxes as $order => $taxes ) {
824
			foreach ( $taxes as $tax ) {
825
				if ( $tax instanceof EE_Price ) {
826
					$tax_line_item = EE_Line_Item::new_instance(
827
						array(
828
							'LIN_name'       => $tax->name(),
829
							'LIN_desc'       => $tax->desc(),
830
							'LIN_percent'    => $tax->amount(),
831
							'LIN_is_taxable' => false,
832
							'LIN_order'      => $order,
833
							'LIN_total'      => 0,
834
							'LIN_type'       => EEM_Line_Item::type_tax,
835
							'OBJ_type'       => 'Price',
836
							'OBJ_ID'         => $tax->ID()
837
						)
838
					);
839
					$tax_line_item = apply_filters(
840
						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
841
						$tax_line_item
842
					);
843
					$taxes_line_item->add_child_line_item( $tax_line_item );
844
				}
845
			}
846
		}
847
		$total_line_item->recalculate_total_including_taxes();
848
	}
849
850
851
852
	/**
853
	 * Ensures that taxes have been applied to the order, if not applies them.
854
	 * Returns the total amount of tax
855
	 *
856
	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
857
	 * @return float
858
	 * @throws \EE_Error
859
	 */
860
	public static function ensure_taxes_applied( $total_line_item ){
861
		$taxes_subtotal = self::get_taxes_subtotal( $total_line_item );
862
		if( ! $taxes_subtotal->children()){
863
			self::apply_taxes( $total_line_item );
864
		}
865
		return $taxes_subtotal->total();
866
	}
867
868
869
870
	/**
871
	 * Deletes ALL children of the passed line item
872
	 *
873
	 * @param EE_Line_Item $parent_line_item
874
	 * @return bool
875
	 * @throws \EE_Error
876
	 */
877
	public static function delete_all_child_items( EE_Line_Item $parent_line_item ) {
878
		$deleted = 0;
879
		foreach ( $parent_line_item->children() as $child_line_item ) {
880
			if ( $child_line_item instanceof EE_Line_Item ) {
881
				$deleted += EEH_Line_Item::delete_all_child_items( $child_line_item );
882
				if ( $child_line_item->ID() ) {
883
					$child_line_item->delete();
884
					unset( $child_line_item );
885
				} else {
886
					$parent_line_item->delete_child_line_item( $child_line_item->code() );
887
				}
888
				$deleted++;
889
			}
890
		}
891
		return $deleted;
892
	}
893
894
895
896
	/**
897
	 * Deletes the line items as indicated by the line item code(s) provided,
898
	 * regardless of where they're found in the line item tree. Automatically
899
	 * re-calculates the line item totals and updates the related transaction. But
900
	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
901
	 * should probably change because of this).
902
	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
903
	 * after using this, to keep the registration final prices in-sync with the transaction's total.
904
	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
905
	 * @param array|bool|string $line_item_codes
906
	 * @return int number of items successfully removed
907
	 */
908
	public static function delete_items( EE_Line_Item $total_line_item, $line_item_codes = FALSE ) {
909
910
		if( $total_line_item->type() !== EEM_Line_Item::type_total ){
911
			EE_Error::doing_it_wrong(
912
				'EEH_Line_Item::delete_items',
913
				__(
914
					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
915
					'event_espresso'
916
				),
917
				'4.6.18'
918
			);
919
		}
920
		do_action( 'AHEE_log', __FILE__, __FUNCTION__, '' );
921
922
		// check if only a single line_item_id was passed
923
		if ( ! empty( $line_item_codes ) && ! is_array( $line_item_codes )) {
924
			// place single line_item_id in an array to appear as multiple line_item_ids
925
			$line_item_codes = array ( $line_item_codes );
926
		}
927
		$removals = 0;
928
		// cycle thru line_item_ids
929
		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...
930
			$removals += $total_line_item->delete_child_line_item($line_item_id);
931
		}
932
933
		if ( $removals > 0 ) {
934
			$total_line_item->recalculate_taxes_and_tax_total();
935
			return $removals;
936
		} else {
937
			return FALSE;
938
		}
939
	}
940
941
942
943
	/**
944
	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
945
	 * tax and updates the total line item accordingly
946
	 *
947
	 * @param EE_Line_Item $total_line_item
948
	 * @param float        $amount
949
	 * @param string       $name
950
	 * @param string       $description
951
	 * @param string       $code
952
	 * @param boolean      $add_to_existing_line_item
953
	 *                          if true, and a duplicate line item with the same code is found,
954
	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
955
	 * @return EE_Line_Item the new tax line item created
956
	 * @throws \EE_Error
957
	 */
958
	public static function set_total_tax_to(
959
		EE_Line_Item $total_line_item,
960
		$amount,
961
		$name = null,
962
		$description = null,
963
		$code = null,
964
		$add_to_existing_line_item = false
965
	) {
966
		$tax_subtotal = self::get_taxes_subtotal( $total_line_item );
967
            $taxable_total = $total_line_item->taxable_total();
968
969
            if( $add_to_existing_line_item ) {
970
                $new_tax = $tax_subtotal->get_child_line_item( $code );
971
	            EEM_Line_Item::instance()->delete(
972
		            array( array( 'LIN_code' => array( '!=', $code ), 'LIN_parent' => $tax_subtotal->ID() ) )
973
	            );
974
            } else {
975
                $new_tax = null;
976
                $tax_subtotal->delete_children_line_items();
977
            }
978
            if( $new_tax ) {
979
                $new_tax->set_total( $new_tax->total() + $amount );
980
                $new_tax->set_percent( $taxable_total ? $new_tax->total() / $taxable_total * 100 : 0 );
981
            } else {
982
                //no existing tax item. Create it
983
				$new_tax = EE_Line_Item::new_instance( array(
984
					'TXN_ID'      => $total_line_item->TXN_ID(),
985
					'LIN_name'    => $name ? $name : __( 'Tax', 'event_espresso' ),
986
					'LIN_desc'    => $description ? $description : '',
987
					'LIN_percent' => $taxable_total ? ( $amount / $taxable_total * 100 ) : 0,
988
					'LIN_total'   => $amount,
989
					'LIN_parent'  => $tax_subtotal->ID(),
990
					'LIN_type'    => EEM_Line_Item::type_tax,
991
					'LIN_code'    => $code
992
				) );
993
			}
994
995
            $new_tax = apply_filters(
996
				'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
997
				$new_tax,
998
				$total_line_item
999
            );
1000
            $new_tax->save();
1001
            $tax_subtotal->set_total( $new_tax->total() );
1002
            $tax_subtotal->save();
1003
            $total_line_item->recalculate_total_including_taxes();
1004
            return $new_tax;
1005
	}
1006
1007
1008
1009
	/**
1010
	 * Makes all the line items which are children of $line_item taxable (or not).
1011
	 * Does NOT save the line items
1012
	 * @param EE_Line_Item $line_item
1013
	 * @param string $code_substring_for_whitelist if this string is part of the line item's code
1014
	 *  it will be whitelisted (ie, except from becoming taxable)
1015
	 * @param boolean $taxable
1016
	 */
1017
	public static function set_line_items_taxable(
1018
		EE_Line_Item $line_item,
1019
		$taxable = true,
1020
		$code_substring_for_whitelist = null
1021
	) {
1022
		$whitelisted = false;
1023
		if( $code_substring_for_whitelist !== null ) {
1024
			$whitelisted = strpos( $line_item->code(), $code_substring_for_whitelist ) !== false ? true : false;
1025
		}
1026
		if( ! $whitelisted && $line_item->is_line_item() ) {
1027
			$line_item->set_is_taxable( $taxable );
1028
		}
1029
		foreach( $line_item->children() as $child_line_item ) {
1030
			EEH_Line_Item::set_line_items_taxable( $child_line_item, $taxable, $code_substring_for_whitelist );
1031
		}
1032
	}
1033
1034
1035
1036
	/**
1037
	 * Gets all descendants that are event subtotals
1038
	 *
1039
	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1040
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1041
	 * @return EE_Line_Item[]
1042
	 */
1043
	public static function get_event_subtotals( EE_Line_Item $parent_line_item ) {
1044
		return self::get_subtotals_of_object_type( $parent_line_item, 'Event' );
1045
	}
1046
1047
1048
1049
	/**
1050
	 * Gets all descendants subtotals that match the supplied object type
1051
	 *
1052
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1053
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1054
	 * @param string $obj_type
1055
	 * @return EE_Line_Item[]
1056
	 */
1057
	public static function get_subtotals_of_object_type( EE_Line_Item $parent_line_item, $obj_type = '' ) {
1058
		return self::_get_descendants_by_type_and_object_type(
1059
			$parent_line_item,
1060
			EEM_Line_Item::type_sub_total,
1061
			$obj_type
1062
		);
1063
	}
1064
1065
1066
1067
	/**
1068
	 * Gets all descendants that are tickets
1069
	 *
1070
	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1071
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1072
	 * @return EE_Line_Item[]
1073
	 */
1074
	public static function get_ticket_line_items( EE_Line_Item $parent_line_item ) {
1075
		return self::get_line_items_of_object_type( $parent_line_item, 'Ticket' );
1076
	}
1077
1078
1079
1080
	/**
1081
	 * Gets all descendants subtotals that match the supplied object type
1082
	 *
1083
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1084
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1085
	 * @param string $obj_type
1086
	 * @return EE_Line_Item[]
1087
	 */
1088
	public static function get_line_items_of_object_type( EE_Line_Item $parent_line_item, $obj_type = '' ) {
1089
		return self::_get_descendants_by_type_and_object_type( $parent_line_item, EEM_Line_Item::type_line_item, $obj_type );
1090
	}
1091
1092
1093
1094
	/**
1095
	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1096
	 * @uses  EEH_Line_Item::get_descendants_of_type()
1097
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1098
	 * @return EE_Line_Item[]
1099
	 */
1100
	public static function get_tax_descendants( EE_Line_Item $parent_line_item ) {
1101
		return EEH_Line_Item::get_descendants_of_type( $parent_line_item, EEM_Line_Item::type_tax );
1102
	}
1103
1104
1105
1106
	/**
1107
	 * Gets all the real items purchased which are children of this item
1108
	 * @uses  EEH_Line_Item::get_descendants_of_type()
1109
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1110
	 * @return EE_Line_Item[]
1111
	 */
1112
	public static function get_line_item_descendants( EE_Line_Item $parent_line_item ) {
1113
		return EEH_Line_Item::get_descendants_of_type( $parent_line_item, EEM_Line_Item::type_line_item );
1114
	}
1115
1116
1117
1118
	/**
1119
	 * Gets all descendants of supplied line item that match the supplied line item type
1120
	 *
1121
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1122
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1123
	 * @param string $line_item_type one of the EEM_Line_Item constants
1124
	 * @return EE_Line_Item[]
1125
	 */
1126
	public static function get_descendants_of_type( EE_Line_Item $parent_line_item, $line_item_type ) {
1127
		return self::_get_descendants_by_type_and_object_type( $parent_line_item, $line_item_type, NULL );
1128
	}
1129
1130
1131
1132
	/**
1133
	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1134
	 *
1135
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1136
	 * @param string $line_item_type one of the EEM_Line_Item constants
1137
	 * @param string | NULL $obj_type object model class name (minus prefix) or NULL to ignore object type when searching
1138
	 * @return EE_Line_Item[]
1139
	 */
1140
	protected static function _get_descendants_by_type_and_object_type(
1141
		EE_Line_Item $parent_line_item,
1142
		$line_item_type,
1143
		$obj_type = null
1144
	) {
1145
		$objects = array();
1146
		foreach ( $parent_line_item->children() as $child_line_item ) {
1147
			if ( $child_line_item instanceof EE_Line_Item ) {
1148
				if (
1149
					$child_line_item->type() === $line_item_type
1150
				    && (
1151
						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1152
					)
1153
				) {
1154
					$objects[] = $child_line_item;
1155
				} else {
1156
					//go-through-all-its children looking for more matches
1157
					$objects = array_merge(
1158
						$objects,
1159
						self::_get_descendants_by_type_and_object_type(
1160
							$child_line_item,
1161
							$line_item_type,
1162
							$obj_type
1163
						)
1164
					);
1165
				}
1166
			}
1167
		}
1168
		return $objects;
1169
	}
1170
1171
1172
1173
	/**
1174
	 * Gets all descendants subtotals that match the supplied object type
1175
	 *
1176
	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1177
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1178
	 * @param string $OBJ_type object type (like Event)
1179
	 * @param array $OBJ_IDs array of OBJ_IDs
1180
	 * @return EE_Line_Item[]
1181
	 */
1182
	public static function get_line_items_by_object_type_and_IDs(
1183
		EE_Line_Item $parent_line_item,
1184
		$OBJ_type = '',
1185
		$OBJ_IDs = array()
1186
	) {
1187
		return self::_get_descendants_by_object_type_and_object_ID( $parent_line_item, $OBJ_type, $OBJ_IDs );
1188
	}
1189
1190
1191
1192
	/**
1193
	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1194
	 *
1195
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1196
	 * @param string $OBJ_type object type (like Event)
1197
	 * @param array $OBJ_IDs array of OBJ_IDs
1198
	 * @return EE_Line_Item[]
1199
	 */
1200
	protected static function _get_descendants_by_object_type_and_object_ID(
1201
		EE_Line_Item $parent_line_item,
1202
		$OBJ_type,
1203
		$OBJ_IDs
1204
	) {
1205
		$objects = array();
1206
		foreach ( $parent_line_item->children() as $child_line_item ) {
1207
			if ( $child_line_item instanceof EE_Line_Item ) {
1208
				if (
1209
					is_array( $OBJ_IDs )
1210
					&& $child_line_item->OBJ_type() === $OBJ_type
1211
					&& in_array( $child_line_item->OBJ_ID(), $OBJ_IDs )
1212
				) {
1213
					$objects[] = $child_line_item;
1214
				} else {
1215
					//go-through-all-its children looking for more matches
1216
					$objects = array_merge(
1217
						$objects,
1218
						self::_get_descendants_by_object_type_and_object_ID(
1219
							$child_line_item,
1220
							$OBJ_type,
1221
							$OBJ_IDs
1222
						)
1223
					);
1224
				}
1225
			}
1226
		}
1227
		return $objects;
1228
	}
1229
1230
1231
1232
	/**
1233
	 * Uses a breadth-first-search in order to find the nearest descendant of
1234
	 * the specified type and returns it, else NULL
1235
	 *
1236
	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1237
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1238
	 * @param string $type like one of the EEM_Line_Item::type_*
1239
	 * @return EE_Line_Item
1240
	 */
1241
	public static function get_nearest_descendant_of_type( EE_Line_Item $parent_line_item, $type ) {
1242
		return self::_get_nearest_descendant( $parent_line_item, 'LIN_type' , $type );
1243
	}
1244
1245
1246
1247
	/**
1248
	 * Uses a breadth-first-search in order to find the nearest descendant
1249
	 * having the specified LIN_code and returns it, else NULL
1250
	 *
1251
	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1252
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1253
	 * @param string $code any value used for LIN_code
1254
	 * @return EE_Line_Item
1255
	 */
1256
	public static function get_nearest_descendant_having_code( EE_Line_Item $parent_line_item, $code ) {
1257
		return self::_get_nearest_descendant( $parent_line_item, 'LIN_code' , $code );
1258
	}
1259
1260
1261
1262
	/**
1263
	 * Uses a breadth-first-search in order to find the nearest descendant
1264
	 * having the specified LIN_code and returns it, else NULL
1265
	 *
1266
	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1267
	 * @param string $search_field  name of EE_Line_Item property
1268
	 * @param string $value any value stored in $search_field
1269
	 * @return EE_Line_Item
1270
	 */
1271
	protected static function _get_nearest_descendant( EE_Line_Item $parent_line_item, $search_field, $value ) {
1272
		foreach( $parent_line_item->children() as $child ){
1273
			if ( $child->get( $search_field ) == $value ){
1274
				return $child;
1275
			}
1276
		}
1277
		foreach( $parent_line_item->children() as $child ){
1278
			$descendant_found = self::_get_nearest_descendant( $child, $search_field, $value );
1279
			if ( $descendant_found ){
1280
				return $descendant_found;
1281
			}
1282
		}
1283
		return NULL;
1284
	}
1285
1286
1287
1288
	/**
1289
	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1290
	 * else recursively walks up the line item tree until a parent of type total is found,
1291
	 *
1292
	 * @param EE_Line_Item $line_item
1293
	 * @return \EE_Line_Item
1294
	 * @throws \EE_Error
1295
	 */
1296
	public static function find_transaction_grand_total_for_line_item( EE_Line_Item $line_item ){
1297
		if ( $line_item->TXN_ID() ) {
1298
			$total_line_item = $line_item->transaction()->total_line_item( false );
1299
			if ( $total_line_item instanceof EE_Line_Item ) {
1300
				return $total_line_item;
1301
			}
1302
		} else {
1303
			$line_item_parent = $line_item->parent();
1304
			if ( $line_item_parent instanceof EE_Line_Item ) {
1305
				if ( $line_item_parent->is_total() ) {
1306
					return $line_item_parent;
1307
				}
1308
				return EEH_Line_Item::find_transaction_grand_total_for_line_item( $line_item_parent );
1309
			}
1310
		}
1311
		throw new EE_Error(
1312
			sprintf(
1313
				__( 'A valid grand total for line item %1$d was not found.', 'event_espresso' ),
1314
				$line_item->ID()
1315
			)
1316
		);
1317
	}
1318
1319
1320
1321
1322
	/**
1323
	 * Prints out a representation of the line item tree
1324
	 *
1325
	 * @param EE_Line_Item $line_item
1326
	 * @param int          $indentation
1327
	 * @return void
1328
	 * @throws \EE_Error
1329
	 */
1330
	public static function visualize( EE_Line_Item $line_item, $indentation = 0 ){
1331
		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1332
		if ( ! $indentation ) {
1333
			echo defined( 'EE_TESTS_DIR' ) ? "\n" : '<br />';
1334
		}
1335
		for( $i = 0; $i < $indentation; $i++ ){
1336
			echo ". ";
1337
		}
1338
		$breakdown = '';
1339
		if ( $line_item->is_line_item()){
1340
			if ( $line_item->is_percent() ) {
1341
				$breakdown = "{$line_item->percent()}%";
1342
			} else {
1343
				$breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1344
			}
1345
		}
1346
		echo $line_item->name() . " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : " . '$' . "{$line_item->total()}";
1347
		if ( $breakdown ) {
1348
			echo " ( {$breakdown} )";
1349
		}
1350
		if( $line_item->is_taxable() ){
1351
			echo "  * taxable";
1352
		}
1353
		if( $line_item->children() ){
1354
			foreach($line_item->children() as $child){
1355
				self::visualize($child, $indentation + 1);
1356
			}
1357
		}
1358
	}
1359
1360
1361
1362
	/**
1363
	 * Calculates the registration's final price, taking into account that they
1364
	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1365
	 * and receive a portion of any transaction-wide discounts.
1366
	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1367
	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1368
	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1369
	 * and brent's final price should be $5.50.
1370
	 *
1371
	 * In order to do this, we basically need to traverse the line item tree calculating
1372
	 * the running totals (just as if we were recalculating the total), but when we identify
1373
	 * regular line items, we need to keep track of their share of the grand total.
1374
	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1375
	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1376
	 * when there are non-taxable items; otherwise they would be the same)
1377
	 *
1378
	 * @param EE_Line_Item $line_item
1379
	 * @param array $billable_ticket_quantities 		array of EE_Ticket IDs and their corresponding quantity that
1380
	 *                                          									can be included in price calculations at this moment
1381
	 * @return array 		keys are line items for tickets IDs and values are their share of the running total,
1382
	 *                                          plus the key 'total', and 'taxable' which also has keys of all the ticket IDs. Eg
1383
	 *                                          array(
1384
	 *                                          12 => 4.3
1385
	 *                                          23 => 8.0
1386
	 *                                          'total' => 16.6,
1387
	 *                                          'taxable' => array(
1388
	 *                                          12 => 10,
1389
	 *                                          23 => 4
1390
	 *                                          ).
1391
	 *                                          So to find which registrations have which final price, we need to find which line item
1392
	 *                                          is theirs, which can be done with
1393
	 *                                          `EEM_Line_Item::instance()->get_line_item_for_registration( $registration );`
1394
	 */
1395
	public static function calculate_reg_final_prices_per_line_item( EE_Line_Item $line_item, $billable_ticket_quantities = array() ) {
1396
		//init running grand total if not already
1397
		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...
1398
			$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...
1399
		}
1400
		if( ! isset( $running_totals[ 'taxable' ] ) ) {
1401
			$running_totals[ 'taxable' ] = array( 'total' => 0 );
1402
		}
1403
		foreach ( $line_item->children() as $child_line_item ) {
1404
			switch ( $child_line_item->type() ) {
1405
1406
				case EEM_Line_Item::type_sub_total :
1407
					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item( $child_line_item, $billable_ticket_quantities );
1408
					//combine arrays but preserve numeric keys
1409
					$running_totals = array_replace_recursive( $running_totals_from_subtotal, $running_totals );
1410
					$running_totals[ 'total' ] += $running_totals_from_subtotal[ 'total' ];
1411
					$running_totals[ 'taxable'][ 'total' ] += $running_totals_from_subtotal[ 'taxable' ][ 'total' ];
1412
					break;
1413
1414
				case EEM_Line_Item::type_tax_sub_total :
1415
1416
					//find how much the taxes percentage is
1417
					if ( $child_line_item->percent() !== 0 ) {
1418
						$tax_percent_decimal = $child_line_item->percent() / 100;
1419
					} else {
1420
						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1421
					}
1422
					//and apply to all the taxable totals, and add to the pretax totals
1423
					foreach ( $running_totals as $line_item_id => $this_running_total ) {
1424
						//"total" and "taxable" array key is an exception
1425
						if ( $line_item_id === 'taxable' ) {
1426
							continue;
1427
						}
1428
						$taxable_total = $running_totals[ 'taxable' ][ $line_item_id ];
1429
						$running_totals[ $line_item_id ] += ( $taxable_total * $tax_percent_decimal );
1430
					}
1431
					break;
1432
1433
				case EEM_Line_Item::type_line_item :
1434
1435
					// ticket line items or ????
1436
					if ( $child_line_item->OBJ_type() === 'Ticket' ) {
1437
						// kk it's a ticket
1438
						if ( isset( $running_totals[ $child_line_item->ID() ] ) ) {
1439
							//huh? that shouldn't happen.
1440
							$running_totals[ 'total' ] += $child_line_item->total();
1441
						} else {
1442
							//its not in our running totals yet. great.
1443
							if ( $child_line_item->is_taxable() ) {
1444
								$taxable_amount = $child_line_item->unit_price();
1445
							} else {
1446
								$taxable_amount = 0;
1447
							}
1448
							// are we only calculating totals for some tickets?
1449
							if ( isset( $billable_ticket_quantities[ $child_line_item->OBJ_ID() ] ) ) {
1450
								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1451
								$running_totals[ $child_line_item->ID() ] = $quantity
1452
									? $child_line_item->unit_price()
1453
									: 0;
1454
								$running_totals[ 'taxable' ][ $child_line_item->ID() ] = $quantity
1455
									? $taxable_amount
1456
									: 0;
1457
							} else {
1458
								$quantity = $child_line_item->quantity();
1459
								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1460
								$running_totals[ 'taxable' ][ $child_line_item->ID() ] = $taxable_amount;
1461
							}
1462
							$running_totals[ 'taxable' ][ 'total' ] += $taxable_amount * $quantity;
1463
							$running_totals[ 'total' ] += $child_line_item->unit_price() * $quantity;
1464
						}
1465
					} else {
1466
						// it's some other type of item added to the cart
1467
						// it should affect the running totals
1468
						// basically we want to convert it into a PERCENT modifier. Because
1469
						// more clearly affect all registration's final price equally
1470
						$line_items_percent_of_running_total = $running_totals[ 'total' ] > 0
1471
							? ( $child_line_item->total() / $running_totals[ 'total' ] ) + 1
1472
							: 1;
1473
						foreach ( $running_totals as $line_item_id => $this_running_total ) {
1474
							//the "taxable" array key is an exception
1475
							if ( $line_item_id === 'taxable' ) {
1476
								continue;
1477
							}
1478
							// update the running totals
1479
							// yes this actually even works for the running grand total!
1480
							$running_totals[ $line_item_id ] =
1481
								$line_items_percent_of_running_total * $this_running_total;
1482
1483
							if ( $child_line_item->is_taxable() ) {
1484
								$running_totals[ 'taxable' ][ $line_item_id ] =
1485
									$line_items_percent_of_running_total * $running_totals[ 'taxable' ][ $line_item_id ];
1486
							}
1487
						}
1488
					}
1489
					break;
1490
			}
1491
		}
1492
		return $running_totals;
1493
	}
1494
1495
1496
1497
	/**
1498
	 * @param \EE_Line_Item $total_line_item
1499
	 * @param \EE_Line_Item $ticket_line_item
1500
	 * @return float | null
1501
	 * @throws \OutOfRangeException
1502
	 */
1503
	public static function calculate_final_price_for_ticket_line_item( \EE_Line_Item $total_line_item, \EE_Line_Item $ticket_line_item ) {
1504
		static $final_prices_per_ticket_line_item = array();
1505
		if ( empty( $final_prices_per_ticket_line_item ) ) {
1506
			$final_prices_per_ticket_line_item = \EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1507
				$total_line_item
1508
			);
1509
		}
1510
		//ok now find this new registration's final price
1511
		if ( isset( $final_prices_per_ticket_line_item[ $ticket_line_item->ID() ] ) ) {
1512
			return $final_prices_per_ticket_line_item[ $ticket_line_item->ID() ];
1513
		}
1514
		$message = sprintf(
1515
			__(
1516
				'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1517
				'event_espresso'
1518
			),
1519
			$ticket_line_item->ID()
1520
		);
1521
		if ( WP_DEBUG ) {
1522
			throw new \OutOfRangeException( $message );
1523
		} else {
1524
			EE_Log::instance()->log( __CLASS__, __FUNCTION__, $message );
1525
		}
1526
		return null;
1527
	}
1528
1529
1530
1531
	/**
1532
	 * Creates a duplicate of the line item tree, except only includes billable items
1533
	 * and the portion of line items attributed to billable things
1534
	 *
1535
	 * @param EE_Line_Item      $line_item
1536
	 * @param EE_Registration[] $registrations
1537
	 * @return \EE_Line_Item
1538
	 * @throws \EE_Error
1539
	 */
1540
	public static function billable_line_item_tree( EE_Line_Item $line_item, $registrations ) {
1541
		$copy_li = EEH_Line_Item::billable_line_item( $line_item, $registrations );
1542
		foreach ( $line_item->children() as $child_li ) {
1543
			$copy_li->add_child_line_item( EEH_Line_Item::billable_line_item_tree( $child_li, $registrations ) );
1544
		}
1545
		//if this is the grand total line item, make sure the totals all add up
1546
		//(we could have duplicated this logic AS we copied the line items, but
1547
		//it seems DRYer this way)
1548
		if ( $copy_li->type() === EEM_Line_Item::type_total ) {
1549
			$copy_li->recalculate_total_including_taxes();
1550
		}
1551
		return $copy_li;
1552
	}
1553
1554
1555
1556
	/**
1557
	 * Creates a new, unsaved line item from $line_item that factors in the
1558
	 * number of billable registrations on $registrations.
1559
	 *
1560
	 * @param EE_Line_Item      $line_item
1561
	 * @return EE_Line_Item
1562
	 * @throws \EE_Error
1563
	 * @param EE_Registration[] $registrations
1564
	 */
1565
	public static function billable_line_item( EE_Line_Item $line_item, $registrations ) {
1566
		$new_li_fields = $line_item->model_field_array();
1567
		if ( $line_item->type() === EEM_Line_Item::type_line_item &&
1568
			$line_item->OBJ_type() === 'Ticket'
1569
		) {
1570
			$count = 0;
1571
			foreach ( $registrations as $registration ) {
1572
				if ( $line_item->OBJ_ID() === $registration->ticket_ID() &&
1573
					in_array( $registration->status_ID(), EEM_Registration::reg_statuses_that_allow_payment() )
1574
				) {
1575
					$count++;
1576
				}
1577
			}
1578
			$new_li_fields[ 'LIN_quantity' ] = $count;
1579
		}
1580
		//don't set the total. We'll leave that up to the code that calculates it
1581
		unset( $new_li_fields[ 'LIN_ID' ], $new_li_fields[ 'LIN_parent' ], $new_li_fields[ 'LIN_total' ] );
1582
		return EE_Line_Item::new_instance( $new_li_fields );
1583
	}
1584
1585
1586
1587
	/**
1588
	 * Returns a modified line item tree where all the subtotals which have a total of 0
1589
	 * are removed, and line items with a quantity of 0
1590
	 *
1591
	 * @param EE_Line_Item $line_item |null
1592
	 * @return \EE_Line_Item|null
1593
	 * @throws \EE_Error
1594
	 */
1595
	public static function non_empty_line_items( EE_Line_Item $line_item ) {
1596
		$copied_li = EEH_Line_Item::non_empty_line_item( $line_item );
1597
		if ( $copied_li === null ) {
1598
			return null;
1599
		}
1600
		//if this is an event subtotal, we want to only include it if it
1601
		//has a non-zero total and at least one ticket line item child
1602
		$ticket_children = 0;
1603
		foreach ( $line_item->children() as $child_li ) {
1604
			$child_li_copy = EEH_Line_Item::non_empty_line_items( $child_li );
1605
			if ( $child_li_copy !== null ) {
1606
				$copied_li->add_child_line_item( $child_li_copy );
1607
				if ( $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1608
					$child_li_copy->OBJ_type() === 'Ticket'
1609
				) {
1610
					$ticket_children++;
1611
				}
1612
			}
1613
		}
1614
		//if this is an event subtotal with NO ticket children
1615
		//we basically want to ignore it
1616
		if (
1617
			$ticket_children === 0
1618
			&& $line_item->type() === EEM_Line_Item::type_sub_total
1619
			&& $line_item->OBJ_type() === 'Event'
1620
			&& $line_item->total() === 0
1621
		) {
1622
			return null;
1623
		}
1624
		return $copied_li;
1625
	}
1626
1627
1628
1629
	/**
1630
	 * Creates a new, unsaved line item, but if it's a ticket line item
1631
	 * with a total of 0, or a subtotal of 0, returns null instead
1632
	 *
1633
	 * @param EE_Line_Item $line_item
1634
	 * @return EE_Line_Item
1635
	 * @throws \EE_Error
1636
	 */
1637
	public static function non_empty_line_item( EE_Line_Item $line_item ) {
1638 View Code Duplication
		if ( $line_item->type() === EEM_Line_Item::type_line_item &&
1639
			$line_item->OBJ_type() === 'Ticket' &&
1640
			$line_item->quantity() === 0
1641
		) {
1642
			return null;
1643
		}
1644
		$new_li_fields = $line_item->model_field_array();
1645
		//don't set the total. We'll leave that up to the code that calculates it
1646
		unset( $new_li_fields[ 'LIN_ID' ], $new_li_fields[ 'LIN_parent' ] );
1647
		return EE_Line_Item::new_instance( $new_li_fields );
1648
	}
1649
1650
1651
1652
	/**************************************** @DEPRECATED METHODS ****************************************/
1653
	/**
1654
	 * @deprecated
1655
	 * @param EE_Line_Item $total_line_item
1656
	 * @return \EE_Line_Item
1657
	 * @throws \EE_Error
1658
	 */
1659
	public static function get_items_subtotal( EE_Line_Item $total_line_item ){
1660
		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' );
1661
		return self::get_pre_tax_subtotal( $total_line_item );
1662
	}
1663
1664
1665
1666
	/**
1667
	 * @deprecated
1668
	 * @param EE_Transaction $transaction
1669
	 * @return \EE_Line_Item
1670
	 * @throws \EE_Error
1671
	 */
1672
	public static function create_default_total_line_item( $transaction = NULL) {
1673
		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' );
1674
		return self::create_total_line_item( $transaction );
1675
	}
1676
1677
1678
1679
	/**
1680
	 * @deprecated
1681
	 * @param EE_Line_Item   $total_line_item
1682
	 * @param EE_Transaction $transaction
1683
	 * @return \EE_Line_Item
1684
	 * @throws \EE_Error
1685
	 */
1686
	public static function create_default_tickets_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1687
		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' );
1688
		return self::create_pre_tax_subtotal( $total_line_item, $transaction );
1689
	}
1690
1691
1692
1693
	/**
1694
	 * @deprecated
1695
	 * @param EE_Line_Item   $total_line_item
1696
	 * @param EE_Transaction $transaction
1697
	 * @return \EE_Line_Item
1698
	 * @throws \EE_Error
1699
	 */
1700
	public static function create_default_taxes_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1701
		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' );
1702
		return self::create_taxes_subtotal( $total_line_item, $transaction );
1703
	}
1704
1705
1706
1707
	/**
1708
	 * @deprecated
1709
	 * @param EE_Line_Item   $total_line_item
1710
	 * @param EE_Transaction $transaction
1711
	 * @return \EE_Line_Item
1712
	 * @throws \EE_Error
1713
	 */
1714
	public static function create_default_event_subtotal( EE_Line_Item $total_line_item, $transaction = NULL) {
1715
		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' );
1716
		return self::create_event_subtotal( $total_line_item, $transaction );
1717
	}
1718
1719
1720
1721
}
1722
// End of file EEH_Line_Item.helper.php
1723