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 ) { |
|
|
|
|
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 ) { |
|
|
|
|
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; |
|
|
|
|
288
|
|
|
$price_total = $price->is_percent() |
|
|
|
|
289
|
|
|
? $running_total_for_ticket * $price->amount() / 100 |
|
|
|
|
290
|
|
|
: $price->amount() * $qty; |
|
|
|
|
291
|
|
|
$sub_line_item = EE_Line_Item::new_instance( array( |
292
|
|
|
'LIN_name' => $price->name(), |
293
|
|
|
'LIN_desc' => $price->desc(), |
|
|
|
|
294
|
|
|
'LIN_quantity' => $price->is_percent() ? null : $qty, |
|
|
|
|
295
|
|
|
'LIN_is_taxable' => false, |
296
|
|
|
'LIN_order' => $price->order(), |
|
|
|
|
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() ) { |
|
|
|
|
307
|
|
|
$sub_line_item->set_percent( $sign * $price->amount() ); |
|
|
|
|
308
|
|
|
} else { |
309
|
|
|
$sub_line_item->set_unit_price( $sign * $price->amount() ); |
|
|
|
|
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 ){ |
|
|
|
|
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 ){ |
|
|
|
|
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 ){ |
|
|
|
|
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 ), |
|
|
|
|
643
|
|
|
'LIN_name' => self::get_event_name( $event ), |
|
|
|
|
644
|
|
|
'LIN_desc' => self::get_event_desc( $event ), |
|
|
|
|
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 ) ); |
|
|
|
|
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 ) { |
|
|
|
|
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' ] ) ) { |
|
|
|
|
1387
|
|
|
$running_totals[ 'total' ] = 0; |
|
|
|
|
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
|
|
|
|
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.