Completed
Branch FET/Gutenberg/11467/espresso-c... (b0019e)
by
unknown
44:57 queued 36:05
created
admin_pages/about/templates/credits.template.php 1 patch
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -3,7 +3,7 @@  discard block
 block discarded – undo
3 3
 <ul class="wp-people-group" id="ee-people-group-owners">
4 4
 	<li class="wp-person" id="ee-person-sshoultes">
5 5
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
6
-			<?php echo esp_gravatar_image( '[email protected]', 'Seth Shoultes' ); ?>
6
+			<?php echo esp_gravatar_image('[email protected]', 'Seth Shoultes'); ?>
7 7
 		</a>
8 8
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
9 9
 			Seth Shoultes
@@ -12,7 +12,7 @@  discard block
 block discarded – undo
12 12
 	</li>
13 13
 	<li class="wp-person" id="ee-person-gkoyle">
14 14
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
15
-			<?php echo esp_gravatar_image( '[email protected]', 'Garth Koyle' ); ?>
15
+			<?php echo esp_gravatar_image('[email protected]', 'Garth Koyle'); ?>
16 16
 		</a>
17 17
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
18 18
 			Garth Koyle
@@ -24,7 +24,7 @@  discard block
 block discarded – undo
24 24
 <ul class="wp-people-group" id="ee-people-group-core-developers">
25 25
 	<li class="wp-person" id="ee-person-bchristensen">
26 26
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
27
-			<?php echo esp_gravatar_image( '[email protected]', 'Brent Christensen' ); ?>
27
+			<?php echo esp_gravatar_image('[email protected]', 'Brent Christensen'); ?>
28 28
 		</a>
29 29
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
30 30
 			Brent Christensen
@@ -33,7 +33,7 @@  discard block
 block discarded – undo
33 33
 	</li>
34 34
 	<li class="wp-person" id="ee-person-dethier">
35 35
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
36
-			<?php echo esp_gravatar_image( '[email protected]', 'Darren Ethier' ); ?>
36
+			<?php echo esp_gravatar_image('[email protected]', 'Darren Ethier'); ?>
37 37
 		</a>
38 38
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
39 39
 			Darren Ethier
@@ -42,7 +42,7 @@  discard block
 block discarded – undo
42 42
 	</li>
43 43
 	<li class="wp-person" id="ee-person-mnelson">
44 44
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
45
-			<?php echo esp_gravatar_image( '[email protected]', 'Michael Nelson' ); ?>
45
+			<?php echo esp_gravatar_image('[email protected]', 'Michael Nelson'); ?>
46 46
 		</a>
47 47
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
48 48
 			Michael Nelson
@@ -51,7 +51,7 @@  discard block
 block discarded – undo
51 51
 	</li>
52 52
 	<li class="wp-person" id="ee-person-nkolivoshka">
53 53
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
54
-			<?php echo esp_gravatar_image( '[email protected]', 'Nazar Kolivoshka' ); ?>
54
+			<?php echo esp_gravatar_image('[email protected]', 'Nazar Kolivoshka'); ?>
55 55
 		</a>
56 56
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
57 57
 			Nazar Kolivoshka
@@ -63,7 +63,7 @@  discard block
 block discarded – undo
63 63
 <ul class="wp-people-group" id="ee-people-group-support-staff">
64 64
 	<li class="wp-person" id="ee-person-jfeck">
65 65
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
66
-			<?php echo esp_gravatar_image( '[email protected]', 'Josh Feck' ); ?>
66
+			<?php echo esp_gravatar_image('[email protected]', 'Josh Feck'); ?>
67 67
 		</a>
68 68
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
69 69
 			Josh Feck
@@ -71,7 +71,7 @@  discard block
 block discarded – undo
71 71
 	</li>
72 72
 	<li class="wp-person" id="ee-person-twarwick">
73 73
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
74
-			<?php echo esp_gravatar_image( '[email protected]', 'Tony Warwick' ); ?>
74
+			<?php echo esp_gravatar_image('[email protected]', 'Tony Warwick'); ?>
75 75
 		</a>
76 76
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
77 77
 			Tony Warwick
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
 	</li>
80 80
 	<li class="wp-person" id="ee-person-lcaum">
81 81
 		<a href="<?php esp_gravatar_profile('[email protected]'); ?>">
82
-			<?php echo esp_gravatar_image( '[email protected]', 'Lorenzo Caum' ); ?>
82
+			<?php echo esp_gravatar_image('[email protected]', 'Lorenzo Caum'); ?>
83 83
 		</a>
84 84
 		<a class="web" href="<?php esp_gravatar_profile('[email protected]'); ?>">
85 85
 			Lorenzo Caum
@@ -89,7 +89,7 @@  discard block
 block discarded – undo
89 89
 </ul>
90 90
 <h3 class="wp-people-group"><?php _e('Contributor Recognition', 'event_espresso'); ?></h3>
91 91
 <p class="description">
92
-	<?php printf( __('For every major release we want to recognize the people who contributed to the release via a GitHub pull request. Want to see your name listed here? %sWhen you submit a pull request that gets included in a major release%s, we\'ll add your name here linked to your GitHub profile.', 'event_espresso'), '<a href="https://github.com/eventespresso/event-espresso-core" title="Contribute to Event Espresso by making a pull request via GitHub">', '</a>' ); ?>
92
+	<?php printf(__('For every major release we want to recognize the people who contributed to the release via a GitHub pull request. Want to see your name listed here? %sWhen you submit a pull request that gets included in a major release%s, we\'ll add your name here linked to your GitHub profile.', 'event_espresso'), '<a href="https://github.com/eventespresso/event-espresso-core" title="Contribute to Event Espresso by making a pull request via GitHub">', '</a>'); ?>
93 93
 </p>
94 94
 <p class="wp-credits-list">
95 95
 	<ul>
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 </p>
104 104
 <h3 class="wp-people-group"><?php _e('External Libraries', 'event_espresso'); ?></h3>
105 105
 <p class="description">
106
-	<?php printf( __('Along with the libraries %sincluded with WordPress%s, Event Espresso utilizes the following third party libraries:', 'event_espresso'), '<a href="credits.php">', '</a>' ); ?>
106
+	<?php printf(__('Along with the libraries %sincluded with WordPress%s, Event Espresso utilizes the following third party libraries:', 'event_espresso'), '<a href="credits.php">', '</a>'); ?>
107 107
 </p>
108 108
 <p class="wp-credits-list">
109 109
 	<a href="http://josscrowcroft.github.io/accounting.js/"><?php _e('accounting.js', 'event_espresso'); ?></a>,
@@ -119,10 +119,10 @@  discard block
 block discarded – undo
119 119
 
120 120
 <?php
121 121
 	function esp_gravatar_profile($email) {
122
-		echo 'http://www.gravatar.com/' . md5($email);
122
+		echo 'http://www.gravatar.com/'.md5($email);
123 123
 	}
124 124
 
125 125
 	function esp_gravatar_image($email, $name) {
126
-		echo '<img src="http://0.gravatar.com/avatar/' . md5($email) . '?s=60" class="gravatar" alt="' . $name . '"/>';
126
+		echo '<img src="http://0.gravatar.com/avatar/'.md5($email).'?s=60" class="gravatar" alt="'.$name.'"/>';
127 127
 	}
128 128
 ?>
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +1698 added lines, -1698 removed lines patch added patch discarded remove patch
@@ -1,5 +1,5 @@  discard block
 block discarded – undo
1 1
 <?php if (!defined('EVENT_ESPRESSO_VERSION')) {
2
-    exit('No direct script access allowed');
2
+	exit('No direct script access allowed');
3 3
 }
4 4
 
5 5
 /**
@@ -23,1703 +23,1703 @@  discard block
 block discarded – undo
23 23
 class EEH_Line_Item
24 24
 {
25 25
 
26
-    //other functions: cancel ticket purchase
27
-    //delete ticket purchase
28
-    //add promotion
29
-
30
-
31
-    /**
32
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
33
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
34
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
35
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
36
-     * 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
-    {
50
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
51
-        $line_item = EE_Line_Item::new_instance(array(
52
-            'LIN_name' => $name,
53
-            'LIN_desc' => $description,
54
-            'LIN_unit_price' => $unit_price,
55
-            'LIN_quantity' => $quantity,
56
-            'LIN_percent' => null,
57
-            'LIN_is_taxable' => $taxable,
58
-            'LIN_order' => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
59
-            'LIN_total' => (float)$unit_price * (int)$quantity,
60
-            'LIN_type' => EEM_Line_Item::type_line_item,
61
-            'LIN_code' => $code,
62
-        ));
63
-        $line_item = apply_filters(
64
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
65
-            $line_item,
66
-            $parent_line_item
67
-        );
68
-        return self::add_item($parent_line_item, $line_item);
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
-    {
91
-        $line_item = EE_Line_Item::new_instance(array(
92
-            'LIN_name' => $name,
93
-            'LIN_desc' => $description,
94
-            'LIN_unit_price' => 0,
95
-            'LIN_percent' => $percentage_amount,
96
-            'LIN_quantity' => 1,
97
-            'LIN_is_taxable' => $taxable,
98
-            'LIN_total' => (float)($percentage_amount * ($parent_line_item->total() / 100)),
99
-            'LIN_type' => EEM_Line_Item::type_line_item,
100
-            'LIN_parent' => $parent_line_item->ID()
101
-        ));
102
-        $line_item = apply_filters(
103
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
104
-            $line_item
105
-        );
106
-        return $parent_line_item->add_child_line_item($line_item, false);
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
-    {
128
-        if (!$total_line_item instanceof EE_Line_Item || !$total_line_item->is_total()) {
129
-            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()));
130
-        }
131
-        // either increment the qty for an existing ticket
132
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
133
-        // or add a new one
134
-        if (!$line_item instanceof EE_Line_Item) {
135
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
136
-        }
137
-        $total_line_item->recalculate_total_including_taxes();
138
-        return $line_item;
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
-    {
152
-        $line_item = null;
153
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
154
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
155
-            foreach ((array)$ticket_line_items as $ticket_line_item) {
156
-                if (
157
-                    $ticket_line_item instanceof EE_Line_Item
158
-                    && (int)$ticket_line_item->OBJ_ID() === (int)$ticket->ID()
159
-                ) {
160
-                    $line_item = $ticket_line_item;
161
-                    break;
162
-                }
163
-            }
164
-        }
165
-        if ($line_item instanceof EE_Line_Item) {
166
-            EEH_Line_Item::increment_quantity($line_item, $qty);
167
-            return $line_item;
168
-        }
169
-        return null;
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
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
183
-    {
184
-        if (!$line_item->is_percent()) {
185
-            $qty += $line_item->quantity();
186
-            $line_item->set_quantity($qty);
187
-            $line_item->set_total($line_item->unit_price() * $qty);
188
-            $line_item->save();
189
-        }
190
-        foreach ($line_item->children() as $child) {
191
-            if ($child->is_sub_line_item()) {
192
-                EEH_Line_Item::update_quantity($child, $qty);
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
-    {
209
-        if (!$line_item->is_percent()) {
210
-            $qty = $line_item->quantity() - $qty;
211
-            $qty = max($qty, 0);
212
-            $line_item->set_quantity($qty);
213
-            $line_item->set_total($line_item->unit_price() * $qty);
214
-            $line_item->save();
215
-        }
216
-        foreach ($line_item->children() as $child) {
217
-            if ($child->is_sub_line_item()) {
218
-                EEH_Line_Item::update_quantity($child, $qty);
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
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
233
-    {
234
-        if (!$line_item->is_percent()) {
235
-            $line_item->set_quantity($new_quantity);
236
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
237
-            $line_item->save();
238
-        }
239
-        foreach ($line_item->children() as $child) {
240
-            if ($child->is_sub_line_item()) {
241
-                EEH_Line_Item::update_quantity($child, $new_quantity);
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
-    {
257
-        $datetimes = $ticket->datetimes();
258
-        $first_datetime = reset($datetimes);
259
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
260
-            $first_datetime_name = $first_datetime->event()->name();
261
-        } else {
262
-            $first_datetime_name = __('Event', 'event_espresso');
263
-        }
264
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
265
-        // get event subtotal line
266
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
267
-        // add $ticket to cart
268
-        $line_item = EE_Line_Item::new_instance(array(
269
-            'LIN_name' => $ticket->name(),
270
-            'LIN_desc' => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
271
-            'LIN_unit_price' => $ticket->price(),
272
-            'LIN_quantity' => $qty,
273
-            'LIN_is_taxable' => $ticket->taxable(),
274
-            'LIN_order' => count($events_sub_total->children()),
275
-            'LIN_total' => $ticket->price() * $qty,
276
-            'LIN_type' => EEM_Line_Item::type_line_item,
277
-            'OBJ_ID' => $ticket->ID(),
278
-            'OBJ_type' => 'Ticket'
279
-        ));
280
-        $line_item = apply_filters(
281
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
282
-            $line_item
283
-        );
284
-        $events_sub_total->add_child_line_item($line_item);
285
-        //now add the sub-line items
286
-        $running_total_for_ticket = 0;
287
-        foreach ($ticket->prices(array('order_by' => array('PRC_order' => 'ASC'))) as $price) {
288
-            $sign = $price->is_discount() ? -1 : 1;
289
-            $price_total = $price->is_percent()
290
-                ? $running_total_for_ticket * $price->amount() / 100
291
-                : $price->amount() * $qty;
292
-            $sub_line_item = EE_Line_Item::new_instance(array(
293
-                'LIN_name' => $price->name(),
294
-                'LIN_desc' => $price->desc(),
295
-                'LIN_quantity' => $price->is_percent() ? null : $qty,
296
-                'LIN_is_taxable' => false,
297
-                'LIN_order' => $price->order(),
298
-                'LIN_total' => $sign * $price_total,
299
-                'LIN_type' => EEM_Line_Item::type_sub_line_item,
300
-                'OBJ_ID' => $price->ID(),
301
-                'OBJ_type' => 'Price'
302
-            ));
303
-            $sub_line_item = apply_filters(
304
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
305
-                $sub_line_item
306
-            );
307
-            if ($price->is_percent()) {
308
-                $sub_line_item->set_percent($sign * $price->amount());
309
-            } else {
310
-                $sub_line_item->set_unit_price($sign * $price->amount());
311
-            }
312
-            $running_total_for_ticket += $price_total;
313
-            $line_item->add_child_line_item($sub_line_item);
314
-        }
315
-        return $line_item;
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
-    {
334
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
335
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
336
-            $success = $pre_tax_subtotal->add_child_line_item($item);
337
-        } else {
338
-            return FALSE;
339
-        }
340
-        $total_line_item->recalculate_total_including_taxes();
341
-        return $success;
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
-    {
357
-        // validate incoming line_item
358
-        if ($ticket_line_item->OBJ_type() !== 'Ticket') {
359
-            throw new EE_Error(
360
-                sprintf(
361
-                    __('The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso'),
362
-                    $ticket_line_item->type()
363
-                )
364
-            );
365
-        }
366
-        if ($ticket_line_item->quantity() < $qty) {
367
-            throw new EE_Error(
368
-                sprintf(
369
-                    __('Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.', 'event_espresso'),
370
-                    $qty,
371
-                    $ticket_line_item->quantity()
372
-                )
373
-            );
374
-        }
375
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
376
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
377
-        foreach ($ticket_line_item->children() as $child_line_item) {
378
-            if (
379
-                $child_line_item->is_sub_line_item()
380
-                && !$child_line_item->is_percent()
381
-                && !$child_line_item->is_cancellation()
382
-            ) {
383
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
384
-            }
385
-        }
386
-        // get cancellation sub line item
387
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
388
-            $ticket_line_item,
389
-            EEM_Line_Item::type_cancellation
390
-        );
391
-        $cancellation_line_item = reset($cancellation_line_item);
392
-        // verify that this ticket was indeed previously cancelled
393
-        if ($cancellation_line_item instanceof EE_Line_Item) {
394
-            // increment cancelled quantity
395
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
396
-        } else {
397
-            // create cancellation sub line item
398
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
399
-                'LIN_name' => __('Cancellation', 'event_espresso'),
400
-                'LIN_desc' => sprintf(
401
-                    _x('Cancelled %1$s : %2$s', 'Cancelled Ticket Name : 2015-01-01 11:11', 'event_espresso'),
402
-                    $ticket_line_item->name(),
403
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
404
-                ),
405
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
406
-                'LIN_quantity' => $qty,
407
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
408
-                'LIN_order' => count($ticket_line_item->children()),
409
-                'LIN_total' => 0, // $ticket_line_item->unit_price()
410
-                'LIN_type' => EEM_Line_Item::type_cancellation,
411
-            ));
412
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
413
-        }
414
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
415
-            // decrement parent line item quantity
416
-            $event_line_item = $ticket_line_item->parent();
417
-            if ($event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event') {
418
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
419
-                $event_line_item->save();
420
-            }
421
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
422
-            return true;
423
-        }
424
-        return false;
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
-    {
440
-        // validate incoming line_item
441
-        if ($ticket_line_item->OBJ_type() !== 'Ticket') {
442
-            throw new EE_Error(
443
-                sprintf(
444
-                    __('The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso'),
445
-                    $ticket_line_item->type()
446
-                )
447
-            );
448
-        }
449
-        // get cancellation sub line item
450
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
451
-            $ticket_line_item,
452
-            EEM_Line_Item::type_cancellation
453
-        );
454
-        $cancellation_line_item = reset($cancellation_line_item);
455
-        // verify that this ticket was indeed previously cancelled
456
-        if (!$cancellation_line_item instanceof EE_Line_Item) {
457
-            return false;
458
-        }
459
-        if ($cancellation_line_item->quantity() > $qty) {
460
-            // decrement cancelled quantity
461
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
462
-        } else if ($cancellation_line_item->quantity() == $qty) {
463
-            // decrement cancelled quantity in case anyone still has the object kicking around
464
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
465
-            // delete because quantity will end up as 0
466
-            $cancellation_line_item->delete();
467
-            // and attempt to destroy the object,
468
-            // even though PHP won't actually destroy it until it needs the memory
469
-            unset($cancellation_line_item);
470
-        } else {
471
-            // what ?!?! negative quantity ?!?!
472
-            throw new EE_Error(
473
-                sprintf(
474
-                    __('Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
475
-                        'event_espresso'),
476
-                    $qty,
477
-                    $cancellation_line_item->quantity()
478
-                )
479
-            );
480
-        }
481
-        // increment ticket quantity
482
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
483
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
484
-            // increment parent line item quantity
485
-            $event_line_item = $ticket_line_item->parent();
486
-            if ($event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event') {
487
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
488
-            }
489
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
490
-            return true;
491
-        }
492
-        return false;
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
-    {
505
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
506
-        return $grand_total_line_item->recalculate_total_including_taxes();
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
-    {
519
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
520
-        return $pre_tax_subtotal instanceof EE_Line_Item
521
-            ? $pre_tax_subtotal
522
-            : self::create_pre_tax_subtotal($total_line_item);
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
-    {
535
-        $taxes = $total_line_item->get_child_line_item('taxes');
536
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
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
-    {
550
-        if ($transaction) {
551
-            /** @type EEM_Transaction $EEM_Transaction */
552
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
553
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
554
-            $line_item->set_TXN_ID($TXN_ID);
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
-    public static function create_total_line_item($transaction = NULL)
569
-    {
570
-        $total_line_item = EE_Line_Item::new_instance(array(
571
-            'LIN_code' => 'total',
572
-            'LIN_name' => __('Grand Total', 'event_espresso'),
573
-            'LIN_type' => EEM_Line_Item::type_total,
574
-            'OBJ_type' => 'Transaction'
575
-        ));
576
-        $total_line_item = apply_filters(
577
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
578
-            $total_line_item
579
-        );
580
-        self::set_TXN_ID($total_line_item, $transaction);
581
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
582
-        self::create_taxes_subtotal($total_line_item, $transaction);
583
-        return $total_line_item;
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
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
596
-    {
597
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
598
-            'LIN_code' => 'pre-tax-subtotal',
599
-            'LIN_name' => __('Pre-Tax Subtotal', 'event_espresso'),
600
-            'LIN_type' => EEM_Line_Item::type_sub_total
601
-        ));
602
-        $pre_tax_line_item = apply_filters(
603
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
604
-            $pre_tax_line_item
605
-        );
606
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
607
-        $total_line_item->add_child_line_item($pre_tax_line_item);
608
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
609
-        return $pre_tax_line_item;
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
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
623
-    {
624
-        $tax_line_item = EE_Line_Item::new_instance(array(
625
-            'LIN_code' => 'taxes',
626
-            'LIN_name' => __('Taxes', 'event_espresso'),
627
-            'LIN_type' => EEM_Line_Item::type_tax_sub_total,
628
-            'LIN_order' => 1000,//this should always come last
629
-        ));
630
-        $tax_line_item = apply_filters(
631
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
632
-            $tax_line_item
633
-        );
634
-        self::set_TXN_ID($tax_line_item, $transaction);
635
-        $total_line_item->add_child_line_item($tax_line_item);
636
-        //and lastly, add the actual taxes
637
-        self::apply_taxes($total_line_item);
638
-        return $tax_line_item;
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
-    {
653
-        $event_line_item = EE_Line_Item::new_instance(array(
654
-            'LIN_code' => self::get_event_code($event),
655
-            'LIN_name' => self::get_event_name($event),
656
-            'LIN_desc' => self::get_event_desc($event),
657
-            'LIN_type' => EEM_Line_Item::type_sub_total,
658
-            'OBJ_type' => 'Event',
659
-            'OBJ_ID' => $event instanceof EE_Event ? $event->ID() : 0
660
-        ));
661
-        $event_line_item = apply_filters(
662
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
663
-            $event_line_item
664
-        );
665
-        self::set_TXN_ID($event_line_item, $transaction);
666
-        $pre_tax_line_item->add_child_line_item($event_line_item);
667
-        return $event_line_item;
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
-    {
680
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
681
-    }
682
-
683
-    /**
684
-     * Gets the event name
685
-     * @param EE_Event $event
686
-     * @return string
687
-     */
688
-    public static function get_event_name($event)
689
-    {
690
-        return $event instanceof EE_Event ? $event->name() : __('Event', 'event_espresso');
691
-    }
692
-
693
-    /**
694
-     * Gets the event excerpt
695
-     * @param EE_Event $event
696
-     * @return string
697
-     */
698
-    public static function get_event_desc($event)
699
-    {
700
-        return $event instanceof EE_Event ? $event->short_description() : '';
701
-    }
702
-
703
-    /**
704
-     * Given the grand total line item and a ticket, finds the event sub-total
705
-     * line item the ticket's purchase should be added onto
706
-     *
707
-     * @access public
708
-     * @param EE_Line_Item $grand_total the grand total line item
709
-     * @param EE_Ticket $ticket
710
-     * @throws \EE_Error
711
-     * @return EE_Line_Item
712
-     */
713
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
714
-    {
715
-        $first_datetime = $ticket->first_datetime();
716
-        if (!$first_datetime instanceof EE_Datetime) {
717
-            throw new EE_Error(
718
-                sprintf(__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'), $ticket->ID())
719
-            );
720
-        }
721
-        $event = $first_datetime->event();
722
-        if (!$event instanceof EE_Event) {
723
-            throw new EE_Error(
724
-                sprintf(
725
-                    __('The supplied ticket (ID %d) has no event data associated with it.', 'event_espresso'),
726
-                    $ticket->ID()
727
-                )
728
-            );
729
-        }
730
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
731
-        if (!$events_sub_total instanceof EE_Line_Item) {
732
-            throw new EE_Error(
733
-                sprintf(
734
-                    __('There is no events sub-total for ticket %s on total line item %d', 'event_espresso'),
735
-                    $ticket->ID(),
736
-                    $grand_total->ID()
737
-                )
738
-            );
739
-        }
740
-        return $events_sub_total;
741
-    }
742
-
743
-
744
-    /**
745
-     * Gets the event line item
746
-     *
747
-     * @param EE_Line_Item $grand_total
748
-     * @param EE_Event $event
749
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
750
-     * @throws \EE_Error
751
-     */
752
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
753
-    {
754
-        /** @type EE_Event $event */
755
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
756
-        $event_line_item = NULL;
757
-        $found = false;
758
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
759
-            // default event subtotal, we should only ever find this the first time this method is called
760
-            if (!$event_line_item->OBJ_ID()) {
761
-                // let's use this! but first... set the event details
762
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
763
-                $found = true;
764
-                break;
765
-            } else if ($event_line_item->OBJ_ID() === $event->ID()) {
766
-                // found existing line item for this event in the cart, so break out of loop and use this one
767
-                $found = true;
768
-                break;
769
-            }
770
-        }
771
-        if (!$found) {
772
-            //there is no event sub-total yet, so add it
773
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
774
-            // create a new "event" subtotal below that
775
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
776
-            // and set the event details
777
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
778
-        }
779
-        return $event_line_item;
780
-    }
781
-
782
-
783
-    /**
784
-     * Creates a default items subtotal line item
785
-     *
786
-     * @param EE_Line_Item $event_line_item
787
-     * @param EE_Event $event
788
-     * @param EE_Transaction $transaction
789
-     * @return EE_Line_Item
790
-     * @throws \EE_Error
791
-     */
792
-    public static function set_event_subtotal_details(
793
-        EE_Line_Item $event_line_item,
794
-        EE_Event $event,
795
-        $transaction = null
796
-    )
797
-    {
798
-        if ($event instanceof EE_Event) {
799
-            $event_line_item->set_code(self::get_event_code($event));
800
-            $event_line_item->set_name(self::get_event_name($event));
801
-            $event_line_item->set_desc(self::get_event_desc($event));
802
-            $event_line_item->set_OBJ_ID($event->ID());
803
-        }
804
-        self::set_TXN_ID($event_line_item, $transaction);
805
-    }
806
-
807
-
808
-    /**
809
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
810
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
811
-     * any old taxes are removed
812
-     *
813
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
814
-     * @throws \EE_Error
815
-     */
816
-    public static function apply_taxes(EE_Line_Item $total_line_item)
817
-    {
818
-        /** @type EEM_Price $EEM_Price */
819
-        $EEM_Price = EE_Registry::instance()->load_model('Price');
820
-        // get array of taxes via Price Model
821
-        $ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
822
-        ksort($ordered_taxes);
823
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
824
-        //just to be safe, remove its old tax line items
825
-        $taxes_line_item->delete_children_line_items();
826
-        //loop thru taxes
827
-        foreach ($ordered_taxes as $order => $taxes) {
828
-            foreach ($taxes as $tax) {
829
-                if ($tax instanceof EE_Price) {
830
-                    $tax_line_item = EE_Line_Item::new_instance(
831
-                        array(
832
-                            'LIN_name' => $tax->name(),
833
-                            'LIN_desc' => $tax->desc(),
834
-                            'LIN_percent' => $tax->amount(),
835
-                            'LIN_is_taxable' => false,
836
-                            'LIN_order' => $order,
837
-                            'LIN_total' => 0,
838
-                            'LIN_type' => EEM_Line_Item::type_tax,
839
-                            'OBJ_type' => 'Price',
840
-                            'OBJ_ID' => $tax->ID()
841
-                        )
842
-                    );
843
-                    $tax_line_item = apply_filters(
844
-                        'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
845
-                        $tax_line_item
846
-                    );
847
-                    $taxes_line_item->add_child_line_item($tax_line_item);
848
-                }
849
-            }
850
-        }
851
-        $total_line_item->recalculate_total_including_taxes();
852
-    }
853
-
854
-
855
-    /**
856
-     * Ensures that taxes have been applied to the order, if not applies them.
857
-     * Returns the total amount of tax
858
-     *
859
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
860
-     * @return float
861
-     * @throws \EE_Error
862
-     */
863
-    public static function ensure_taxes_applied($total_line_item)
864
-    {
865
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
866
-        if (!$taxes_subtotal->children()) {
867
-            self::apply_taxes($total_line_item);
868
-        }
869
-        return $taxes_subtotal->total();
870
-    }
871
-
872
-
873
-    /**
874
-     * Deletes ALL children of the passed line item
875
-     *
876
-     * @param EE_Line_Item $parent_line_item
877
-     * @return bool
878
-     * @throws \EE_Error
879
-     */
880
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
881
-    {
882
-        $deleted = 0;
883
-        foreach ($parent_line_item->children() as $child_line_item) {
884
-            if ($child_line_item instanceof EE_Line_Item) {
885
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
886
-                if ($child_line_item->ID()) {
887
-                    $child_line_item->delete();
888
-                    unset($child_line_item);
889
-                } else {
890
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
891
-                }
892
-                $deleted++;
893
-            }
894
-        }
895
-        return $deleted;
896
-    }
897
-
898
-
899
-    /**
900
-     * Deletes the line items as indicated by the line item code(s) provided,
901
-     * regardless of where they're found in the line item tree. Automatically
902
-     * re-calculates the line item totals and updates the related transaction. But
903
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
904
-     * should probably change because of this).
905
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
906
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
907
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
908
-     * @param array|bool|string $line_item_codes
909
-     * @return int number of items successfully removed
910
-     */
911
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = FALSE)
912
-    {
913
-
914
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
915
-            EE_Error::doing_it_wrong(
916
-                'EEH_Line_Item::delete_items',
917
-                __(
918
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
919
-                    'event_espresso'
920
-                ),
921
-                '4.6.18'
922
-            );
923
-        }
924
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
925
-
926
-        // check if only a single line_item_id was passed
927
-        if (!empty($line_item_codes) && !is_array($line_item_codes)) {
928
-            // place single line_item_id in an array to appear as multiple line_item_ids
929
-            $line_item_codes = array($line_item_codes);
930
-        }
931
-        $removals = 0;
932
-        // cycle thru line_item_ids
933
-        foreach ($line_item_codes as $line_item_id) {
934
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
935
-        }
936
-
937
-        if ($removals > 0) {
938
-            $total_line_item->recalculate_taxes_and_tax_total();
939
-            return $removals;
940
-        } else {
941
-            return FALSE;
942
-        }
943
-    }
944
-
945
-
946
-    /**
947
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
948
-     * tax and updates the total line item accordingly
949
-     *
950
-     * @param EE_Line_Item $total_line_item
951
-     * @param float $amount
952
-     * @param string $name
953
-     * @param string $description
954
-     * @param string $code
955
-     * @param boolean $add_to_existing_line_item
956
-     *                          if true, and a duplicate line item with the same code is found,
957
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
958
-     * @return EE_Line_Item the new tax line item created
959
-     * @throws \EE_Error
960
-     */
961
-    public static function set_total_tax_to(
962
-        EE_Line_Item $total_line_item,
963
-        $amount,
964
-        $name = null,
965
-        $description = null,
966
-        $code = null,
967
-        $add_to_existing_line_item = false
968
-    )
969
-    {
970
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
971
-        $taxable_total = $total_line_item->taxable_total();
972
-
973
-        if ($add_to_existing_line_item) {
974
-            $new_tax = $tax_subtotal->get_child_line_item($code);
975
-            EEM_Line_Item::instance()->delete(
976
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
977
-            );
978
-        } else {
979
-            $new_tax = null;
980
-            $tax_subtotal->delete_children_line_items();
981
-        }
982
-        if ($new_tax) {
983
-            $new_tax->set_total($new_tax->total() + $amount);
984
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
985
-        } else {
986
-            //no existing tax item. Create it
987
-            $new_tax = EE_Line_Item::new_instance(array(
988
-                'TXN_ID' => $total_line_item->TXN_ID(),
989
-                'LIN_name' => $name ? $name : __('Tax', 'event_espresso'),
990
-                'LIN_desc' => $description ? $description : '',
991
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
992
-                'LIN_total' => $amount,
993
-                'LIN_parent' => $tax_subtotal->ID(),
994
-                'LIN_type' => EEM_Line_Item::type_tax,
995
-                'LIN_code' => $code
996
-            ));
997
-        }
998
-
999
-        $new_tax = apply_filters(
1000
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1001
-            $new_tax,
1002
-            $total_line_item
1003
-        );
1004
-        $new_tax->save();
1005
-        $tax_subtotal->set_total($new_tax->total());
1006
-        $tax_subtotal->save();
1007
-        $total_line_item->recalculate_total_including_taxes();
1008
-        return $new_tax;
1009
-    }
1010
-
1011
-
1012
-    /**
1013
-     * Makes all the line items which are children of $line_item taxable (or not).
1014
-     * Does NOT save the line items
1015
-     * @param EE_Line_Item $line_item
1016
-     * @param string $code_substring_for_whitelist if this string is part of the line item's code
1017
-     *  it will be whitelisted (ie, except from becoming taxable)
1018
-     * @param boolean $taxable
1019
-     */
1020
-    public static function set_line_items_taxable(
1021
-        EE_Line_Item $line_item,
1022
-        $taxable = true,
1023
-        $code_substring_for_whitelist = null
1024
-    )
1025
-    {
1026
-        $whitelisted = false;
1027
-        if ($code_substring_for_whitelist !== null) {
1028
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false ? true : false;
1029
-        }
1030
-        if (!$whitelisted && $line_item->is_line_item()) {
1031
-            $line_item->set_is_taxable($taxable);
1032
-        }
1033
-        foreach ($line_item->children() as $child_line_item) {
1034
-            EEH_Line_Item::set_line_items_taxable($child_line_item, $taxable, $code_substring_for_whitelist);
1035
-        }
1036
-    }
1037
-
1038
-
1039
-    /**
1040
-     * Gets all descendants that are event subtotals
1041
-     *
1042
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1043
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1044
-     * @return EE_Line_Item[]
1045
-     */
1046
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1047
-    {
1048
-        return self::get_subtotals_of_object_type($parent_line_item, 'Event');
1049
-    }
1050
-
1051
-
1052
-    /**
1053
-     * Gets all descendants subtotals that match the supplied object type
1054
-     *
1055
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1056
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1057
-     * @param string $obj_type
1058
-     * @return EE_Line_Item[]
1059
-     */
1060
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1061
-    {
1062
-        return self::_get_descendants_by_type_and_object_type(
1063
-            $parent_line_item,
1064
-            EEM_Line_Item::type_sub_total,
1065
-            $obj_type
1066
-        );
1067
-    }
1068
-
1069
-
1070
-    /**
1071
-     * Gets all descendants that are tickets
1072
-     *
1073
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1074
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1075
-     * @return EE_Line_Item[]
1076
-     */
1077
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1078
-    {
1079
-        return self::get_line_items_of_object_type($parent_line_item, 'Ticket');
1080
-    }
1081
-
1082
-
1083
-    /**
1084
-     * Gets all descendants subtotals that match the supplied object type
1085
-     *
1086
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1087
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1088
-     * @param string $obj_type
1089
-     * @return EE_Line_Item[]
1090
-     */
1091
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1092
-    {
1093
-        return self::_get_descendants_by_type_and_object_type($parent_line_item, EEM_Line_Item::type_line_item, $obj_type);
1094
-    }
1095
-
1096
-
1097
-    /**
1098
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1099
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1100
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1101
-     * @return EE_Line_Item[]
1102
-     */
1103
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1104
-    {
1105
-        return EEH_Line_Item::get_descendants_of_type($parent_line_item, EEM_Line_Item::type_tax);
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * Gets all the real items purchased which are children of this item
1111
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1112
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1113
-     * @return EE_Line_Item[]
1114
-     */
1115
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1116
-    {
1117
-        return EEH_Line_Item::get_descendants_of_type($parent_line_item, EEM_Line_Item::type_line_item);
1118
-    }
1119
-
1120
-
1121
-    /**
1122
-     * Gets all descendants of supplied line item that match the supplied line item type
1123
-     *
1124
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1125
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1126
-     * @param string $line_item_type one of the EEM_Line_Item constants
1127
-     * @return EE_Line_Item[]
1128
-     */
1129
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1130
-    {
1131
-        return self::_get_descendants_by_type_and_object_type($parent_line_item, $line_item_type, NULL);
1132
-    }
1133
-
1134
-
1135
-    /**
1136
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1137
-     *
1138
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1139
-     * @param string $line_item_type one of the EEM_Line_Item constants
1140
-     * @param string | NULL $obj_type object model class name (minus prefix) or NULL to ignore object type when searching
1141
-     * @return EE_Line_Item[]
1142
-     */
1143
-    protected static function _get_descendants_by_type_and_object_type(
1144
-        EE_Line_Item $parent_line_item,
1145
-        $line_item_type,
1146
-        $obj_type = null
1147
-    )
1148
-    {
1149
-        $objects = array();
1150
-        foreach ($parent_line_item->children() as $child_line_item) {
1151
-            if ($child_line_item instanceof EE_Line_Item) {
1152
-                if (
1153
-                    $child_line_item->type() === $line_item_type
1154
-                    && (
1155
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1156
-                    )
1157
-                ) {
1158
-                    $objects[] = $child_line_item;
1159
-                } else {
1160
-                    //go-through-all-its children looking for more matches
1161
-                    $objects = array_merge(
1162
-                        $objects,
1163
-                        self::_get_descendants_by_type_and_object_type(
1164
-                            $child_line_item,
1165
-                            $line_item_type,
1166
-                            $obj_type
1167
-                        )
1168
-                    );
1169
-                }
1170
-            }
1171
-        }
1172
-        return $objects;
1173
-    }
1174
-
1175
-
1176
-    /**
1177
-     * Gets all descendants subtotals that match the supplied object type
1178
-     *
1179
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1180
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1181
-     * @param string $OBJ_type object type (like Event)
1182
-     * @param array $OBJ_IDs array of OBJ_IDs
1183
-     * @return EE_Line_Item[]
1184
-     */
1185
-    public static function get_line_items_by_object_type_and_IDs(
1186
-        EE_Line_Item $parent_line_item,
1187
-        $OBJ_type = '',
1188
-        $OBJ_IDs = array()
1189
-    )
1190
-    {
1191
-        return self::_get_descendants_by_object_type_and_object_ID($parent_line_item, $OBJ_type, $OBJ_IDs);
1192
-    }
1193
-
1194
-
1195
-    /**
1196
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1197
-     *
1198
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1199
-     * @param string $OBJ_type object type (like Event)
1200
-     * @param array $OBJ_IDs array of OBJ_IDs
1201
-     * @return EE_Line_Item[]
1202
-     */
1203
-    protected static function _get_descendants_by_object_type_and_object_ID(
1204
-        EE_Line_Item $parent_line_item,
1205
-        $OBJ_type,
1206
-        $OBJ_IDs
1207
-    )
1208
-    {
1209
-        $objects = array();
1210
-        foreach ($parent_line_item->children() as $child_line_item) {
1211
-            if ($child_line_item instanceof EE_Line_Item) {
1212
-                if (
1213
-                    $child_line_item->OBJ_type() === $OBJ_type
1214
-                    && is_array($OBJ_IDs)
1215
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1216
-                ) {
1217
-                    $objects[] = $child_line_item;
1218
-                } else {
1219
-                    //go-through-all-its children looking for more matches
1220
-                    $objects = array_merge(
1221
-                        $objects,
1222
-                        self::_get_descendants_by_object_type_and_object_ID(
1223
-                            $child_line_item,
1224
-                            $OBJ_type,
1225
-                            $OBJ_IDs
1226
-                        )
1227
-                    );
1228
-                }
1229
-            }
1230
-        }
1231
-        return $objects;
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * Uses a breadth-first-search in order to find the nearest descendant of
1237
-     * the specified type and returns it, else NULL
1238
-     *
1239
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1240
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1241
-     * @param string $type like one of the EEM_Line_Item::type_*
1242
-     * @return EE_Line_Item
1243
-     */
1244
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1245
-    {
1246
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1247
-    }
1248
-
1249
-
1250
-    /**
1251
-     * Uses a breadth-first-search in order to find the nearest descendant
1252
-     * having the specified LIN_code and returns it, else NULL
1253
-     *
1254
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1255
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1256
-     * @param string $code any value used for LIN_code
1257
-     * @return EE_Line_Item
1258
-     */
1259
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1260
-    {
1261
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1262
-    }
1263
-
1264
-
1265
-    /**
1266
-     * Uses a breadth-first-search in order to find the nearest descendant
1267
-     * having the specified LIN_code and returns it, else NULL
1268
-     *
1269
-     * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1270
-     * @param string $search_field name of EE_Line_Item property
1271
-     * @param string $value any value stored in $search_field
1272
-     * @return EE_Line_Item
1273
-     */
1274
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1275
-    {
1276
-        foreach ($parent_line_item->children() as $child) {
1277
-            if ($child->get($search_field) == $value) {
1278
-                return $child;
1279
-            }
1280
-        }
1281
-        foreach ($parent_line_item->children() as $child) {
1282
-            $descendant_found = self::_get_nearest_descendant($child, $search_field, $value);
1283
-            if ($descendant_found) {
1284
-                return $descendant_found;
1285
-            }
1286
-        }
1287
-        return NULL;
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1293
-     * else recursively walks up the line item tree until a parent of type total is found,
1294
-     *
1295
-     * @param EE_Line_Item $line_item
1296
-     * @return \EE_Line_Item
1297
-     * @throws \EE_Error
1298
-     */
1299
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1300
-    {
1301
-        if ($line_item->TXN_ID()) {
1302
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1303
-            if ($total_line_item instanceof EE_Line_Item) {
1304
-                return $total_line_item;
1305
-            }
1306
-        } else {
1307
-            $line_item_parent = $line_item->parent();
1308
-            if ($line_item_parent instanceof EE_Line_Item) {
1309
-                if ($line_item_parent->is_total()) {
1310
-                    return $line_item_parent;
1311
-                }
1312
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1313
-            }
1314
-        }
1315
-        throw new EE_Error(
1316
-            sprintf(
1317
-                __('A valid grand total for line item %1$d was not found.', 'event_espresso'),
1318
-                $line_item->ID()
1319
-            )
1320
-        );
1321
-    }
1322
-
1323
-
1324
-    /**
1325
-     * Prints out a representation of the line item tree
1326
-     *
1327
-     * @param EE_Line_Item $line_item
1328
-     * @param int $indentation
1329
-     * @return void
1330
-     * @throws \EE_Error
1331
-     */
1332
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1333
-    {
1334
-        echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1335
-        if (!$indentation) {
1336
-            echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1337
-        }
1338
-        for ($i = 0; $i < $indentation; $i++) {
1339
-            echo ". ";
1340
-        }
1341
-        $breakdown = '';
1342
-        if ($line_item->is_line_item()) {
1343
-            if ($line_item->is_percent()) {
1344
-                $breakdown = "{$line_item->percent()}%";
1345
-            } else {
1346
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1347
-            }
1348
-        }
1349
-        echo $line_item->name() . " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : " . '$' . "{$line_item->total()}";
1350
-        if ($breakdown) {
1351
-            echo " ( {$breakdown} )";
1352
-        }
1353
-        if ($line_item->is_taxable()) {
1354
-            echo "  * taxable";
1355
-        }
1356
-        if ($line_item->children()) {
1357
-            foreach ($line_item->children() as $child) {
1358
-                self::visualize($child, $indentation + 1);
1359
-            }
1360
-        }
1361
-    }
1362
-
1363
-
1364
-    /**
1365
-     * Calculates the registration's final price, taking into account that they
1366
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1367
-     * and receive a portion of any transaction-wide discounts.
1368
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1369
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1370
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1371
-     * and brent's final price should be $5.50.
1372
-     *
1373
-     * In order to do this, we basically need to traverse the line item tree calculating
1374
-     * the running totals (just as if we were recalculating the total), but when we identify
1375
-     * regular line items, we need to keep track of their share of the grand total.
1376
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1377
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1378
-     * when there are non-taxable items; otherwise they would be the same)
1379
-     *
1380
-     * @param EE_Line_Item $line_item
1381
-     * @param array $billable_ticket_quantities array of EE_Ticket IDs and their corresponding quantity that
1382
-     *                                                                            can be included in price calculations at this moment
1383
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1384
-     *                                          plus the key 'total', and 'taxable' which also has keys of all the ticket IDs. Eg
1385
-     *                                          array(
1386
-     *                                          12 => 4.3
1387
-     *                                          23 => 8.0
1388
-     *                                          'total' => 16.6,
1389
-     *                                          'taxable' => array(
1390
-     *                                          12 => 10,
1391
-     *                                          23 => 4
1392
-     *                                          ).
1393
-     *                                          So to find which registrations have which final price, we need to find which line item
1394
-     *                                          is theirs, which can be done with
1395
-     *                                          `EEM_Line_Item::instance()->get_line_item_for_registration( $registration );`
1396
-     */
1397
-    public static function calculate_reg_final_prices_per_line_item(EE_Line_Item $line_item, $billable_ticket_quantities = array())
1398
-    {
1399
-        //init running grand total if not already
1400
-        if (!isset($running_totals['total'])) {
1401
-            $running_totals['total'] = 0;
1402
-        }
1403
-        if (!isset($running_totals['taxable'])) {
1404
-            $running_totals['taxable'] = array('total' => 0);
1405
-        }
1406
-        foreach ($line_item->children() as $child_line_item) {
1407
-            switch ($child_line_item->type()) {
1408
-
1409
-                case EEM_Line_Item::type_sub_total :
1410
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item($child_line_item, $billable_ticket_quantities);
1411
-                    //combine arrays but preserve numeric keys
1412
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1413
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1414
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1415
-                    break;
1416
-
1417
-                case EEM_Line_Item::type_tax_sub_total :
1418
-
1419
-                    //find how much the taxes percentage is
1420
-                    if ($child_line_item->percent() !== 0) {
1421
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1422
-                    } else {
1423
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1424
-                    }
1425
-                    //and apply to all the taxable totals, and add to the pretax totals
1426
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1427
-                        //"total" and "taxable" array key is an exception
1428
-                        if ($line_item_id === 'taxable') {
1429
-                            continue;
1430
-                        }
1431
-                        $taxable_total = $running_totals['taxable'][$line_item_id];
1432
-                        $running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1433
-                    }
1434
-                    break;
1435
-
1436
-                case EEM_Line_Item::type_line_item :
1437
-
1438
-                    // ticket line items or ????
1439
-                    if ($child_line_item->OBJ_type() === 'Ticket') {
1440
-                        // kk it's a ticket
1441
-                        if (isset($running_totals[$child_line_item->ID()])) {
1442
-                            //huh? that shouldn't happen.
1443
-                            $running_totals['total'] += $child_line_item->total();
1444
-                        } else {
1445
-                            //its not in our running totals yet. great.
1446
-                            if ($child_line_item->is_taxable()) {
1447
-                                $taxable_amount = $child_line_item->unit_price();
1448
-                            } else {
1449
-                                $taxable_amount = 0;
1450
-                            }
1451
-                            // are we only calculating totals for some tickets?
1452
-                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1453
-                                $quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1454
-                                $running_totals[$child_line_item->ID()] = $quantity
1455
-                                    ? $child_line_item->unit_price()
1456
-                                    : 0;
1457
-                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1458
-                                    ? $taxable_amount
1459
-                                    : 0;
1460
-                            } else {
1461
-                                $quantity = $child_line_item->quantity();
1462
-                                $running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1463
-                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1464
-                            }
1465
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1466
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1467
-                        }
1468
-                    } else {
1469
-                        // it's some other type of item added to the cart
1470
-                        // it should affect the running totals
1471
-                        // basically we want to convert it into a PERCENT modifier. Because
1472
-                        // more clearly affect all registration's final price equally
1473
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1474
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1475
-                            : 1;
1476
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1477
-                            //the "taxable" array key is an exception
1478
-                            if ($line_item_id === 'taxable') {
1479
-                                continue;
1480
-                            }
1481
-                            // update the running totals
1482
-                            // yes this actually even works for the running grand total!
1483
-                            $running_totals[$line_item_id] =
1484
-                                $line_items_percent_of_running_total * $this_running_total;
1485
-
1486
-                            if ($child_line_item->is_taxable()) {
1487
-                                $running_totals['taxable'][$line_item_id] =
1488
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1489
-                            }
1490
-                        }
1491
-                    }
1492
-                    break;
1493
-            }
1494
-        }
1495
-        return $running_totals;
1496
-    }
1497
-
1498
-
1499
-    /**
1500
-     * @param \EE_Line_Item $total_line_item
1501
-     * @param \EE_Line_Item $ticket_line_item
1502
-     * @return float | null
1503
-     * @throws \OutOfRangeException
1504
-     */
1505
-    public static function calculate_final_price_for_ticket_line_item(\EE_Line_Item $total_line_item, \EE_Line_Item $ticket_line_item)
1506
-    {
1507
-        static $final_prices_per_ticket_line_item = array();
1508
-        if (empty($final_prices_per_ticket_line_item)) {
1509
-            $final_prices_per_ticket_line_item = \EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1510
-                $total_line_item
1511
-            );
1512
-        }
1513
-        //ok now find this new registration's final price
1514
-        if (isset($final_prices_per_ticket_line_item[$ticket_line_item->ID()])) {
1515
-            return $final_prices_per_ticket_line_item[$ticket_line_item->ID()];
1516
-        }
1517
-        $message = sprintf(
1518
-            __(
1519
-                'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1520
-                'event_espresso'
1521
-            ),
1522
-            $ticket_line_item->ID()
1523
-        );
1524
-        if (WP_DEBUG) {
1525
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1526
-            throw new \OutOfRangeException($message);
1527
-        } else {
1528
-            EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1529
-        }
1530
-        return null;
1531
-    }
1532
-
1533
-
1534
-    /**
1535
-     * Creates a duplicate of the line item tree, except only includes billable items
1536
-     * and the portion of line items attributed to billable things
1537
-     *
1538
-     * @param EE_Line_Item $line_item
1539
-     * @param EE_Registration[] $registrations
1540
-     * @return \EE_Line_Item
1541
-     * @throws \EE_Error
1542
-     */
1543
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1544
-    {
1545
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1546
-        foreach ($line_item->children() as $child_li) {
1547
-            $copy_li->add_child_line_item(EEH_Line_Item::billable_line_item_tree($child_li, $registrations));
1548
-        }
1549
-        //if this is the grand total line item, make sure the totals all add up
1550
-        //(we could have duplicated this logic AS we copied the line items, but
1551
-        //it seems DRYer this way)
1552
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1553
-            $copy_li->recalculate_total_including_taxes();
1554
-        }
1555
-        return $copy_li;
1556
-    }
1557
-
1558
-
1559
-    /**
1560
-     * Creates a new, unsaved line item from $line_item that factors in the
1561
-     * number of billable registrations on $registrations.
1562
-     *
1563
-     * @param EE_Line_Item $line_item
1564
-     * @return EE_Line_Item
1565
-     * @throws \EE_Error
1566
-     * @param EE_Registration[] $registrations
1567
-     */
1568
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1569
-    {
1570
-        $new_li_fields = $line_item->model_field_array();
1571
-        if ($line_item->type() === EEM_Line_Item::type_line_item &&
1572
-            $line_item->OBJ_type() === 'Ticket'
1573
-        ) {
1574
-            $count = 0;
1575
-            foreach ($registrations as $registration) {
1576
-                if ($line_item->OBJ_ID() === $registration->ticket_ID() &&
1577
-                    in_array($registration->status_ID(), EEM_Registration::reg_statuses_that_allow_payment())
1578
-                ) {
1579
-                    $count++;
1580
-                }
1581
-            }
1582
-            $new_li_fields['LIN_quantity'] = $count;
1583
-        }
1584
-        //don't set the total. We'll leave that up to the code that calculates it
1585
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1586
-        return EE_Line_Item::new_instance($new_li_fields);
1587
-    }
1588
-
1589
-
1590
-    /**
1591
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1592
-     * are removed, and line items with a quantity of 0
1593
-     *
1594
-     * @param EE_Line_Item $line_item |null
1595
-     * @return \EE_Line_Item|null
1596
-     * @throws \EE_Error
1597
-     */
1598
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1599
-    {
1600
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1601
-        if ($copied_li === null) {
1602
-            return null;
1603
-        }
1604
-        //if this is an event subtotal, we want to only include it if it
1605
-        //has a non-zero total and at least one ticket line item child
1606
-        $ticket_children = 0;
1607
-        foreach ($line_item->children() as $child_li) {
1608
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1609
-            if ($child_li_copy !== null) {
1610
-                $copied_li->add_child_line_item($child_li_copy);
1611
-                if ($child_li_copy->type() === EEM_Line_Item::type_line_item &&
1612
-                    $child_li_copy->OBJ_type() === 'Ticket'
1613
-                ) {
1614
-                    $ticket_children++;
1615
-                }
1616
-            }
1617
-        }
1618
-        //if this is an event subtotal with NO ticket children
1619
-        //we basically want to ignore it
1620
-        if (
1621
-            $ticket_children === 0
1622
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1623
-            && $line_item->OBJ_type() === 'Event'
1624
-            && $line_item->total() === 0
1625
-        ) {
1626
-            return null;
1627
-        }
1628
-        return $copied_li;
1629
-    }
1630
-
1631
-
1632
-    /**
1633
-     * Creates a new, unsaved line item, but if it's a ticket line item
1634
-     * with a total of 0, or a subtotal of 0, returns null instead
1635
-     *
1636
-     * @param EE_Line_Item $line_item
1637
-     * @return EE_Line_Item
1638
-     * @throws \EE_Error
1639
-     */
1640
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1641
-    {
1642
-        if ($line_item->type() === EEM_Line_Item::type_line_item &&
1643
-            $line_item->OBJ_type() === 'Ticket' &&
1644
-            $line_item->quantity() === 0
1645
-        ) {
1646
-            return null;
1647
-        }
1648
-        $new_li_fields = $line_item->model_field_array();
1649
-        //don't set the total. We'll leave that up to the code that calculates it
1650
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1651
-        return EE_Line_Item::new_instance($new_li_fields);
1652
-    }
1653
-
1654
-
1655
-
1656
-    /**************************************** @DEPRECATED METHODS *************************************** */
1657
-    /**
1658
-     * @deprecated
1659
-     * @param EE_Line_Item $total_line_item
1660
-     * @return \EE_Line_Item
1661
-     * @throws \EE_Error
1662
-     */
1663
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
1664
-    {
1665
-        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');
1666
-        return self::get_pre_tax_subtotal($total_line_item);
1667
-    }
1668
-
1669
-
1670
-    /**
1671
-     * @deprecated
1672
-     * @param EE_Transaction $transaction
1673
-     * @return \EE_Line_Item
1674
-     * @throws \EE_Error
1675
-     */
1676
-    public static function create_default_total_line_item($transaction = NULL)
1677
-    {
1678
-        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');
1679
-        return self::create_total_line_item($transaction);
1680
-    }
1681
-
1682
-
1683
-    /**
1684
-     * @deprecated
1685
-     * @param EE_Line_Item $total_line_item
1686
-     * @param EE_Transaction $transaction
1687
-     * @return \EE_Line_Item
1688
-     * @throws \EE_Error
1689
-     */
1690
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1691
-    {
1692
-        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');
1693
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
1694
-    }
1695
-
1696
-
1697
-    /**
1698
-     * @deprecated
1699
-     * @param EE_Line_Item $total_line_item
1700
-     * @param EE_Transaction $transaction
1701
-     * @return \EE_Line_Item
1702
-     * @throws \EE_Error
1703
-     */
1704
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1705
-    {
1706
-        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');
1707
-        return self::create_taxes_subtotal($total_line_item, $transaction);
1708
-    }
1709
-
1710
-
1711
-    /**
1712
-     * @deprecated
1713
-     * @param EE_Line_Item $total_line_item
1714
-     * @param EE_Transaction $transaction
1715
-     * @return \EE_Line_Item
1716
-     * @throws \EE_Error
1717
-     */
1718
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1719
-    {
1720
-        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');
1721
-        return self::create_event_subtotal($total_line_item, $transaction);
1722
-    }
26
+	//other functions: cancel ticket purchase
27
+	//delete ticket purchase
28
+	//add promotion
29
+
30
+
31
+	/**
32
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
33
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
34
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
35
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
36
+	 * 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
+	{
50
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
51
+		$line_item = EE_Line_Item::new_instance(array(
52
+			'LIN_name' => $name,
53
+			'LIN_desc' => $description,
54
+			'LIN_unit_price' => $unit_price,
55
+			'LIN_quantity' => $quantity,
56
+			'LIN_percent' => null,
57
+			'LIN_is_taxable' => $taxable,
58
+			'LIN_order' => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
59
+			'LIN_total' => (float)$unit_price * (int)$quantity,
60
+			'LIN_type' => EEM_Line_Item::type_line_item,
61
+			'LIN_code' => $code,
62
+		));
63
+		$line_item = apply_filters(
64
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
65
+			$line_item,
66
+			$parent_line_item
67
+		);
68
+		return self::add_item($parent_line_item, $line_item);
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
+	{
91
+		$line_item = EE_Line_Item::new_instance(array(
92
+			'LIN_name' => $name,
93
+			'LIN_desc' => $description,
94
+			'LIN_unit_price' => 0,
95
+			'LIN_percent' => $percentage_amount,
96
+			'LIN_quantity' => 1,
97
+			'LIN_is_taxable' => $taxable,
98
+			'LIN_total' => (float)($percentage_amount * ($parent_line_item->total() / 100)),
99
+			'LIN_type' => EEM_Line_Item::type_line_item,
100
+			'LIN_parent' => $parent_line_item->ID()
101
+		));
102
+		$line_item = apply_filters(
103
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
104
+			$line_item
105
+		);
106
+		return $parent_line_item->add_child_line_item($line_item, false);
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
+	{
128
+		if (!$total_line_item instanceof EE_Line_Item || !$total_line_item->is_total()) {
129
+			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()));
130
+		}
131
+		// either increment the qty for an existing ticket
132
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
133
+		// or add a new one
134
+		if (!$line_item instanceof EE_Line_Item) {
135
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
136
+		}
137
+		$total_line_item->recalculate_total_including_taxes();
138
+		return $line_item;
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
+	{
152
+		$line_item = null;
153
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
154
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
155
+			foreach ((array)$ticket_line_items as $ticket_line_item) {
156
+				if (
157
+					$ticket_line_item instanceof EE_Line_Item
158
+					&& (int)$ticket_line_item->OBJ_ID() === (int)$ticket->ID()
159
+				) {
160
+					$line_item = $ticket_line_item;
161
+					break;
162
+				}
163
+			}
164
+		}
165
+		if ($line_item instanceof EE_Line_Item) {
166
+			EEH_Line_Item::increment_quantity($line_item, $qty);
167
+			return $line_item;
168
+		}
169
+		return null;
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
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
183
+	{
184
+		if (!$line_item->is_percent()) {
185
+			$qty += $line_item->quantity();
186
+			$line_item->set_quantity($qty);
187
+			$line_item->set_total($line_item->unit_price() * $qty);
188
+			$line_item->save();
189
+		}
190
+		foreach ($line_item->children() as $child) {
191
+			if ($child->is_sub_line_item()) {
192
+				EEH_Line_Item::update_quantity($child, $qty);
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
+	{
209
+		if (!$line_item->is_percent()) {
210
+			$qty = $line_item->quantity() - $qty;
211
+			$qty = max($qty, 0);
212
+			$line_item->set_quantity($qty);
213
+			$line_item->set_total($line_item->unit_price() * $qty);
214
+			$line_item->save();
215
+		}
216
+		foreach ($line_item->children() as $child) {
217
+			if ($child->is_sub_line_item()) {
218
+				EEH_Line_Item::update_quantity($child, $qty);
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
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
233
+	{
234
+		if (!$line_item->is_percent()) {
235
+			$line_item->set_quantity($new_quantity);
236
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
237
+			$line_item->save();
238
+		}
239
+		foreach ($line_item->children() as $child) {
240
+			if ($child->is_sub_line_item()) {
241
+				EEH_Line_Item::update_quantity($child, $new_quantity);
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
+	{
257
+		$datetimes = $ticket->datetimes();
258
+		$first_datetime = reset($datetimes);
259
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
260
+			$first_datetime_name = $first_datetime->event()->name();
261
+		} else {
262
+			$first_datetime_name = __('Event', 'event_espresso');
263
+		}
264
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
265
+		// get event subtotal line
266
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
267
+		// add $ticket to cart
268
+		$line_item = EE_Line_Item::new_instance(array(
269
+			'LIN_name' => $ticket->name(),
270
+			'LIN_desc' => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
271
+			'LIN_unit_price' => $ticket->price(),
272
+			'LIN_quantity' => $qty,
273
+			'LIN_is_taxable' => $ticket->taxable(),
274
+			'LIN_order' => count($events_sub_total->children()),
275
+			'LIN_total' => $ticket->price() * $qty,
276
+			'LIN_type' => EEM_Line_Item::type_line_item,
277
+			'OBJ_ID' => $ticket->ID(),
278
+			'OBJ_type' => 'Ticket'
279
+		));
280
+		$line_item = apply_filters(
281
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
282
+			$line_item
283
+		);
284
+		$events_sub_total->add_child_line_item($line_item);
285
+		//now add the sub-line items
286
+		$running_total_for_ticket = 0;
287
+		foreach ($ticket->prices(array('order_by' => array('PRC_order' => 'ASC'))) as $price) {
288
+			$sign = $price->is_discount() ? -1 : 1;
289
+			$price_total = $price->is_percent()
290
+				? $running_total_for_ticket * $price->amount() / 100
291
+				: $price->amount() * $qty;
292
+			$sub_line_item = EE_Line_Item::new_instance(array(
293
+				'LIN_name' => $price->name(),
294
+				'LIN_desc' => $price->desc(),
295
+				'LIN_quantity' => $price->is_percent() ? null : $qty,
296
+				'LIN_is_taxable' => false,
297
+				'LIN_order' => $price->order(),
298
+				'LIN_total' => $sign * $price_total,
299
+				'LIN_type' => EEM_Line_Item::type_sub_line_item,
300
+				'OBJ_ID' => $price->ID(),
301
+				'OBJ_type' => 'Price'
302
+			));
303
+			$sub_line_item = apply_filters(
304
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
305
+				$sub_line_item
306
+			);
307
+			if ($price->is_percent()) {
308
+				$sub_line_item->set_percent($sign * $price->amount());
309
+			} else {
310
+				$sub_line_item->set_unit_price($sign * $price->amount());
311
+			}
312
+			$running_total_for_ticket += $price_total;
313
+			$line_item->add_child_line_item($sub_line_item);
314
+		}
315
+		return $line_item;
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
+	{
334
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
335
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
336
+			$success = $pre_tax_subtotal->add_child_line_item($item);
337
+		} else {
338
+			return FALSE;
339
+		}
340
+		$total_line_item->recalculate_total_including_taxes();
341
+		return $success;
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
+	{
357
+		// validate incoming line_item
358
+		if ($ticket_line_item->OBJ_type() !== 'Ticket') {
359
+			throw new EE_Error(
360
+				sprintf(
361
+					__('The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso'),
362
+					$ticket_line_item->type()
363
+				)
364
+			);
365
+		}
366
+		if ($ticket_line_item->quantity() < $qty) {
367
+			throw new EE_Error(
368
+				sprintf(
369
+					__('Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.', 'event_espresso'),
370
+					$qty,
371
+					$ticket_line_item->quantity()
372
+				)
373
+			);
374
+		}
375
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
376
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
377
+		foreach ($ticket_line_item->children() as $child_line_item) {
378
+			if (
379
+				$child_line_item->is_sub_line_item()
380
+				&& !$child_line_item->is_percent()
381
+				&& !$child_line_item->is_cancellation()
382
+			) {
383
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
384
+			}
385
+		}
386
+		// get cancellation sub line item
387
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
388
+			$ticket_line_item,
389
+			EEM_Line_Item::type_cancellation
390
+		);
391
+		$cancellation_line_item = reset($cancellation_line_item);
392
+		// verify that this ticket was indeed previously cancelled
393
+		if ($cancellation_line_item instanceof EE_Line_Item) {
394
+			// increment cancelled quantity
395
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
396
+		} else {
397
+			// create cancellation sub line item
398
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
399
+				'LIN_name' => __('Cancellation', 'event_espresso'),
400
+				'LIN_desc' => sprintf(
401
+					_x('Cancelled %1$s : %2$s', 'Cancelled Ticket Name : 2015-01-01 11:11', 'event_espresso'),
402
+					$ticket_line_item->name(),
403
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
404
+				),
405
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
406
+				'LIN_quantity' => $qty,
407
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
408
+				'LIN_order' => count($ticket_line_item->children()),
409
+				'LIN_total' => 0, // $ticket_line_item->unit_price()
410
+				'LIN_type' => EEM_Line_Item::type_cancellation,
411
+			));
412
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
413
+		}
414
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
415
+			// decrement parent line item quantity
416
+			$event_line_item = $ticket_line_item->parent();
417
+			if ($event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event') {
418
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
419
+				$event_line_item->save();
420
+			}
421
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
422
+			return true;
423
+		}
424
+		return false;
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
+	{
440
+		// validate incoming line_item
441
+		if ($ticket_line_item->OBJ_type() !== 'Ticket') {
442
+			throw new EE_Error(
443
+				sprintf(
444
+					__('The supplied line item must have an Object Type of "Ticket", not %1$s.', 'event_espresso'),
445
+					$ticket_line_item->type()
446
+				)
447
+			);
448
+		}
449
+		// get cancellation sub line item
450
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
451
+			$ticket_line_item,
452
+			EEM_Line_Item::type_cancellation
453
+		);
454
+		$cancellation_line_item = reset($cancellation_line_item);
455
+		// verify that this ticket was indeed previously cancelled
456
+		if (!$cancellation_line_item instanceof EE_Line_Item) {
457
+			return false;
458
+		}
459
+		if ($cancellation_line_item->quantity() > $qty) {
460
+			// decrement cancelled quantity
461
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
462
+		} else if ($cancellation_line_item->quantity() == $qty) {
463
+			// decrement cancelled quantity in case anyone still has the object kicking around
464
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
465
+			// delete because quantity will end up as 0
466
+			$cancellation_line_item->delete();
467
+			// and attempt to destroy the object,
468
+			// even though PHP won't actually destroy it until it needs the memory
469
+			unset($cancellation_line_item);
470
+		} else {
471
+			// what ?!?! negative quantity ?!?!
472
+			throw new EE_Error(
473
+				sprintf(
474
+					__('Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
475
+						'event_espresso'),
476
+					$qty,
477
+					$cancellation_line_item->quantity()
478
+				)
479
+			);
480
+		}
481
+		// increment ticket quantity
482
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
483
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
484
+			// increment parent line item quantity
485
+			$event_line_item = $ticket_line_item->parent();
486
+			if ($event_line_item instanceof EE_Line_Item && $event_line_item->OBJ_type() === 'Event') {
487
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
488
+			}
489
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
490
+			return true;
491
+		}
492
+		return false;
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
+	{
505
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
506
+		return $grand_total_line_item->recalculate_total_including_taxes();
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
+	{
519
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
520
+		return $pre_tax_subtotal instanceof EE_Line_Item
521
+			? $pre_tax_subtotal
522
+			: self::create_pre_tax_subtotal($total_line_item);
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
+	{
535
+		$taxes = $total_line_item->get_child_line_item('taxes');
536
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
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
+	{
550
+		if ($transaction) {
551
+			/** @type EEM_Transaction $EEM_Transaction */
552
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
553
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
554
+			$line_item->set_TXN_ID($TXN_ID);
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
+	public static function create_total_line_item($transaction = NULL)
569
+	{
570
+		$total_line_item = EE_Line_Item::new_instance(array(
571
+			'LIN_code' => 'total',
572
+			'LIN_name' => __('Grand Total', 'event_espresso'),
573
+			'LIN_type' => EEM_Line_Item::type_total,
574
+			'OBJ_type' => 'Transaction'
575
+		));
576
+		$total_line_item = apply_filters(
577
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
578
+			$total_line_item
579
+		);
580
+		self::set_TXN_ID($total_line_item, $transaction);
581
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
582
+		self::create_taxes_subtotal($total_line_item, $transaction);
583
+		return $total_line_item;
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
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
596
+	{
597
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
598
+			'LIN_code' => 'pre-tax-subtotal',
599
+			'LIN_name' => __('Pre-Tax Subtotal', 'event_espresso'),
600
+			'LIN_type' => EEM_Line_Item::type_sub_total
601
+		));
602
+		$pre_tax_line_item = apply_filters(
603
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
604
+			$pre_tax_line_item
605
+		);
606
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
607
+		$total_line_item->add_child_line_item($pre_tax_line_item);
608
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
609
+		return $pre_tax_line_item;
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
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
623
+	{
624
+		$tax_line_item = EE_Line_Item::new_instance(array(
625
+			'LIN_code' => 'taxes',
626
+			'LIN_name' => __('Taxes', 'event_espresso'),
627
+			'LIN_type' => EEM_Line_Item::type_tax_sub_total,
628
+			'LIN_order' => 1000,//this should always come last
629
+		));
630
+		$tax_line_item = apply_filters(
631
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
632
+			$tax_line_item
633
+		);
634
+		self::set_TXN_ID($tax_line_item, $transaction);
635
+		$total_line_item->add_child_line_item($tax_line_item);
636
+		//and lastly, add the actual taxes
637
+		self::apply_taxes($total_line_item);
638
+		return $tax_line_item;
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
+	{
653
+		$event_line_item = EE_Line_Item::new_instance(array(
654
+			'LIN_code' => self::get_event_code($event),
655
+			'LIN_name' => self::get_event_name($event),
656
+			'LIN_desc' => self::get_event_desc($event),
657
+			'LIN_type' => EEM_Line_Item::type_sub_total,
658
+			'OBJ_type' => 'Event',
659
+			'OBJ_ID' => $event instanceof EE_Event ? $event->ID() : 0
660
+		));
661
+		$event_line_item = apply_filters(
662
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
663
+			$event_line_item
664
+		);
665
+		self::set_TXN_ID($event_line_item, $transaction);
666
+		$pre_tax_line_item->add_child_line_item($event_line_item);
667
+		return $event_line_item;
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
+	{
680
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
681
+	}
682
+
683
+	/**
684
+	 * Gets the event name
685
+	 * @param EE_Event $event
686
+	 * @return string
687
+	 */
688
+	public static function get_event_name($event)
689
+	{
690
+		return $event instanceof EE_Event ? $event->name() : __('Event', 'event_espresso');
691
+	}
692
+
693
+	/**
694
+	 * Gets the event excerpt
695
+	 * @param EE_Event $event
696
+	 * @return string
697
+	 */
698
+	public static function get_event_desc($event)
699
+	{
700
+		return $event instanceof EE_Event ? $event->short_description() : '';
701
+	}
702
+
703
+	/**
704
+	 * Given the grand total line item and a ticket, finds the event sub-total
705
+	 * line item the ticket's purchase should be added onto
706
+	 *
707
+	 * @access public
708
+	 * @param EE_Line_Item $grand_total the grand total line item
709
+	 * @param EE_Ticket $ticket
710
+	 * @throws \EE_Error
711
+	 * @return EE_Line_Item
712
+	 */
713
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
714
+	{
715
+		$first_datetime = $ticket->first_datetime();
716
+		if (!$first_datetime instanceof EE_Datetime) {
717
+			throw new EE_Error(
718
+				sprintf(__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'), $ticket->ID())
719
+			);
720
+		}
721
+		$event = $first_datetime->event();
722
+		if (!$event instanceof EE_Event) {
723
+			throw new EE_Error(
724
+				sprintf(
725
+					__('The supplied ticket (ID %d) has no event data associated with it.', 'event_espresso'),
726
+					$ticket->ID()
727
+				)
728
+			);
729
+		}
730
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
731
+		if (!$events_sub_total instanceof EE_Line_Item) {
732
+			throw new EE_Error(
733
+				sprintf(
734
+					__('There is no events sub-total for ticket %s on total line item %d', 'event_espresso'),
735
+					$ticket->ID(),
736
+					$grand_total->ID()
737
+				)
738
+			);
739
+		}
740
+		return $events_sub_total;
741
+	}
742
+
743
+
744
+	/**
745
+	 * Gets the event line item
746
+	 *
747
+	 * @param EE_Line_Item $grand_total
748
+	 * @param EE_Event $event
749
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
750
+	 * @throws \EE_Error
751
+	 */
752
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
753
+	{
754
+		/** @type EE_Event $event */
755
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
756
+		$event_line_item = NULL;
757
+		$found = false;
758
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
759
+			// default event subtotal, we should only ever find this the first time this method is called
760
+			if (!$event_line_item->OBJ_ID()) {
761
+				// let's use this! but first... set the event details
762
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
763
+				$found = true;
764
+				break;
765
+			} else if ($event_line_item->OBJ_ID() === $event->ID()) {
766
+				// found existing line item for this event in the cart, so break out of loop and use this one
767
+				$found = true;
768
+				break;
769
+			}
770
+		}
771
+		if (!$found) {
772
+			//there is no event sub-total yet, so add it
773
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
774
+			// create a new "event" subtotal below that
775
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
776
+			// and set the event details
777
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
778
+		}
779
+		return $event_line_item;
780
+	}
781
+
782
+
783
+	/**
784
+	 * Creates a default items subtotal line item
785
+	 *
786
+	 * @param EE_Line_Item $event_line_item
787
+	 * @param EE_Event $event
788
+	 * @param EE_Transaction $transaction
789
+	 * @return EE_Line_Item
790
+	 * @throws \EE_Error
791
+	 */
792
+	public static function set_event_subtotal_details(
793
+		EE_Line_Item $event_line_item,
794
+		EE_Event $event,
795
+		$transaction = null
796
+	)
797
+	{
798
+		if ($event instanceof EE_Event) {
799
+			$event_line_item->set_code(self::get_event_code($event));
800
+			$event_line_item->set_name(self::get_event_name($event));
801
+			$event_line_item->set_desc(self::get_event_desc($event));
802
+			$event_line_item->set_OBJ_ID($event->ID());
803
+		}
804
+		self::set_TXN_ID($event_line_item, $transaction);
805
+	}
806
+
807
+
808
+	/**
809
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
810
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
811
+	 * any old taxes are removed
812
+	 *
813
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
814
+	 * @throws \EE_Error
815
+	 */
816
+	public static function apply_taxes(EE_Line_Item $total_line_item)
817
+	{
818
+		/** @type EEM_Price $EEM_Price */
819
+		$EEM_Price = EE_Registry::instance()->load_model('Price');
820
+		// get array of taxes via Price Model
821
+		$ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
822
+		ksort($ordered_taxes);
823
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
824
+		//just to be safe, remove its old tax line items
825
+		$taxes_line_item->delete_children_line_items();
826
+		//loop thru taxes
827
+		foreach ($ordered_taxes as $order => $taxes) {
828
+			foreach ($taxes as $tax) {
829
+				if ($tax instanceof EE_Price) {
830
+					$tax_line_item = EE_Line_Item::new_instance(
831
+						array(
832
+							'LIN_name' => $tax->name(),
833
+							'LIN_desc' => $tax->desc(),
834
+							'LIN_percent' => $tax->amount(),
835
+							'LIN_is_taxable' => false,
836
+							'LIN_order' => $order,
837
+							'LIN_total' => 0,
838
+							'LIN_type' => EEM_Line_Item::type_tax,
839
+							'OBJ_type' => 'Price',
840
+							'OBJ_ID' => $tax->ID()
841
+						)
842
+					);
843
+					$tax_line_item = apply_filters(
844
+						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
845
+						$tax_line_item
846
+					);
847
+					$taxes_line_item->add_child_line_item($tax_line_item);
848
+				}
849
+			}
850
+		}
851
+		$total_line_item->recalculate_total_including_taxes();
852
+	}
853
+
854
+
855
+	/**
856
+	 * Ensures that taxes have been applied to the order, if not applies them.
857
+	 * Returns the total amount of tax
858
+	 *
859
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
860
+	 * @return float
861
+	 * @throws \EE_Error
862
+	 */
863
+	public static function ensure_taxes_applied($total_line_item)
864
+	{
865
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
866
+		if (!$taxes_subtotal->children()) {
867
+			self::apply_taxes($total_line_item);
868
+		}
869
+		return $taxes_subtotal->total();
870
+	}
871
+
872
+
873
+	/**
874
+	 * Deletes ALL children of the passed line item
875
+	 *
876
+	 * @param EE_Line_Item $parent_line_item
877
+	 * @return bool
878
+	 * @throws \EE_Error
879
+	 */
880
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
881
+	{
882
+		$deleted = 0;
883
+		foreach ($parent_line_item->children() as $child_line_item) {
884
+			if ($child_line_item instanceof EE_Line_Item) {
885
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
886
+				if ($child_line_item->ID()) {
887
+					$child_line_item->delete();
888
+					unset($child_line_item);
889
+				} else {
890
+					$parent_line_item->delete_child_line_item($child_line_item->code());
891
+				}
892
+				$deleted++;
893
+			}
894
+		}
895
+		return $deleted;
896
+	}
897
+
898
+
899
+	/**
900
+	 * Deletes the line items as indicated by the line item code(s) provided,
901
+	 * regardless of where they're found in the line item tree. Automatically
902
+	 * re-calculates the line item totals and updates the related transaction. But
903
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
904
+	 * should probably change because of this).
905
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
906
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
907
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
908
+	 * @param array|bool|string $line_item_codes
909
+	 * @return int number of items successfully removed
910
+	 */
911
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = FALSE)
912
+	{
913
+
914
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
915
+			EE_Error::doing_it_wrong(
916
+				'EEH_Line_Item::delete_items',
917
+				__(
918
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
919
+					'event_espresso'
920
+				),
921
+				'4.6.18'
922
+			);
923
+		}
924
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
925
+
926
+		// check if only a single line_item_id was passed
927
+		if (!empty($line_item_codes) && !is_array($line_item_codes)) {
928
+			// place single line_item_id in an array to appear as multiple line_item_ids
929
+			$line_item_codes = array($line_item_codes);
930
+		}
931
+		$removals = 0;
932
+		// cycle thru line_item_ids
933
+		foreach ($line_item_codes as $line_item_id) {
934
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
935
+		}
936
+
937
+		if ($removals > 0) {
938
+			$total_line_item->recalculate_taxes_and_tax_total();
939
+			return $removals;
940
+		} else {
941
+			return FALSE;
942
+		}
943
+	}
944
+
945
+
946
+	/**
947
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
948
+	 * tax and updates the total line item accordingly
949
+	 *
950
+	 * @param EE_Line_Item $total_line_item
951
+	 * @param float $amount
952
+	 * @param string $name
953
+	 * @param string $description
954
+	 * @param string $code
955
+	 * @param boolean $add_to_existing_line_item
956
+	 *                          if true, and a duplicate line item with the same code is found,
957
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
958
+	 * @return EE_Line_Item the new tax line item created
959
+	 * @throws \EE_Error
960
+	 */
961
+	public static function set_total_tax_to(
962
+		EE_Line_Item $total_line_item,
963
+		$amount,
964
+		$name = null,
965
+		$description = null,
966
+		$code = null,
967
+		$add_to_existing_line_item = false
968
+	)
969
+	{
970
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
971
+		$taxable_total = $total_line_item->taxable_total();
972
+
973
+		if ($add_to_existing_line_item) {
974
+			$new_tax = $tax_subtotal->get_child_line_item($code);
975
+			EEM_Line_Item::instance()->delete(
976
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
977
+			);
978
+		} else {
979
+			$new_tax = null;
980
+			$tax_subtotal->delete_children_line_items();
981
+		}
982
+		if ($new_tax) {
983
+			$new_tax->set_total($new_tax->total() + $amount);
984
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
985
+		} else {
986
+			//no existing tax item. Create it
987
+			$new_tax = EE_Line_Item::new_instance(array(
988
+				'TXN_ID' => $total_line_item->TXN_ID(),
989
+				'LIN_name' => $name ? $name : __('Tax', 'event_espresso'),
990
+				'LIN_desc' => $description ? $description : '',
991
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
992
+				'LIN_total' => $amount,
993
+				'LIN_parent' => $tax_subtotal->ID(),
994
+				'LIN_type' => EEM_Line_Item::type_tax,
995
+				'LIN_code' => $code
996
+			));
997
+		}
998
+
999
+		$new_tax = apply_filters(
1000
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1001
+			$new_tax,
1002
+			$total_line_item
1003
+		);
1004
+		$new_tax->save();
1005
+		$tax_subtotal->set_total($new_tax->total());
1006
+		$tax_subtotal->save();
1007
+		$total_line_item->recalculate_total_including_taxes();
1008
+		return $new_tax;
1009
+	}
1010
+
1011
+
1012
+	/**
1013
+	 * Makes all the line items which are children of $line_item taxable (or not).
1014
+	 * Does NOT save the line items
1015
+	 * @param EE_Line_Item $line_item
1016
+	 * @param string $code_substring_for_whitelist if this string is part of the line item's code
1017
+	 *  it will be whitelisted (ie, except from becoming taxable)
1018
+	 * @param boolean $taxable
1019
+	 */
1020
+	public static function set_line_items_taxable(
1021
+		EE_Line_Item $line_item,
1022
+		$taxable = true,
1023
+		$code_substring_for_whitelist = null
1024
+	)
1025
+	{
1026
+		$whitelisted = false;
1027
+		if ($code_substring_for_whitelist !== null) {
1028
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false ? true : false;
1029
+		}
1030
+		if (!$whitelisted && $line_item->is_line_item()) {
1031
+			$line_item->set_is_taxable($taxable);
1032
+		}
1033
+		foreach ($line_item->children() as $child_line_item) {
1034
+			EEH_Line_Item::set_line_items_taxable($child_line_item, $taxable, $code_substring_for_whitelist);
1035
+		}
1036
+	}
1037
+
1038
+
1039
+	/**
1040
+	 * Gets all descendants that are event subtotals
1041
+	 *
1042
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1043
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1044
+	 * @return EE_Line_Item[]
1045
+	 */
1046
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1047
+	{
1048
+		return self::get_subtotals_of_object_type($parent_line_item, 'Event');
1049
+	}
1050
+
1051
+
1052
+	/**
1053
+	 * Gets all descendants subtotals that match the supplied object type
1054
+	 *
1055
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1056
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1057
+	 * @param string $obj_type
1058
+	 * @return EE_Line_Item[]
1059
+	 */
1060
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1061
+	{
1062
+		return self::_get_descendants_by_type_and_object_type(
1063
+			$parent_line_item,
1064
+			EEM_Line_Item::type_sub_total,
1065
+			$obj_type
1066
+		);
1067
+	}
1068
+
1069
+
1070
+	/**
1071
+	 * Gets all descendants that are tickets
1072
+	 *
1073
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1074
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1075
+	 * @return EE_Line_Item[]
1076
+	 */
1077
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1078
+	{
1079
+		return self::get_line_items_of_object_type($parent_line_item, 'Ticket');
1080
+	}
1081
+
1082
+
1083
+	/**
1084
+	 * Gets all descendants subtotals that match the supplied object type
1085
+	 *
1086
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1087
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1088
+	 * @param string $obj_type
1089
+	 * @return EE_Line_Item[]
1090
+	 */
1091
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1092
+	{
1093
+		return self::_get_descendants_by_type_and_object_type($parent_line_item, EEM_Line_Item::type_line_item, $obj_type);
1094
+	}
1095
+
1096
+
1097
+	/**
1098
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1099
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1100
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1101
+	 * @return EE_Line_Item[]
1102
+	 */
1103
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1104
+	{
1105
+		return EEH_Line_Item::get_descendants_of_type($parent_line_item, EEM_Line_Item::type_tax);
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * Gets all the real items purchased which are children of this item
1111
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1112
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1113
+	 * @return EE_Line_Item[]
1114
+	 */
1115
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1116
+	{
1117
+		return EEH_Line_Item::get_descendants_of_type($parent_line_item, EEM_Line_Item::type_line_item);
1118
+	}
1119
+
1120
+
1121
+	/**
1122
+	 * Gets all descendants of supplied line item that match the supplied line item type
1123
+	 *
1124
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1125
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1126
+	 * @param string $line_item_type one of the EEM_Line_Item constants
1127
+	 * @return EE_Line_Item[]
1128
+	 */
1129
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1130
+	{
1131
+		return self::_get_descendants_by_type_and_object_type($parent_line_item, $line_item_type, NULL);
1132
+	}
1133
+
1134
+
1135
+	/**
1136
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1137
+	 *
1138
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1139
+	 * @param string $line_item_type one of the EEM_Line_Item constants
1140
+	 * @param string | NULL $obj_type object model class name (minus prefix) or NULL to ignore object type when searching
1141
+	 * @return EE_Line_Item[]
1142
+	 */
1143
+	protected static function _get_descendants_by_type_and_object_type(
1144
+		EE_Line_Item $parent_line_item,
1145
+		$line_item_type,
1146
+		$obj_type = null
1147
+	)
1148
+	{
1149
+		$objects = array();
1150
+		foreach ($parent_line_item->children() as $child_line_item) {
1151
+			if ($child_line_item instanceof EE_Line_Item) {
1152
+				if (
1153
+					$child_line_item->type() === $line_item_type
1154
+					&& (
1155
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1156
+					)
1157
+				) {
1158
+					$objects[] = $child_line_item;
1159
+				} else {
1160
+					//go-through-all-its children looking for more matches
1161
+					$objects = array_merge(
1162
+						$objects,
1163
+						self::_get_descendants_by_type_and_object_type(
1164
+							$child_line_item,
1165
+							$line_item_type,
1166
+							$obj_type
1167
+						)
1168
+					);
1169
+				}
1170
+			}
1171
+		}
1172
+		return $objects;
1173
+	}
1174
+
1175
+
1176
+	/**
1177
+	 * Gets all descendants subtotals that match the supplied object type
1178
+	 *
1179
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1180
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1181
+	 * @param string $OBJ_type object type (like Event)
1182
+	 * @param array $OBJ_IDs array of OBJ_IDs
1183
+	 * @return EE_Line_Item[]
1184
+	 */
1185
+	public static function get_line_items_by_object_type_and_IDs(
1186
+		EE_Line_Item $parent_line_item,
1187
+		$OBJ_type = '',
1188
+		$OBJ_IDs = array()
1189
+	)
1190
+	{
1191
+		return self::_get_descendants_by_object_type_and_object_ID($parent_line_item, $OBJ_type, $OBJ_IDs);
1192
+	}
1193
+
1194
+
1195
+	/**
1196
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type as well
1197
+	 *
1198
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1199
+	 * @param string $OBJ_type object type (like Event)
1200
+	 * @param array $OBJ_IDs array of OBJ_IDs
1201
+	 * @return EE_Line_Item[]
1202
+	 */
1203
+	protected static function _get_descendants_by_object_type_and_object_ID(
1204
+		EE_Line_Item $parent_line_item,
1205
+		$OBJ_type,
1206
+		$OBJ_IDs
1207
+	)
1208
+	{
1209
+		$objects = array();
1210
+		foreach ($parent_line_item->children() as $child_line_item) {
1211
+			if ($child_line_item instanceof EE_Line_Item) {
1212
+				if (
1213
+					$child_line_item->OBJ_type() === $OBJ_type
1214
+					&& is_array($OBJ_IDs)
1215
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1216
+				) {
1217
+					$objects[] = $child_line_item;
1218
+				} else {
1219
+					//go-through-all-its children looking for more matches
1220
+					$objects = array_merge(
1221
+						$objects,
1222
+						self::_get_descendants_by_object_type_and_object_ID(
1223
+							$child_line_item,
1224
+							$OBJ_type,
1225
+							$OBJ_IDs
1226
+						)
1227
+					);
1228
+				}
1229
+			}
1230
+		}
1231
+		return $objects;
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1237
+	 * the specified type and returns it, else NULL
1238
+	 *
1239
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1240
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1241
+	 * @param string $type like one of the EEM_Line_Item::type_*
1242
+	 * @return EE_Line_Item
1243
+	 */
1244
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1245
+	{
1246
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1247
+	}
1248
+
1249
+
1250
+	/**
1251
+	 * Uses a breadth-first-search in order to find the nearest descendant
1252
+	 * having the specified LIN_code and returns it, else NULL
1253
+	 *
1254
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1255
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1256
+	 * @param string $code any value used for LIN_code
1257
+	 * @return EE_Line_Item
1258
+	 */
1259
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1260
+	{
1261
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1262
+	}
1263
+
1264
+
1265
+	/**
1266
+	 * Uses a breadth-first-search in order to find the nearest descendant
1267
+	 * having the specified LIN_code and returns it, else NULL
1268
+	 *
1269
+	 * @param \EE_Line_Item $parent_line_item - the line item to find descendants of
1270
+	 * @param string $search_field name of EE_Line_Item property
1271
+	 * @param string $value any value stored in $search_field
1272
+	 * @return EE_Line_Item
1273
+	 */
1274
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1275
+	{
1276
+		foreach ($parent_line_item->children() as $child) {
1277
+			if ($child->get($search_field) == $value) {
1278
+				return $child;
1279
+			}
1280
+		}
1281
+		foreach ($parent_line_item->children() as $child) {
1282
+			$descendant_found = self::_get_nearest_descendant($child, $search_field, $value);
1283
+			if ($descendant_found) {
1284
+				return $descendant_found;
1285
+			}
1286
+		}
1287
+		return NULL;
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1293
+	 * else recursively walks up the line item tree until a parent of type total is found,
1294
+	 *
1295
+	 * @param EE_Line_Item $line_item
1296
+	 * @return \EE_Line_Item
1297
+	 * @throws \EE_Error
1298
+	 */
1299
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1300
+	{
1301
+		if ($line_item->TXN_ID()) {
1302
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1303
+			if ($total_line_item instanceof EE_Line_Item) {
1304
+				return $total_line_item;
1305
+			}
1306
+		} else {
1307
+			$line_item_parent = $line_item->parent();
1308
+			if ($line_item_parent instanceof EE_Line_Item) {
1309
+				if ($line_item_parent->is_total()) {
1310
+					return $line_item_parent;
1311
+				}
1312
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1313
+			}
1314
+		}
1315
+		throw new EE_Error(
1316
+			sprintf(
1317
+				__('A valid grand total for line item %1$d was not found.', 'event_espresso'),
1318
+				$line_item->ID()
1319
+			)
1320
+		);
1321
+	}
1322
+
1323
+
1324
+	/**
1325
+	 * Prints out a representation of the line item tree
1326
+	 *
1327
+	 * @param EE_Line_Item $line_item
1328
+	 * @param int $indentation
1329
+	 * @return void
1330
+	 * @throws \EE_Error
1331
+	 */
1332
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1333
+	{
1334
+		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1335
+		if (!$indentation) {
1336
+			echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1337
+		}
1338
+		for ($i = 0; $i < $indentation; $i++) {
1339
+			echo ". ";
1340
+		}
1341
+		$breakdown = '';
1342
+		if ($line_item->is_line_item()) {
1343
+			if ($line_item->is_percent()) {
1344
+				$breakdown = "{$line_item->percent()}%";
1345
+			} else {
1346
+				$breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1347
+			}
1348
+		}
1349
+		echo $line_item->name() . " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : " . '$' . "{$line_item->total()}";
1350
+		if ($breakdown) {
1351
+			echo " ( {$breakdown} )";
1352
+		}
1353
+		if ($line_item->is_taxable()) {
1354
+			echo "  * taxable";
1355
+		}
1356
+		if ($line_item->children()) {
1357
+			foreach ($line_item->children() as $child) {
1358
+				self::visualize($child, $indentation + 1);
1359
+			}
1360
+		}
1361
+	}
1362
+
1363
+
1364
+	/**
1365
+	 * Calculates the registration's final price, taking into account that they
1366
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1367
+	 * and receive a portion of any transaction-wide discounts.
1368
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1369
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1370
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1371
+	 * and brent's final price should be $5.50.
1372
+	 *
1373
+	 * In order to do this, we basically need to traverse the line item tree calculating
1374
+	 * the running totals (just as if we were recalculating the total), but when we identify
1375
+	 * regular line items, we need to keep track of their share of the grand total.
1376
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1377
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1378
+	 * when there are non-taxable items; otherwise they would be the same)
1379
+	 *
1380
+	 * @param EE_Line_Item $line_item
1381
+	 * @param array $billable_ticket_quantities array of EE_Ticket IDs and their corresponding quantity that
1382
+	 *                                                                            can be included in price calculations at this moment
1383
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1384
+	 *                                          plus the key 'total', and 'taxable' which also has keys of all the ticket IDs. Eg
1385
+	 *                                          array(
1386
+	 *                                          12 => 4.3
1387
+	 *                                          23 => 8.0
1388
+	 *                                          'total' => 16.6,
1389
+	 *                                          'taxable' => array(
1390
+	 *                                          12 => 10,
1391
+	 *                                          23 => 4
1392
+	 *                                          ).
1393
+	 *                                          So to find which registrations have which final price, we need to find which line item
1394
+	 *                                          is theirs, which can be done with
1395
+	 *                                          `EEM_Line_Item::instance()->get_line_item_for_registration( $registration );`
1396
+	 */
1397
+	public static function calculate_reg_final_prices_per_line_item(EE_Line_Item $line_item, $billable_ticket_quantities = array())
1398
+	{
1399
+		//init running grand total if not already
1400
+		if (!isset($running_totals['total'])) {
1401
+			$running_totals['total'] = 0;
1402
+		}
1403
+		if (!isset($running_totals['taxable'])) {
1404
+			$running_totals['taxable'] = array('total' => 0);
1405
+		}
1406
+		foreach ($line_item->children() as $child_line_item) {
1407
+			switch ($child_line_item->type()) {
1408
+
1409
+				case EEM_Line_Item::type_sub_total :
1410
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item($child_line_item, $billable_ticket_quantities);
1411
+					//combine arrays but preserve numeric keys
1412
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1413
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1414
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1415
+					break;
1416
+
1417
+				case EEM_Line_Item::type_tax_sub_total :
1418
+
1419
+					//find how much the taxes percentage is
1420
+					if ($child_line_item->percent() !== 0) {
1421
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1422
+					} else {
1423
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1424
+					}
1425
+					//and apply to all the taxable totals, and add to the pretax totals
1426
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1427
+						//"total" and "taxable" array key is an exception
1428
+						if ($line_item_id === 'taxable') {
1429
+							continue;
1430
+						}
1431
+						$taxable_total = $running_totals['taxable'][$line_item_id];
1432
+						$running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1433
+					}
1434
+					break;
1435
+
1436
+				case EEM_Line_Item::type_line_item :
1437
+
1438
+					// ticket line items or ????
1439
+					if ($child_line_item->OBJ_type() === 'Ticket') {
1440
+						// kk it's a ticket
1441
+						if (isset($running_totals[$child_line_item->ID()])) {
1442
+							//huh? that shouldn't happen.
1443
+							$running_totals['total'] += $child_line_item->total();
1444
+						} else {
1445
+							//its not in our running totals yet. great.
1446
+							if ($child_line_item->is_taxable()) {
1447
+								$taxable_amount = $child_line_item->unit_price();
1448
+							} else {
1449
+								$taxable_amount = 0;
1450
+							}
1451
+							// are we only calculating totals for some tickets?
1452
+							if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1453
+								$quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1454
+								$running_totals[$child_line_item->ID()] = $quantity
1455
+									? $child_line_item->unit_price()
1456
+									: 0;
1457
+								$running_totals['taxable'][$child_line_item->ID()] = $quantity
1458
+									? $taxable_amount
1459
+									: 0;
1460
+							} else {
1461
+								$quantity = $child_line_item->quantity();
1462
+								$running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1463
+								$running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1464
+							}
1465
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1466
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1467
+						}
1468
+					} else {
1469
+						// it's some other type of item added to the cart
1470
+						// it should affect the running totals
1471
+						// basically we want to convert it into a PERCENT modifier. Because
1472
+						// more clearly affect all registration's final price equally
1473
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1474
+							? ($child_line_item->total() / $running_totals['total']) + 1
1475
+							: 1;
1476
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1477
+							//the "taxable" array key is an exception
1478
+							if ($line_item_id === 'taxable') {
1479
+								continue;
1480
+							}
1481
+							// update the running totals
1482
+							// yes this actually even works for the running grand total!
1483
+							$running_totals[$line_item_id] =
1484
+								$line_items_percent_of_running_total * $this_running_total;
1485
+
1486
+							if ($child_line_item->is_taxable()) {
1487
+								$running_totals['taxable'][$line_item_id] =
1488
+									$line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1489
+							}
1490
+						}
1491
+					}
1492
+					break;
1493
+			}
1494
+		}
1495
+		return $running_totals;
1496
+	}
1497
+
1498
+
1499
+	/**
1500
+	 * @param \EE_Line_Item $total_line_item
1501
+	 * @param \EE_Line_Item $ticket_line_item
1502
+	 * @return float | null
1503
+	 * @throws \OutOfRangeException
1504
+	 */
1505
+	public static function calculate_final_price_for_ticket_line_item(\EE_Line_Item $total_line_item, \EE_Line_Item $ticket_line_item)
1506
+	{
1507
+		static $final_prices_per_ticket_line_item = array();
1508
+		if (empty($final_prices_per_ticket_line_item)) {
1509
+			$final_prices_per_ticket_line_item = \EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1510
+				$total_line_item
1511
+			);
1512
+		}
1513
+		//ok now find this new registration's final price
1514
+		if (isset($final_prices_per_ticket_line_item[$ticket_line_item->ID()])) {
1515
+			return $final_prices_per_ticket_line_item[$ticket_line_item->ID()];
1516
+		}
1517
+		$message = sprintf(
1518
+			__(
1519
+				'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1520
+				'event_espresso'
1521
+			),
1522
+			$ticket_line_item->ID()
1523
+		);
1524
+		if (WP_DEBUG) {
1525
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1526
+			throw new \OutOfRangeException($message);
1527
+		} else {
1528
+			EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1529
+		}
1530
+		return null;
1531
+	}
1532
+
1533
+
1534
+	/**
1535
+	 * Creates a duplicate of the line item tree, except only includes billable items
1536
+	 * and the portion of line items attributed to billable things
1537
+	 *
1538
+	 * @param EE_Line_Item $line_item
1539
+	 * @param EE_Registration[] $registrations
1540
+	 * @return \EE_Line_Item
1541
+	 * @throws \EE_Error
1542
+	 */
1543
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1544
+	{
1545
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1546
+		foreach ($line_item->children() as $child_li) {
1547
+			$copy_li->add_child_line_item(EEH_Line_Item::billable_line_item_tree($child_li, $registrations));
1548
+		}
1549
+		//if this is the grand total line item, make sure the totals all add up
1550
+		//(we could have duplicated this logic AS we copied the line items, but
1551
+		//it seems DRYer this way)
1552
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1553
+			$copy_li->recalculate_total_including_taxes();
1554
+		}
1555
+		return $copy_li;
1556
+	}
1557
+
1558
+
1559
+	/**
1560
+	 * Creates a new, unsaved line item from $line_item that factors in the
1561
+	 * number of billable registrations on $registrations.
1562
+	 *
1563
+	 * @param EE_Line_Item $line_item
1564
+	 * @return EE_Line_Item
1565
+	 * @throws \EE_Error
1566
+	 * @param EE_Registration[] $registrations
1567
+	 */
1568
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1569
+	{
1570
+		$new_li_fields = $line_item->model_field_array();
1571
+		if ($line_item->type() === EEM_Line_Item::type_line_item &&
1572
+			$line_item->OBJ_type() === 'Ticket'
1573
+		) {
1574
+			$count = 0;
1575
+			foreach ($registrations as $registration) {
1576
+				if ($line_item->OBJ_ID() === $registration->ticket_ID() &&
1577
+					in_array($registration->status_ID(), EEM_Registration::reg_statuses_that_allow_payment())
1578
+				) {
1579
+					$count++;
1580
+				}
1581
+			}
1582
+			$new_li_fields['LIN_quantity'] = $count;
1583
+		}
1584
+		//don't set the total. We'll leave that up to the code that calculates it
1585
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1586
+		return EE_Line_Item::new_instance($new_li_fields);
1587
+	}
1588
+
1589
+
1590
+	/**
1591
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1592
+	 * are removed, and line items with a quantity of 0
1593
+	 *
1594
+	 * @param EE_Line_Item $line_item |null
1595
+	 * @return \EE_Line_Item|null
1596
+	 * @throws \EE_Error
1597
+	 */
1598
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1599
+	{
1600
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1601
+		if ($copied_li === null) {
1602
+			return null;
1603
+		}
1604
+		//if this is an event subtotal, we want to only include it if it
1605
+		//has a non-zero total and at least one ticket line item child
1606
+		$ticket_children = 0;
1607
+		foreach ($line_item->children() as $child_li) {
1608
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1609
+			if ($child_li_copy !== null) {
1610
+				$copied_li->add_child_line_item($child_li_copy);
1611
+				if ($child_li_copy->type() === EEM_Line_Item::type_line_item &&
1612
+					$child_li_copy->OBJ_type() === 'Ticket'
1613
+				) {
1614
+					$ticket_children++;
1615
+				}
1616
+			}
1617
+		}
1618
+		//if this is an event subtotal with NO ticket children
1619
+		//we basically want to ignore it
1620
+		if (
1621
+			$ticket_children === 0
1622
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1623
+			&& $line_item->OBJ_type() === 'Event'
1624
+			&& $line_item->total() === 0
1625
+		) {
1626
+			return null;
1627
+		}
1628
+		return $copied_li;
1629
+	}
1630
+
1631
+
1632
+	/**
1633
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1634
+	 * with a total of 0, or a subtotal of 0, returns null instead
1635
+	 *
1636
+	 * @param EE_Line_Item $line_item
1637
+	 * @return EE_Line_Item
1638
+	 * @throws \EE_Error
1639
+	 */
1640
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1641
+	{
1642
+		if ($line_item->type() === EEM_Line_Item::type_line_item &&
1643
+			$line_item->OBJ_type() === 'Ticket' &&
1644
+			$line_item->quantity() === 0
1645
+		) {
1646
+			return null;
1647
+		}
1648
+		$new_li_fields = $line_item->model_field_array();
1649
+		//don't set the total. We'll leave that up to the code that calculates it
1650
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1651
+		return EE_Line_Item::new_instance($new_li_fields);
1652
+	}
1653
+
1654
+
1655
+
1656
+	/**************************************** @DEPRECATED METHODS *************************************** */
1657
+	/**
1658
+	 * @deprecated
1659
+	 * @param EE_Line_Item $total_line_item
1660
+	 * @return \EE_Line_Item
1661
+	 * @throws \EE_Error
1662
+	 */
1663
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
1664
+	{
1665
+		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');
1666
+		return self::get_pre_tax_subtotal($total_line_item);
1667
+	}
1668
+
1669
+
1670
+	/**
1671
+	 * @deprecated
1672
+	 * @param EE_Transaction $transaction
1673
+	 * @return \EE_Line_Item
1674
+	 * @throws \EE_Error
1675
+	 */
1676
+	public static function create_default_total_line_item($transaction = NULL)
1677
+	{
1678
+		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');
1679
+		return self::create_total_line_item($transaction);
1680
+	}
1681
+
1682
+
1683
+	/**
1684
+	 * @deprecated
1685
+	 * @param EE_Line_Item $total_line_item
1686
+	 * @param EE_Transaction $transaction
1687
+	 * @return \EE_Line_Item
1688
+	 * @throws \EE_Error
1689
+	 */
1690
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1691
+	{
1692
+		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');
1693
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
1694
+	}
1695
+
1696
+
1697
+	/**
1698
+	 * @deprecated
1699
+	 * @param EE_Line_Item $total_line_item
1700
+	 * @param EE_Transaction $transaction
1701
+	 * @return \EE_Line_Item
1702
+	 * @throws \EE_Error
1703
+	 */
1704
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1705
+	{
1706
+		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');
1707
+		return self::create_taxes_subtotal($total_line_item, $transaction);
1708
+	}
1709
+
1710
+
1711
+	/**
1712
+	 * @deprecated
1713
+	 * @param EE_Line_Item $total_line_item
1714
+	 * @param EE_Transaction $transaction
1715
+	 * @return \EE_Line_Item
1716
+	 * @throws \EE_Error
1717
+	 */
1718
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = NULL)
1719
+	{
1720
+		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');
1721
+		return self::create_event_subtotal($total_line_item, $transaction);
1722
+	}
1723 1723
 
1724 1724
 
1725 1725
 }
Please login to merge, or discard this patch.
Spacing   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -1,4 +1,4 @@  discard block
 block discarded – undo
1
-<?php if (!defined('EVENT_ESPRESSO_VERSION')) {
1
+<?php if ( ! defined('EVENT_ESPRESSO_VERSION')) {
2 2
     exit('No direct script access allowed');
3 3
 }
4 4
 
@@ -56,7 +56,7 @@  discard block
 block discarded – undo
56 56
             'LIN_percent' => null,
57 57
             'LIN_is_taxable' => $taxable,
58 58
             'LIN_order' => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
59
-            'LIN_total' => (float)$unit_price * (int)$quantity,
59
+            'LIN_total' => (float) $unit_price * (int) $quantity,
60 60
             'LIN_type' => EEM_Line_Item::type_line_item,
61 61
             'LIN_code' => $code,
62 62
         ));
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
             'LIN_percent' => $percentage_amount,
96 96
             'LIN_quantity' => 1,
97 97
             'LIN_is_taxable' => $taxable,
98
-            'LIN_total' => (float)($percentage_amount * ($parent_line_item->total() / 100)),
98
+            'LIN_total' => (float) ($percentage_amount * ($parent_line_item->total() / 100)),
99 99
             'LIN_type' => EEM_Line_Item::type_line_item,
100 100
             'LIN_parent' => $parent_line_item->ID()
101 101
         ));
@@ -125,13 +125,13 @@  discard block
 block discarded – undo
125 125
      */
126 126
     public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
127 127
     {
128
-        if (!$total_line_item instanceof EE_Line_Item || !$total_line_item->is_total()) {
128
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
129 129
             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()));
130 130
         }
131 131
         // either increment the qty for an existing ticket
132 132
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
133 133
         // or add a new one
134
-        if (!$line_item instanceof EE_Line_Item) {
134
+        if ( ! $line_item instanceof EE_Line_Item) {
135 135
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
136 136
         }
137 137
         $total_line_item->recalculate_total_including_taxes();
@@ -152,10 +152,10 @@  discard block
 block discarded – undo
152 152
         $line_item = null;
153 153
         if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
154 154
             $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
155
-            foreach ((array)$ticket_line_items as $ticket_line_item) {
155
+            foreach ((array) $ticket_line_items as $ticket_line_item) {
156 156
                 if (
157 157
                     $ticket_line_item instanceof EE_Line_Item
158
-                    && (int)$ticket_line_item->OBJ_ID() === (int)$ticket->ID()
158
+                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
159 159
                 ) {
160 160
                     $line_item = $ticket_line_item;
161 161
                     break;
@@ -181,7 +181,7 @@  discard block
 block discarded – undo
181 181
      */
182 182
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
183 183
     {
184
-        if (!$line_item->is_percent()) {
184
+        if ( ! $line_item->is_percent()) {
185 185
             $qty += $line_item->quantity();
186 186
             $line_item->set_quantity($qty);
187 187
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -206,7 +206,7 @@  discard block
 block discarded – undo
206 206
      */
207 207
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
208 208
     {
209
-        if (!$line_item->is_percent()) {
209
+        if ( ! $line_item->is_percent()) {
210 210
             $qty = $line_item->quantity() - $qty;
211 211
             $qty = max($qty, 0);
212 212
             $line_item->set_quantity($qty);
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
      */
232 232
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
233 233
     {
234
-        if (!$line_item->is_percent()) {
234
+        if ( ! $line_item->is_percent()) {
235 235
             $line_item->set_quantity($new_quantity);
236 236
             $line_item->set_total($line_item->unit_price() * $new_quantity);
237 237
             $line_item->save();
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
         // add $ticket to cart
268 268
         $line_item = EE_Line_Item::new_instance(array(
269 269
             'LIN_name' => $ticket->name(),
270
-            'LIN_desc' => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
270
+            'LIN_desc' => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
271 271
             'LIN_unit_price' => $ticket->price(),
272 272
             'LIN_quantity' => $qty,
273 273
             'LIN_is_taxable' => $ticket->taxable(),
@@ -377,8 +377,8 @@  discard block
 block discarded – undo
377 377
         foreach ($ticket_line_item->children() as $child_line_item) {
378 378
             if (
379 379
                 $child_line_item->is_sub_line_item()
380
-                && !$child_line_item->is_percent()
381
-                && !$child_line_item->is_cancellation()
380
+                && ! $child_line_item->is_percent()
381
+                && ! $child_line_item->is_cancellation()
382 382
             ) {
383 383
                 $child_line_item->set_quantity($child_line_item->quantity() - $qty);
384 384
             }
@@ -400,7 +400,7 @@  discard block
 block discarded – undo
400 400
                 'LIN_desc' => sprintf(
401 401
                     _x('Cancelled %1$s : %2$s', 'Cancelled Ticket Name : 2015-01-01 11:11', 'event_espresso'),
402 402
                     $ticket_line_item->name(),
403
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
403
+                    current_time(get_option('date_format').' '.get_option('time_format'))
404 404
                 ),
405 405
                 'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
406 406
                 'LIN_quantity' => $qty,
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
         );
454 454
         $cancellation_line_item = reset($cancellation_line_item);
455 455
         // verify that this ticket was indeed previously cancelled
456
-        if (!$cancellation_line_item instanceof EE_Line_Item) {
456
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
457 457
             return false;
458 458
         }
459 459
         if ($cancellation_line_item->quantity() > $qty) {
@@ -625,7 +625,7 @@  discard block
 block discarded – undo
625 625
             'LIN_code' => 'taxes',
626 626
             'LIN_name' => __('Taxes', 'event_espresso'),
627 627
             'LIN_type' => EEM_Line_Item::type_tax_sub_total,
628
-            'LIN_order' => 1000,//this should always come last
628
+            'LIN_order' => 1000, //this should always come last
629 629
         ));
630 630
         $tax_line_item = apply_filters(
631 631
             'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
@@ -677,7 +677,7 @@  discard block
 block discarded – undo
677 677
      */
678 678
     public static function get_event_code($event)
679 679
     {
680
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
680
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
681 681
     }
682 682
 
683 683
     /**
@@ -713,13 +713,13 @@  discard block
 block discarded – undo
713 713
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
714 714
     {
715 715
         $first_datetime = $ticket->first_datetime();
716
-        if (!$first_datetime instanceof EE_Datetime) {
716
+        if ( ! $first_datetime instanceof EE_Datetime) {
717 717
             throw new EE_Error(
718 718
                 sprintf(__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'), $ticket->ID())
719 719
             );
720 720
         }
721 721
         $event = $first_datetime->event();
722
-        if (!$event instanceof EE_Event) {
722
+        if ( ! $event instanceof EE_Event) {
723 723
             throw new EE_Error(
724 724
                 sprintf(
725 725
                     __('The supplied ticket (ID %d) has no event data associated with it.', 'event_espresso'),
@@ -728,7 +728,7 @@  discard block
 block discarded – undo
728 728
             );
729 729
         }
730 730
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
731
-        if (!$events_sub_total instanceof EE_Line_Item) {
731
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
732 732
             throw new EE_Error(
733 733
                 sprintf(
734 734
                     __('There is no events sub-total for ticket %s on total line item %d', 'event_espresso'),
@@ -757,7 +757,7 @@  discard block
 block discarded – undo
757 757
         $found = false;
758 758
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
759 759
             // default event subtotal, we should only ever find this the first time this method is called
760
-            if (!$event_line_item->OBJ_ID()) {
760
+            if ( ! $event_line_item->OBJ_ID()) {
761 761
                 // let's use this! but first... set the event details
762 762
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
763 763
                 $found = true;
@@ -768,7 +768,7 @@  discard block
 block discarded – undo
768 768
                 break;
769 769
             }
770 770
         }
771
-        if (!$found) {
771
+        if ( ! $found) {
772 772
             //there is no event sub-total yet, so add it
773 773
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
774 774
             // create a new "event" subtotal below that
@@ -863,7 +863,7 @@  discard block
 block discarded – undo
863 863
     public static function ensure_taxes_applied($total_line_item)
864 864
     {
865 865
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
866
-        if (!$taxes_subtotal->children()) {
866
+        if ( ! $taxes_subtotal->children()) {
867 867
             self::apply_taxes($total_line_item);
868 868
         }
869 869
         return $taxes_subtotal->total();
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
925 925
 
926 926
         // check if only a single line_item_id was passed
927
-        if (!empty($line_item_codes) && !is_array($line_item_codes)) {
927
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
928 928
             // place single line_item_id in an array to appear as multiple line_item_ids
929 929
             $line_item_codes = array($line_item_codes);
930 930
         }
@@ -1027,7 +1027,7 @@  discard block
 block discarded – undo
1027 1027
         if ($code_substring_for_whitelist !== null) {
1028 1028
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false ? true : false;
1029 1029
         }
1030
-        if (!$whitelisted && $line_item->is_line_item()) {
1030
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1031 1031
             $line_item->set_is_taxable($taxable);
1032 1032
         }
1033 1033
         foreach ($line_item->children() as $child_line_item) {
@@ -1332,7 +1332,7 @@  discard block
 block discarded – undo
1332 1332
     public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1333 1333
     {
1334 1334
         echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1335
-        if (!$indentation) {
1335
+        if ( ! $indentation) {
1336 1336
             echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1337 1337
         }
1338 1338
         for ($i = 0; $i < $indentation; $i++) {
@@ -1343,10 +1343,10 @@  discard block
 block discarded – undo
1343 1343
             if ($line_item->is_percent()) {
1344 1344
                 $breakdown = "{$line_item->percent()}%";
1345 1345
             } else {
1346
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1346
+                $breakdown = '$'."{$line_item->unit_price()} x {$line_item->quantity()}";
1347 1347
             }
1348 1348
         }
1349
-        echo $line_item->name() . " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : " . '$' . "{$line_item->total()}";
1349
+        echo $line_item->name()." [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ".'$'."{$line_item->total()}";
1350 1350
         if ($breakdown) {
1351 1351
             echo " ( {$breakdown} )";
1352 1352
         }
@@ -1397,10 +1397,10 @@  discard block
 block discarded – undo
1397 1397
     public static function calculate_reg_final_prices_per_line_item(EE_Line_Item $line_item, $billable_ticket_quantities = array())
1398 1398
     {
1399 1399
         //init running grand total if not already
1400
-        if (!isset($running_totals['total'])) {
1400
+        if ( ! isset($running_totals['total'])) {
1401 1401
             $running_totals['total'] = 0;
1402 1402
         }
1403
-        if (!isset($running_totals['taxable'])) {
1403
+        if ( ! isset($running_totals['taxable'])) {
1404 1404
             $running_totals['taxable'] = array('total' => 0);
1405 1405
         }
1406 1406
         foreach ($line_item->children() as $child_line_item) {
@@ -1522,7 +1522,7 @@  discard block
 block discarded – undo
1522 1522
             $ticket_line_item->ID()
1523 1523
         );
1524 1524
         if (WP_DEBUG) {
1525
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1525
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1526 1526
             throw new \OutOfRangeException($message);
1527 1527
         } else {
1528 1528
             EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/db_classes/EE_Line_Item.class.php 2 patches
Indentation   +1426 added lines, -1426 removed lines patch added patch discarded remove patch
@@ -17,1432 +17,1432 @@
 block discarded – undo
17 17
 class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
18 18
 {
19 19
 
20
-    /**
21
-     * for children line items (currently not a normal relation)
22
-     *
23
-     * @type EE_Line_Item[]
24
-     */
25
-    protected $_children = array();
26
-
27
-    /**
28
-     * for the parent line item
29
-     *
30
-     * @var EE_Line_Item
31
-     */
32
-    protected $_parent;
33
-
34
-
35
-    /**
36
-     *
37
-     * @param array $props_n_values incoming values
38
-     * @param string $timezone incoming timezone (if not set the timezone set for the website will be
39
-     *                                        used.)
40
-     * @param array $date_formats incoming date_formats in an array where the first value is the
41
-     *                                        date_format and the second value is the time format
42
-     * @return EE_Line_Item
43
-     * @throws EE_Error
44
-     */
45
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
46
-    {
47
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
48
-        return $has_object
49
-            ? $has_object
50
-            : new self($props_n_values, false, $timezone);
51
-    }
52
-
53
-
54
-    /**
55
-     * @param array $props_n_values incoming values from the database
56
-     * @param string $timezone incoming timezone as set by the model.  If not set the timezone for
57
-     *                                the website will be used.
58
-     * @return EE_Line_Item
59
-     * @throws EE_Error
60
-     */
61
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
62
-    {
63
-        return new self($props_n_values, true, $timezone);
64
-    }
65
-
66
-
67
-    /**
68
-     * Adds some defaults if they're not specified
69
-     *
70
-     * @param array $fieldValues
71
-     * @param bool $bydb
72
-     * @param string $timezone
73
-     * @throws EE_Error
74
-     */
75
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
76
-    {
77
-        parent::__construct($fieldValues, $bydb, $timezone);
78
-        if (!$this->get('LIN_code')) {
79
-            $this->set_code($this->generate_code());
80
-        }
81
-    }
82
-
83
-
84
-    /**
85
-     * Gets ID
86
-     *
87
-     * @return int
88
-     * @throws EE_Error
89
-     */
90
-    public function ID()
91
-    {
92
-        return $this->get('LIN_ID');
93
-    }
94
-
95
-
96
-    /**
97
-     * Gets TXN_ID
98
-     *
99
-     * @return int
100
-     * @throws EE_Error
101
-     */
102
-    public function TXN_ID()
103
-    {
104
-        return $this->get('TXN_ID');
105
-    }
106
-
107
-
108
-    /**
109
-     * Sets TXN_ID
110
-     *
111
-     * @param int $TXN_ID
112
-     * @throws EE_Error
113
-     */
114
-    public function set_TXN_ID($TXN_ID)
115
-    {
116
-        $this->set('TXN_ID', $TXN_ID);
117
-    }
118
-
119
-
120
-    /**
121
-     * Gets name
122
-     *
123
-     * @return string
124
-     * @throws EE_Error
125
-     */
126
-    public function name()
127
-    {
128
-        $name = $this->get('LIN_name');
129
-        if (!$name) {
130
-            $name = ucwords(str_replace('-', ' ', $this->type()));
131
-        }
132
-        return $name;
133
-    }
134
-
135
-
136
-    /**
137
-     * Sets name
138
-     *
139
-     * @param string $name
140
-     * @throws EE_Error
141
-     */
142
-    public function set_name($name)
143
-    {
144
-        $this->set('LIN_name', $name);
145
-    }
146
-
147
-
148
-    /**
149
-     * Gets desc
150
-     *
151
-     * @return string
152
-     * @throws EE_Error
153
-     */
154
-    public function desc()
155
-    {
156
-        return $this->get('LIN_desc');
157
-    }
158
-
159
-
160
-    /**
161
-     * Sets desc
162
-     *
163
-     * @param string $desc
164
-     * @throws EE_Error
165
-     */
166
-    public function set_desc($desc)
167
-    {
168
-        $this->set('LIN_desc', $desc);
169
-    }
170
-
171
-
172
-    /**
173
-     * Gets quantity
174
-     *
175
-     * @return int
176
-     * @throws EE_Error
177
-     */
178
-    public function quantity()
179
-    {
180
-        return $this->get('LIN_quantity');
181
-    }
182
-
183
-
184
-    /**
185
-     * Sets quantity
186
-     *
187
-     * @param int $quantity
188
-     * @throws EE_Error
189
-     */
190
-    public function set_quantity($quantity)
191
-    {
192
-        $this->set('LIN_quantity', max($quantity, 0));
193
-    }
194
-
195
-
196
-    /**
197
-     * Gets item_id
198
-     *
199
-     * @return string
200
-     * @throws EE_Error
201
-     */
202
-    public function OBJ_ID()
203
-    {
204
-        return $this->get('OBJ_ID');
205
-    }
206
-
207
-
208
-    /**
209
-     * Sets item_id
210
-     *
211
-     * @param string $item_id
212
-     * @throws EE_Error
213
-     */
214
-    public function set_OBJ_ID($item_id)
215
-    {
216
-        $this->set('OBJ_ID', $item_id);
217
-    }
218
-
219
-
220
-    /**
221
-     * Gets item_type
222
-     *
223
-     * @return string
224
-     * @throws EE_Error
225
-     */
226
-    public function OBJ_type()
227
-    {
228
-        return $this->get('OBJ_type');
229
-    }
230
-
231
-
232
-    /**
233
-     * Gets item_type
234
-     *
235
-     * @return string
236
-     * @throws EE_Error
237
-     */
238
-    public function OBJ_type_i18n()
239
-    {
240
-        $obj_type = $this->OBJ_type();
241
-        switch ($obj_type) {
242
-            case 'Event':
243
-                $obj_type = __('Event', 'event_espresso');
244
-                break;
245
-            case 'Price':
246
-                $obj_type = __('Price', 'event_espresso');
247
-                break;
248
-            case 'Promotion':
249
-                $obj_type = __('Promotion', 'event_espresso');
250
-                break;
251
-            case 'Ticket':
252
-                $obj_type = __('Ticket', 'event_espresso');
253
-                break;
254
-            case 'Transaction':
255
-                $obj_type = __('Transaction', 'event_espresso');
256
-                break;
257
-        }
258
-        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
259
-    }
260
-
261
-
262
-    /**
263
-     * Sets item_type
264
-     *
265
-     * @param string $OBJ_type
266
-     * @throws EE_Error
267
-     */
268
-    public function set_OBJ_type($OBJ_type)
269
-    {
270
-        $this->set('OBJ_type', $OBJ_type);
271
-    }
272
-
273
-
274
-    /**
275
-     * Gets unit_price
276
-     *
277
-     * @return float
278
-     * @throws EE_Error
279
-     */
280
-    public function unit_price()
281
-    {
282
-        return $this->get('LIN_unit_price');
283
-    }
284
-
285
-
286
-    /**
287
-     * Sets unit_price
288
-     *
289
-     * @param float $unit_price
290
-     * @throws EE_Error
291
-     */
292
-    public function set_unit_price($unit_price)
293
-    {
294
-        $this->set('LIN_unit_price', $unit_price);
295
-    }
296
-
297
-
298
-    /**
299
-     * Checks if this item is a percentage modifier or not
300
-     *
301
-     * @return boolean
302
-     * @throws EE_Error
303
-     */
304
-    public function is_percent()
305
-    {
306
-        if ($this->is_tax_sub_total()) {
307
-            //tax subtotals HAVE a percent on them, that percentage only applies
308
-            //to taxable items, so its' an exception. Treat it like a flat line item
309
-            return false;
310
-        }
311
-        $unit_price = abs($this->get('LIN_unit_price'));
312
-        $percent = abs($this->get('LIN_percent'));
313
-        if ($unit_price < .001 && $percent) {
314
-            return true;
315
-        }
316
-        if ($unit_price >= .001 && !$percent) {
317
-            return false;
318
-        }
319
-        if ($unit_price >= .001 && $percent) {
320
-            throw new EE_Error(
321
-                sprintf(
322
-                    esc_html__('A Line Item can not have a unit price of (%s) AND a percent (%s)!', 'event_espresso'),
323
-                    $unit_price, $percent
324
-                )
325
-            );
326
-        }
327
-        // if they're both 0, assume its not a percent item
328
-        return false;
329
-    }
330
-
331
-
332
-    /**
333
-     * Gets percent (between 100-.001)
334
-     *
335
-     * @return float
336
-     * @throws EE_Error
337
-     */
338
-    public function percent()
339
-    {
340
-        return $this->get('LIN_percent');
341
-    }
342
-
343
-
344
-    /**
345
-     * Sets percent (between 100-0.01)
346
-     *
347
-     * @param float $percent
348
-     * @throws EE_Error
349
-     */
350
-    public function set_percent($percent)
351
-    {
352
-        $this->set('LIN_percent', $percent);
353
-    }
354
-
355
-
356
-    /**
357
-     * Gets total
358
-     *
359
-     * @return float
360
-     * @throws EE_Error
361
-     */
362
-    public function total()
363
-    {
364
-        return $this->get('LIN_total');
365
-    }
366
-
367
-
368
-    /**
369
-     * Sets total
370
-     *
371
-     * @param float $total
372
-     * @throws EE_Error
373
-     */
374
-    public function set_total($total)
375
-    {
376
-        $this->set('LIN_total', $total);
377
-    }
378
-
379
-
380
-    /**
381
-     * Gets order
382
-     *
383
-     * @return int
384
-     * @throws EE_Error
385
-     */
386
-    public function order()
387
-    {
388
-        return $this->get('LIN_order');
389
-    }
390
-
391
-
392
-    /**
393
-     * Sets order
394
-     *
395
-     * @param int $order
396
-     * @throws EE_Error
397
-     */
398
-    public function set_order($order)
399
-    {
400
-        $this->set('LIN_order', $order);
401
-    }
402
-
403
-
404
-    /**
405
-     * Gets parent
406
-     *
407
-     * @return int
408
-     * @throws EE_Error
409
-     */
410
-    public function parent_ID()
411
-    {
412
-        return $this->get('LIN_parent');
413
-    }
414
-
415
-
416
-    /**
417
-     * Sets parent
418
-     *
419
-     * @param int $parent
420
-     * @throws EE_Error
421
-     */
422
-    public function set_parent_ID($parent)
423
-    {
424
-        $this->set('LIN_parent', $parent);
425
-    }
426
-
427
-
428
-    /**
429
-     * Gets type
430
-     *
431
-     * @return string
432
-     * @throws EE_Error
433
-     */
434
-    public function type()
435
-    {
436
-        return $this->get('LIN_type');
437
-    }
438
-
439
-
440
-    /**
441
-     * Sets type
442
-     *
443
-     * @param string $type
444
-     * @throws EE_Error
445
-     */
446
-    public function set_type($type)
447
-    {
448
-        $this->set('LIN_type', $type);
449
-    }
450
-
451
-
452
-    /**
453
-     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
454
-     * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
455
-     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
456
-     * or indirectly by `EE_Line_item::add_child_line_item()`)
457
-     *
458
-     * @return EE_Base_Class|EE_Line_Item
459
-     * @throws EE_Error
460
-     */
461
-    public function parent()
462
-    {
463
-        return $this->ID()
464
-            ? $this->get_model()->get_one_by_ID($this->parent_ID())
465
-            : $this->_parent;
466
-    }
467
-
468
-
469
-    /**
470
-     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
471
-     *
472
-     * @return EE_Base_Class[]|EE_Line_Item[]
473
-     * @throws EE_Error
474
-     */
475
-    public function children()
476
-    {
477
-        if ($this->ID()) {
478
-            return $this->get_model()->get_all(
479
-                array(
480
-                    array('LIN_parent' => $this->ID()),
481
-                    'order_by' => array('LIN_order' => 'ASC'),
482
-                )
483
-            );
484
-        }
485
-        if (!is_array($this->_children)) {
486
-            $this->_children = array();
487
-        }
488
-        return $this->_children;
489
-    }
490
-
491
-
492
-    /**
493
-     * Gets code
494
-     *
495
-     * @return string
496
-     * @throws EE_Error
497
-     */
498
-    public function code()
499
-    {
500
-        return $this->get('LIN_code');
501
-    }
502
-
503
-
504
-    /**
505
-     * Sets code
506
-     *
507
-     * @param string $code
508
-     * @throws EE_Error
509
-     */
510
-    public function set_code($code)
511
-    {
512
-        $this->set('LIN_code', $code);
513
-    }
514
-
515
-
516
-    /**
517
-     * Gets is_taxable
518
-     *
519
-     * @return boolean
520
-     * @throws EE_Error
521
-     */
522
-    public function is_taxable()
523
-    {
524
-        return $this->get('LIN_is_taxable');
525
-    }
526
-
527
-
528
-    /**
529
-     * Sets is_taxable
530
-     *
531
-     * @param boolean $is_taxable
532
-     * @throws EE_Error
533
-     */
534
-    public function set_is_taxable($is_taxable)
535
-    {
536
-        $this->set('LIN_is_taxable', $is_taxable);
537
-    }
538
-
539
-
540
-    /**
541
-     * Gets the object that this model-joins-to.
542
-     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
543
-     * EEM_Promotion_Object
544
-     *
545
-     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
546
-     *
547
-     * @return EE_Base_Class | NULL
548
-     * @throws EE_Error
549
-     */
550
-    public function get_object()
551
-    {
552
-        $model_name_of_related_obj = $this->OBJ_type();
553
-        return $this->get_model()->has_relation($model_name_of_related_obj)
554
-            ? $this->get_first_related($model_name_of_related_obj)
555
-            : null;
556
-    }
557
-
558
-
559
-    /**
560
-     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
561
-     * (IE, if this line item is for a price or something else, will return NULL)
562
-     *
563
-     * @param array $query_params
564
-     * @return EE_Base_Class|EE_Ticket
565
-     * @throws EE_Error
566
-     */
567
-    public function ticket($query_params = array())
568
-    {
569
-        //we're going to assume that when this method is called we always want to receive the attached ticket EVEN if that ticket is archived.  This can be overridden via the incoming $query_params argument
570
-        $remove_defaults = array('default_where_conditions' => 'none');
571
-        $query_params = array_merge($remove_defaults, $query_params);
572
-        return $this->get_first_related('Ticket', $query_params);
573
-    }
574
-
575
-
576
-    /**
577
-     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
578
-     *
579
-     * @return EE_Datetime | NULL
580
-     * @throws EE_Error
581
-     */
582
-    public function get_ticket_datetime()
583
-    {
584
-        if ($this->OBJ_type() === 'Ticket') {
585
-            $ticket = $this->ticket();
586
-            if ($ticket instanceof EE_Ticket) {
587
-                $datetime = $ticket->first_datetime();
588
-                if ($datetime instanceof EE_Datetime) {
589
-                    return $datetime;
590
-                }
591
-            }
592
-        }
593
-        return null;
594
-    }
595
-
596
-
597
-    /**
598
-     * Gets the event's name that's related to the ticket, if this is for
599
-     * a ticket
600
-     *
601
-     * @return string
602
-     * @throws EE_Error
603
-     */
604
-    public function ticket_event_name()
605
-    {
606
-        $event_name = esc_html__('Unknown', 'event_espresso');
607
-        $event = $this->ticket_event();
608
-        if ($event instanceof EE_Event) {
609
-            $event_name = $event->name();
610
-        }
611
-        return $event_name;
612
-    }
613
-
614
-
615
-    /**
616
-     * Gets the event that's related to the ticket, if this line item represents a ticket.
617
-     *
618
-     * @return EE_Event|null
619
-     * @throws EE_Error
620
-     */
621
-    public function ticket_event()
622
-    {
623
-        $event = null;
624
-        $ticket = $this->ticket();
625
-        if ($ticket instanceof EE_Ticket) {
626
-            $datetime = $ticket->first_datetime();
627
-            if ($datetime instanceof EE_Datetime) {
628
-                $event = $datetime->event();
629
-            }
630
-        }
631
-        return $event;
632
-    }
633
-
634
-
635
-    /**
636
-     * Gets the first datetime for this lien item, assuming it's for a ticket
637
-     *
638
-     * @param string $date_format
639
-     * @param string $time_format
640
-     * @return string
641
-     * @throws EE_Error
642
-     */
643
-    public function ticket_datetime_start($date_format = '', $time_format = '')
644
-    {
645
-        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
646
-        $datetime = $this->get_ticket_datetime();
647
-        if ($datetime) {
648
-            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
649
-        }
650
-        return $first_datetime_string;
651
-    }
652
-
653
-
654
-    /**
655
-     * Adds the line item as a child to this line item. If there is another child line
656
-     * item with the same LIN_code, it is overwritten by this new one
657
-     *
658
-     * @param EEI_Line_Item $line_item
659
-     * @param bool $set_order
660
-     * @return bool success
661
-     * @throws EE_Error
662
-     */
663
-    public function add_child_line_item(EEI_Line_Item $line_item, $set_order = true)
664
-    {
665
-        // should we calculate the LIN_order for this line item ?
666
-        if ($set_order || $line_item->order() === null) {
667
-            $line_item->set_order(count($this->children()));
668
-        }
669
-        if ($this->ID()) {
670
-            //check for any duplicate line items (with the same code), if so, this replaces it
671
-            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
672
-            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
673
-                $this->delete_child_line_item($line_item_with_same_code->code());
674
-            }
675
-            $line_item->set_parent_ID($this->ID());
676
-            if ($this->TXN_ID()) {
677
-                $line_item->set_TXN_ID($this->TXN_ID());
678
-            }
679
-            return $line_item->save();
680
-        }
681
-        $this->_children[$line_item->code()] = $line_item;
682
-        if ($line_item->parent() !== $this) {
683
-            $line_item->set_parent($this);
684
-        }
685
-        return true;
686
-    }
687
-
688
-
689
-    /**
690
-     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
691
-     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
692
-     * However, if this line item is NOT saved to the DB, this just caches the parent on
693
-     * the EE_Line_Item::_parent property.
694
-     *
695
-     * @param EE_Line_Item $line_item
696
-     * @throws EE_Error
697
-     */
698
-    public function set_parent($line_item)
699
-    {
700
-        if ($this->ID()) {
701
-            if (!$line_item->ID()) {
702
-                $line_item->save();
703
-            }
704
-            $this->set_parent_ID($line_item->ID());
705
-            $this->save();
706
-        } else {
707
-            $this->_parent = $line_item;
708
-            $this->set_parent_ID($line_item->ID());
709
-        }
710
-    }
711
-
712
-
713
-    /**
714
-     * Gets the child line item as specified by its code. Because this returns an object (by reference)
715
-     * you can modify this child line item and the parent (this object) can know about them
716
-     * because it also has a reference to that line item
717
-     *
718
-     * @param string $code
719
-     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
720
-     * @throws EE_Error
721
-     */
722
-    public function get_child_line_item($code)
723
-    {
724
-        if ($this->ID()) {
725
-            return $this->get_model()->get_one(
726
-                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
727
-            );
728
-        }
729
-        return isset($this->_children[$code])
730
-            ? $this->_children[$code]
731
-            : null;
732
-    }
733
-
734
-
735
-    /**
736
-     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
737
-     * cached on it)
738
-     *
739
-     * @return int
740
-     * @throws EE_Error
741
-     */
742
-    public function delete_children_line_items()
743
-    {
744
-        if ($this->ID()) {
745
-            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
746
-        }
747
-        $count = count($this->_children);
748
-        $this->_children = array();
749
-        return $count;
750
-    }
751
-
752
-
753
-    /**
754
-     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
755
-     * HAS NOT been saved to the DB, removes the child line item with index $code.
756
-     * Also searches through the child's children for a matching line item. However, once a line item has been found
757
-     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
758
-     * deleted)
759
-     *
760
-     * @param string $code
761
-     * @param bool $stop_search_once_found
762
-     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
763
-     *             the DB yet)
764
-     * @throws EE_Error
765
-     */
766
-    public function delete_child_line_item($code, $stop_search_once_found = true)
767
-    {
768
-        if ($this->ID()) {
769
-            $items_deleted = 0;
770
-            if ($this->code() === $code) {
771
-                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
772
-                $items_deleted += (int)$this->delete();
773
-                if ($stop_search_once_found) {
774
-                    return $items_deleted;
775
-                }
776
-            }
777
-            foreach ($this->children() as $child_line_item) {
778
-                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
779
-            }
780
-            return $items_deleted;
781
-        }
782
-        if (isset($this->_children[$code])) {
783
-            unset($this->_children[$code]);
784
-            return 1;
785
-        }
786
-        return 0;
787
-    }
788
-
789
-
790
-    /**
791
-     * If this line item is in the database, is of the type subtotal, and
792
-     * has no children, why do we have it? It should be deleted so this function
793
-     * does that
794
-     *
795
-     * @return boolean
796
-     * @throws EE_Error
797
-     */
798
-    public function delete_if_childless_subtotal()
799
-    {
800
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && !$this->children()) {
801
-            return $this->delete();
802
-        }
803
-        return false;
804
-    }
805
-
806
-
807
-    /**
808
-     * Creates a code and returns a string. doesn't assign the code to this model object
809
-     *
810
-     * @return string
811
-     * @throws EE_Error
812
-     */
813
-    public function generate_code()
814
-    {
815
-        // each line item in the cart requires a unique identifier
816
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
817
-    }
818
-
819
-
820
-    /**
821
-     * @return bool
822
-     * @throws EE_Error
823
-     */
824
-    public function is_tax()
825
-    {
826
-        return $this->type() === EEM_Line_Item::type_tax;
827
-    }
828
-
829
-
830
-    /**
831
-     * @return bool
832
-     * @throws EE_Error
833
-     */
834
-    public function is_tax_sub_total()
835
-    {
836
-        return $this->type() === EEM_Line_Item::type_tax_sub_total;
837
-    }
838
-
839
-
840
-    /**
841
-     * @return bool
842
-     * @throws EE_Error
843
-     */
844
-    public function is_line_item()
845
-    {
846
-        return $this->type() === EEM_Line_Item::type_line_item;
847
-    }
848
-
849
-
850
-    /**
851
-     * @return bool
852
-     * @throws EE_Error
853
-     */
854
-    public function is_sub_line_item()
855
-    {
856
-        return $this->type() === EEM_Line_Item::type_sub_line_item;
857
-    }
858
-
859
-
860
-    /**
861
-     * @return bool
862
-     * @throws EE_Error
863
-     */
864
-    public function is_sub_total()
865
-    {
866
-        return $this->type() === EEM_Line_Item::type_sub_total;
867
-    }
868
-
869
-
870
-    /**
871
-     * Whether or not this line item is a cancellation line item
872
-     *
873
-     * @return boolean
874
-     * @throws EE_Error
875
-     */
876
-    public function is_cancellation()
877
-    {
878
-        return EEM_Line_Item::type_cancellation === $this->type();
879
-    }
880
-
881
-
882
-    /**
883
-     * @return bool
884
-     * @throws EE_Error
885
-     */
886
-    public function is_total()
887
-    {
888
-        return $this->type() === EEM_Line_Item::type_total;
889
-    }
890
-
891
-
892
-    /**
893
-     * @return bool
894
-     * @throws EE_Error
895
-     */
896
-    public function is_cancelled()
897
-    {
898
-        return $this->type() === EEM_Line_Item::type_cancellation;
899
-    }
900
-
901
-
902
-    /**
903
-     * @return string like '2, 004.00', formatted according to the localized currency
904
-     * @throws EE_Error
905
-     */
906
-    public function unit_price_no_code()
907
-    {
908
-        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
909
-    }
910
-
911
-
912
-    /**
913
-     * @return string like '2, 004.00', formatted according to the localized currency
914
-     * @throws EE_Error
915
-     */
916
-    public function total_no_code()
917
-    {
918
-        return $this->get_pretty('LIN_total', 'no_currency_code');
919
-    }
920
-
921
-
922
-    /**
923
-     * Gets the final total on this item, taking taxes into account.
924
-     * Has the side-effect of setting the sub-total as it was just calculated.
925
-     * If this is used on a grand-total line item, also updates the transaction's
926
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
927
-     * want to change a persistable transaction with info from a non-persistent line item)
928
-     *
929
-     * @return float
930
-     * @throws EE_Error
931
-     * @throws InvalidArgumentException
932
-     * @throws InvalidInterfaceException
933
-     * @throws InvalidDataTypeException
934
-     */
935
-    public function recalculate_total_including_taxes()
936
-    {
937
-        $pre_tax_total = $this->recalculate_pre_tax_total();
938
-        $tax_total = $this->recalculate_taxes_and_tax_total();
939
-        $total = $pre_tax_total + $tax_total;
940
-        // no negative totals plz
941
-        $total = max($total, 0);
942
-        $this->set_total($total);
943
-        //only update the related transaction's total
944
-        //if we intend to save this line item and its a grand total
945
-        if (
946
-            $this->allow_persist() && $this->type() === EEM_Line_Item::type_total
947
-            && $this->transaction()
948
-            instanceof
949
-            EE_Transaction
950
-        ) {
951
-            $this->transaction()->set_total($total);
952
-            if ($this->transaction()->ID()) {
953
-                $this->transaction()->save();
954
-            }
955
-        }
956
-        $this->maybe_save();
957
-        return $total;
958
-    }
959
-
960
-
961
-    /**
962
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
963
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
964
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
965
-     * when this is called on the grand total
966
-     *
967
-     * @return float
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidInterfaceException
970
-     * @throws InvalidDataTypeException
971
-     * @throws EE_Error
972
-     */
973
-    public function recalculate_pre_tax_total()
974
-    {
975
-        $total = 0;
976
-        $my_children = $this->children();
977
-        $has_children = !empty($my_children);
978
-        if ($has_children && $this->is_line_item()) {
979
-            $total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
980
-        } elseif (!$has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
981
-            $total = $this->unit_price() * $this->quantity();
982
-        } elseif ($this->is_sub_total() || $this->is_total()) {
983
-            $total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
984
-        } elseif ($this->is_tax_sub_total() || $this->is_tax() || $this->is_cancelled()) {
985
-            // completely ignore tax totals, tax sub-totals, and cancelled line items, when calculating the pre-tax-total
986
-            return 0;
987
-        }
988
-        // ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
989
-        if (
990
-            !$this->is_line_item() && !$this->is_sub_line_item() && !$this->is_cancellation()
991
-        ) {
992
-            if ($this->OBJ_type() !== 'Event') {
993
-                $this->set_quantity(1);
994
-            }
995
-            if (!$this->is_percent()) {
996
-                $this->set_unit_price($total);
997
-            }
998
-        }
999
-        //we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1000
-        //so it ought to be
1001
-        if (!$this->is_total()) {
1002
-            $this->set_total($total);
1003
-            //if not a percent line item, make sure we keep the unit price in sync
1004
-            if (
1005
-                $has_children
1006
-                && $this->is_line_item()
1007
-                && !$this->is_percent()
1008
-            ) {
1009
-                if ($this->quantity() === 0) {
1010
-                    $new_unit_price = 0;
1011
-                } else {
1012
-                    $new_unit_price = $this->total() / $this->quantity();
1013
-                }
1014
-                $this->set_unit_price($new_unit_price);
1015
-            }
1016
-            $this->maybe_save();
1017
-        }
1018
-        return $total;
1019
-    }
1020
-
1021
-
1022
-    /**
1023
-     * Calculates the pretax total when this line item is a subtotal or total line item.
1024
-     * Basically does a sum-then-round approach (ie, any percent line item that are children
1025
-     * will calculate their total based on the un-rounded total we're working with so far, and
1026
-     * THEN round the result; instead of rounding as we go like with sub-line-items)
1027
-     *
1028
-     * @param float $calculated_total_so_far
1029
-     * @param EE_Line_Item[] $my_children
1030
-     * @return float
1031
-     * @throws InvalidArgumentException
1032
-     * @throws InvalidInterfaceException
1033
-     * @throws InvalidDataTypeException
1034
-     * @throws EE_Error
1035
-     */
1036
-    protected function _recalculate_pretax_total_for_subtotal($calculated_total_so_far, $my_children = null)
1037
-    {
1038
-        if ($my_children === null) {
1039
-            $my_children = $this->children();
1040
-        }
1041
-        $subtotal_quantity = 0;
1042
-        //get the total of all its children
1043
-        foreach ($my_children as $child_line_item) {
1044
-            if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1045
-                // percentage line items are based on total so far
1046
-                if ($child_line_item->is_percent()) {
1047
-                    //round as we go so that the line items add up ok
1048
-                    $percent_total = round(
1049
-                        $calculated_total_so_far * $child_line_item->percent() / 100,
1050
-                        EE_Registry::instance()->CFG->currency->dec_plc
1051
-                    );
1052
-                    $child_line_item->set_total($percent_total);
1053
-                    //so far all percent line items should have a quantity of 1
1054
-                    //(ie, no double percent discounts. Although that might be requested someday)
1055
-                    $child_line_item->set_quantity(1);
1056
-                    $child_line_item->maybe_save();
1057
-                    $calculated_total_so_far += $percent_total;
1058
-                } else {
1059
-                    //verify flat sub-line-item quantities match their parent
1060
-                    if ($child_line_item->is_sub_line_item()) {
1061
-                        $child_line_item->set_quantity($this->quantity());
1062
-                    }
1063
-                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1064
-                    $subtotal_quantity += $child_line_item->quantity();
1065
-                }
1066
-            }
1067
-        }
1068
-        if ($this->is_sub_total()) {
1069
-            // no negative totals plz
1070
-            $calculated_total_so_far = max($calculated_total_so_far, 0);
1071
-            $subtotal_quantity = $subtotal_quantity > 0 ? 1 : 0;
1072
-            $this->set_quantity($subtotal_quantity);
1073
-            $this->maybe_save();
1074
-        }
1075
-        return $calculated_total_so_far;
1076
-    }
1077
-
1078
-
1079
-    /**
1080
-     * Calculates the pretax total for a normal line item, in a round-then-sum approach
1081
-     * (where each sub-line-item is applied to the base price for the line item
1082
-     * and the result is immediately rounded, rather than summing all the sub-line-items
1083
-     * then rounding, like we do when recalculating pretax totals on totals and subtotals).
1084
-     *
1085
-     * @param float $calculated_total_so_far
1086
-     * @param EE_Line_Item[] $my_children
1087
-     * @return float
1088
-     * @throws InvalidArgumentException
1089
-     * @throws InvalidInterfaceException
1090
-     * @throws InvalidDataTypeException
1091
-     * @throws EE_Error
1092
-     */
1093
-    protected function _recalculate_pretax_total_for_line_item($calculated_total_so_far, $my_children = null)
1094
-    {
1095
-        if ($my_children === null) {
1096
-            $my_children = $this->children();
1097
-        }
1098
-        //we need to keep track of the running total for a single item,
1099
-        //because we need to round as we go
1100
-        $unit_price_for_total = 0;
1101
-        $quantity_for_total = 1;
1102
-        //get the total of all its children
1103
-        foreach ($my_children as $child_line_item) {
1104
-            if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1105
-                if ($child_line_item->is_percent()) {
1106
-                    //it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1107
-                    //not total multiplied by percent, because that ignores rounding along-the-way
1108
-                    $percent_unit_price = round(
1109
-                        $unit_price_for_total * $child_line_item->percent() / 100,
1110
-                        EE_Registry::instance()->CFG->currency->dec_plc
1111
-                    );
1112
-                    $percent_total = $percent_unit_price * $quantity_for_total;
1113
-                    $child_line_item->set_total($percent_total);
1114
-                    //so far all percent line items should have a quantity of 1
1115
-                    //(ie, no double percent discounts. Although that might be requested someday)
1116
-                    $child_line_item->set_quantity(1);
1117
-                    $child_line_item->maybe_save();
1118
-                    $calculated_total_so_far += $percent_total;
1119
-                    $unit_price_for_total += $percent_unit_price;
1120
-                } else {
1121
-                    //verify flat sub-line-item quantities match their parent
1122
-                    if ($child_line_item->is_sub_line_item()) {
1123
-                        $child_line_item->set_quantity($this->quantity());
1124
-                    }
1125
-                    $quantity_for_total = $child_line_item->quantity();
1126
-                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1127
-                    $unit_price_for_total += $child_line_item->unit_price();
1128
-                }
1129
-            }
1130
-        }
1131
-        return $calculated_total_so_far;
1132
-    }
1133
-
1134
-
1135
-    /**
1136
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1137
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1138
-     * and tax sub-total if already in the DB
1139
-     *
1140
-     * @return float
1141
-     * @throws EE_Error
1142
-     */
1143
-    public function recalculate_taxes_and_tax_total()
1144
-    {
1145
-        //get all taxes
1146
-        $taxes = $this->tax_descendants();
1147
-        //calculate the pretax total
1148
-        $taxable_total = $this->taxable_total();
1149
-        $tax_total = 0;
1150
-        foreach ($taxes as $tax) {
1151
-            $total_on_this_tax = $taxable_total * $tax->percent() / 100;
1152
-            //remember the total on this line item
1153
-            $tax->set_total($total_on_this_tax);
1154
-            $tax->maybe_save();
1155
-            $tax_total += $tax->total();
1156
-        }
1157
-        $this->_recalculate_tax_sub_total();
1158
-        return $tax_total;
1159
-    }
1160
-
1161
-
1162
-    /**
1163
-     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
1164
-     *
1165
-     * @return void
1166
-     * @throws EE_Error
1167
-     */
1168
-    private function _recalculate_tax_sub_total()
1169
-    {
1170
-        if ($this->is_tax_sub_total()) {
1171
-            $total = 0;
1172
-            $total_percent = 0;
1173
-            //simply loop through all its children (which should be taxes) and sum their total
1174
-            foreach ($this->children() as $child_tax) {
1175
-                if ($child_tax instanceof EE_Line_Item) {
1176
-                    $total += $child_tax->total();
1177
-                    $total_percent += $child_tax->percent();
1178
-                }
1179
-            }
1180
-            $this->set_total($total);
1181
-            $this->set_percent($total_percent);
1182
-            $this->maybe_save();
1183
-        } elseif ($this->is_total()) {
1184
-            foreach ($this->children() as $maybe_tax_subtotal) {
1185
-                if ($maybe_tax_subtotal instanceof EE_Line_Item) {
1186
-                    $maybe_tax_subtotal->_recalculate_tax_sub_total();
1187
-                }
1188
-            }
1189
-        }
1190
-    }
1191
-
1192
-
1193
-    /**
1194
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1195
-     * recalculate_taxes_and_total
1196
-     *
1197
-     * @return float
1198
-     * @throws EE_Error
1199
-     */
1200
-    public function get_total_tax()
1201
-    {
1202
-        $this->_recalculate_tax_sub_total();
1203
-        $total = 0;
1204
-        foreach ($this->tax_descendants() as $tax_line_item) {
1205
-            if ($tax_line_item instanceof EE_Line_Item) {
1206
-                $total += $tax_line_item->total();
1207
-            }
1208
-        }
1209
-        return $total;
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * Gets the total for all the items purchased only
1215
-     *
1216
-     * @return float
1217
-     * @throws EE_Error
1218
-     */
1219
-    public function get_items_total()
1220
-    {
1221
-        //by default, let's make sure we're consistent with the existing line item
1222
-        if ($this->is_total()) {
1223
-            $pretax_subtotal_li = EEH_Line_Item::get_pre_tax_subtotal($this);
1224
-            if ($pretax_subtotal_li instanceof EE_Line_Item) {
1225
-                return $pretax_subtotal_li->total();
1226
-            }
1227
-        }
1228
-        $total = 0;
1229
-        foreach ($this->get_items() as $item) {
1230
-            if ($item instanceof EE_Line_Item) {
1231
-                $total += $item->total();
1232
-            }
1233
-        }
1234
-        return $total;
1235
-    }
1236
-
1237
-
1238
-    /**
1239
-     * Gets all the descendants (ie, children or children of children etc) that
1240
-     * are of the type 'tax'
1241
-     *
1242
-     * @return EE_Line_Item[]
1243
-     */
1244
-    public function tax_descendants()
1245
-    {
1246
-        return EEH_Line_Item::get_tax_descendants($this);
1247
-    }
1248
-
1249
-
1250
-    /**
1251
-     * Gets all the real items purchased which are children of this item
1252
-     *
1253
-     * @return EE_Line_Item[]
1254
-     */
1255
-    public function get_items()
1256
-    {
1257
-        return EEH_Line_Item::get_line_item_descendants($this);
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * Returns the amount taxable among this line item's children (or if it has no children,
1263
-     * how much of it is taxable). Does not recalculate totals or subtotals.
1264
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1265
-     * but there is a "Taxable" discount), returns 0.
1266
-     *
1267
-     * @return float
1268
-     * @throws EE_Error
1269
-     */
1270
-    public function taxable_total()
1271
-    {
1272
-        $total = 0;
1273
-        if ($this->children()) {
1274
-            foreach ($this->children() as $child_line_item) {
1275
-                if ($child_line_item->type() === EEM_Line_Item::type_line_item && $child_line_item->is_taxable()) {
1276
-                    //if it's a percent item, only take into account the percent
1277
-                    //that's taxable too (the taxable total so far)
1278
-                    if ($child_line_item->is_percent()) {
1279
-                        $total += ($total * $child_line_item->percent() / 100);
1280
-                    } else {
1281
-                        $total += $child_line_item->total();
1282
-                    }
1283
-                } elseif ($child_line_item->type() === EEM_Line_Item::type_sub_total) {
1284
-                    $total += $child_line_item->taxable_total();
1285
-                }
1286
-            }
1287
-        }
1288
-        return max($total, 0);
1289
-    }
1290
-
1291
-
1292
-    /**
1293
-     * Gets the transaction for this line item
1294
-     *
1295
-     * @return EE_Base_Class|EE_Transaction
1296
-     * @throws EE_Error
1297
-     */
1298
-    public function transaction()
1299
-    {
1300
-        return $this->get_first_related('Transaction');
1301
-    }
1302
-
1303
-
1304
-    /**
1305
-     * Saves this line item to the DB, and recursively saves its descendants.
1306
-     * Because there currently is no proper parent-child relation on the model,
1307
-     * save_this_and_cached() will NOT save the descendants.
1308
-     * Also sets the transaction on this line item and all its descendants before saving
1309
-     *
1310
-     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1311
-     * @return int count of items saved
1312
-     * @throws EE_Error
1313
-     */
1314
-    public function save_this_and_descendants_to_txn($txn_id = null)
1315
-    {
1316
-        $count = 0;
1317
-        if (!$txn_id) {
1318
-            $txn_id = $this->TXN_ID();
1319
-        }
1320
-        $this->set_TXN_ID($txn_id);
1321
-        $children = $this->children();
1322
-        $count += $this->save()
1323
-            ? 1
1324
-            : 0;
1325
-        foreach ($children as $child_line_item) {
1326
-            if ($child_line_item instanceof EE_Line_Item) {
1327
-                $child_line_item->set_parent_ID($this->ID());
1328
-                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1329
-            }
1330
-        }
1331
-        return $count;
1332
-    }
1333
-
1334
-
1335
-    /**
1336
-     * Saves this line item to the DB, and recursively saves its descendants.
1337
-     *
1338
-     * @return int count of items saved
1339
-     * @throws EE_Error
1340
-     */
1341
-    public function save_this_and_descendants()
1342
-    {
1343
-        $count = 0;
1344
-        $children = $this->children();
1345
-        $count += $this->save()
1346
-            ? 1
1347
-            : 0;
1348
-        foreach ($children as $child_line_item) {
1349
-            if ($child_line_item instanceof EE_Line_Item) {
1350
-                $child_line_item->set_parent_ID($this->ID());
1351
-                $count += $child_line_item->save_this_and_descendants();
1352
-            }
1353
-        }
1354
-        return $count;
1355
-    }
1356
-
1357
-
1358
-    /**
1359
-     * returns the cancellation line item if this item was cancelled
1360
-     *
1361
-     * @return EE_Line_Item[]
1362
-     * @throws InvalidArgumentException
1363
-     * @throws InvalidInterfaceException
1364
-     * @throws InvalidDataTypeException
1365
-     * @throws ReflectionException
1366
-     * @throws EE_Error
1367
-     */
1368
-    public function get_cancellations()
1369
-    {
1370
-        EE_Registry::instance()->load_helper('Line_Item');
1371
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1372
-    }
1373
-
1374
-
1375
-    /**
1376
-     * If this item has an ID, then this saves it again to update the db
1377
-     *
1378
-     * @return int count of items saved
1379
-     * @throws EE_Error
1380
-     */
1381
-    public function maybe_save()
1382
-    {
1383
-        if ($this->ID()) {
1384
-            return $this->save();
1385
-        }
1386
-        return false;
1387
-    }
1388
-
1389
-
1390
-    /**
1391
-     * clears the cached children and parent from the line item
1392
-     *
1393
-     * @return void
1394
-     */
1395
-    public function clear_related_line_item_cache()
1396
-    {
1397
-        $this->_children = array();
1398
-        $this->_parent = null;
1399
-    }
1400
-
1401
-
1402
-    /**
1403
-     * @param bool $raw
1404
-     * @return int
1405
-     * @throws EE_Error
1406
-     */
1407
-    public function timestamp($raw = false)
1408
-    {
1409
-        return $raw
1410
-            ? $this->get_raw('LIN_timestamp')
1411
-            : $this->get('LIN_timestamp');
1412
-    }
1413
-
1414
-
1415
-
1416
-
1417
-    /************************* DEPRECATED *************************/
1418
-    /**
1419
-     * @deprecated 4.6.0
1420
-     * @param string $type one of the constants on EEM_Line_Item
1421
-     * @return EE_Line_Item[]
1422
-     */
1423
-    protected function _get_descendants_of_type($type)
1424
-    {
1425
-        EE_Error::doing_it_wrong(
1426
-            'EE_Line_Item::_get_descendants_of_type()',
1427
-            __('Method replaced with EEH_Line_Item::get_descendants_of_type()', 'event_espresso'), '4.6.0'
1428
-        );
1429
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1430
-    }
1431
-
1432
-
1433
-    /**
1434
-     * @deprecated 4.6.0
1435
-     * @param string $type like one of the EEM_Line_Item::type_*
1436
-     * @return EE_Line_Item
1437
-     */
1438
-    public function get_nearest_descendant_of_type($type)
1439
-    {
1440
-        EE_Error::doing_it_wrong(
1441
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1442
-            __('Method replaced with EEH_Line_Item::get_nearest_descendant_of_type()', 'event_espresso'), '4.6.0'
1443
-        );
1444
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1445
-    }
20
+	/**
21
+	 * for children line items (currently not a normal relation)
22
+	 *
23
+	 * @type EE_Line_Item[]
24
+	 */
25
+	protected $_children = array();
26
+
27
+	/**
28
+	 * for the parent line item
29
+	 *
30
+	 * @var EE_Line_Item
31
+	 */
32
+	protected $_parent;
33
+
34
+
35
+	/**
36
+	 *
37
+	 * @param array $props_n_values incoming values
38
+	 * @param string $timezone incoming timezone (if not set the timezone set for the website will be
39
+	 *                                        used.)
40
+	 * @param array $date_formats incoming date_formats in an array where the first value is the
41
+	 *                                        date_format and the second value is the time format
42
+	 * @return EE_Line_Item
43
+	 * @throws EE_Error
44
+	 */
45
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
46
+	{
47
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
48
+		return $has_object
49
+			? $has_object
50
+			: new self($props_n_values, false, $timezone);
51
+	}
52
+
53
+
54
+	/**
55
+	 * @param array $props_n_values incoming values from the database
56
+	 * @param string $timezone incoming timezone as set by the model.  If not set the timezone for
57
+	 *                                the website will be used.
58
+	 * @return EE_Line_Item
59
+	 * @throws EE_Error
60
+	 */
61
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
62
+	{
63
+		return new self($props_n_values, true, $timezone);
64
+	}
65
+
66
+
67
+	/**
68
+	 * Adds some defaults if they're not specified
69
+	 *
70
+	 * @param array $fieldValues
71
+	 * @param bool $bydb
72
+	 * @param string $timezone
73
+	 * @throws EE_Error
74
+	 */
75
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
76
+	{
77
+		parent::__construct($fieldValues, $bydb, $timezone);
78
+		if (!$this->get('LIN_code')) {
79
+			$this->set_code($this->generate_code());
80
+		}
81
+	}
82
+
83
+
84
+	/**
85
+	 * Gets ID
86
+	 *
87
+	 * @return int
88
+	 * @throws EE_Error
89
+	 */
90
+	public function ID()
91
+	{
92
+		return $this->get('LIN_ID');
93
+	}
94
+
95
+
96
+	/**
97
+	 * Gets TXN_ID
98
+	 *
99
+	 * @return int
100
+	 * @throws EE_Error
101
+	 */
102
+	public function TXN_ID()
103
+	{
104
+		return $this->get('TXN_ID');
105
+	}
106
+
107
+
108
+	/**
109
+	 * Sets TXN_ID
110
+	 *
111
+	 * @param int $TXN_ID
112
+	 * @throws EE_Error
113
+	 */
114
+	public function set_TXN_ID($TXN_ID)
115
+	{
116
+		$this->set('TXN_ID', $TXN_ID);
117
+	}
118
+
119
+
120
+	/**
121
+	 * Gets name
122
+	 *
123
+	 * @return string
124
+	 * @throws EE_Error
125
+	 */
126
+	public function name()
127
+	{
128
+		$name = $this->get('LIN_name');
129
+		if (!$name) {
130
+			$name = ucwords(str_replace('-', ' ', $this->type()));
131
+		}
132
+		return $name;
133
+	}
134
+
135
+
136
+	/**
137
+	 * Sets name
138
+	 *
139
+	 * @param string $name
140
+	 * @throws EE_Error
141
+	 */
142
+	public function set_name($name)
143
+	{
144
+		$this->set('LIN_name', $name);
145
+	}
146
+
147
+
148
+	/**
149
+	 * Gets desc
150
+	 *
151
+	 * @return string
152
+	 * @throws EE_Error
153
+	 */
154
+	public function desc()
155
+	{
156
+		return $this->get('LIN_desc');
157
+	}
158
+
159
+
160
+	/**
161
+	 * Sets desc
162
+	 *
163
+	 * @param string $desc
164
+	 * @throws EE_Error
165
+	 */
166
+	public function set_desc($desc)
167
+	{
168
+		$this->set('LIN_desc', $desc);
169
+	}
170
+
171
+
172
+	/**
173
+	 * Gets quantity
174
+	 *
175
+	 * @return int
176
+	 * @throws EE_Error
177
+	 */
178
+	public function quantity()
179
+	{
180
+		return $this->get('LIN_quantity');
181
+	}
182
+
183
+
184
+	/**
185
+	 * Sets quantity
186
+	 *
187
+	 * @param int $quantity
188
+	 * @throws EE_Error
189
+	 */
190
+	public function set_quantity($quantity)
191
+	{
192
+		$this->set('LIN_quantity', max($quantity, 0));
193
+	}
194
+
195
+
196
+	/**
197
+	 * Gets item_id
198
+	 *
199
+	 * @return string
200
+	 * @throws EE_Error
201
+	 */
202
+	public function OBJ_ID()
203
+	{
204
+		return $this->get('OBJ_ID');
205
+	}
206
+
207
+
208
+	/**
209
+	 * Sets item_id
210
+	 *
211
+	 * @param string $item_id
212
+	 * @throws EE_Error
213
+	 */
214
+	public function set_OBJ_ID($item_id)
215
+	{
216
+		$this->set('OBJ_ID', $item_id);
217
+	}
218
+
219
+
220
+	/**
221
+	 * Gets item_type
222
+	 *
223
+	 * @return string
224
+	 * @throws EE_Error
225
+	 */
226
+	public function OBJ_type()
227
+	{
228
+		return $this->get('OBJ_type');
229
+	}
230
+
231
+
232
+	/**
233
+	 * Gets item_type
234
+	 *
235
+	 * @return string
236
+	 * @throws EE_Error
237
+	 */
238
+	public function OBJ_type_i18n()
239
+	{
240
+		$obj_type = $this->OBJ_type();
241
+		switch ($obj_type) {
242
+			case 'Event':
243
+				$obj_type = __('Event', 'event_espresso');
244
+				break;
245
+			case 'Price':
246
+				$obj_type = __('Price', 'event_espresso');
247
+				break;
248
+			case 'Promotion':
249
+				$obj_type = __('Promotion', 'event_espresso');
250
+				break;
251
+			case 'Ticket':
252
+				$obj_type = __('Ticket', 'event_espresso');
253
+				break;
254
+			case 'Transaction':
255
+				$obj_type = __('Transaction', 'event_espresso');
256
+				break;
257
+		}
258
+		return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
259
+	}
260
+
261
+
262
+	/**
263
+	 * Sets item_type
264
+	 *
265
+	 * @param string $OBJ_type
266
+	 * @throws EE_Error
267
+	 */
268
+	public function set_OBJ_type($OBJ_type)
269
+	{
270
+		$this->set('OBJ_type', $OBJ_type);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Gets unit_price
276
+	 *
277
+	 * @return float
278
+	 * @throws EE_Error
279
+	 */
280
+	public function unit_price()
281
+	{
282
+		return $this->get('LIN_unit_price');
283
+	}
284
+
285
+
286
+	/**
287
+	 * Sets unit_price
288
+	 *
289
+	 * @param float $unit_price
290
+	 * @throws EE_Error
291
+	 */
292
+	public function set_unit_price($unit_price)
293
+	{
294
+		$this->set('LIN_unit_price', $unit_price);
295
+	}
296
+
297
+
298
+	/**
299
+	 * Checks if this item is a percentage modifier or not
300
+	 *
301
+	 * @return boolean
302
+	 * @throws EE_Error
303
+	 */
304
+	public function is_percent()
305
+	{
306
+		if ($this->is_tax_sub_total()) {
307
+			//tax subtotals HAVE a percent on them, that percentage only applies
308
+			//to taxable items, so its' an exception. Treat it like a flat line item
309
+			return false;
310
+		}
311
+		$unit_price = abs($this->get('LIN_unit_price'));
312
+		$percent = abs($this->get('LIN_percent'));
313
+		if ($unit_price < .001 && $percent) {
314
+			return true;
315
+		}
316
+		if ($unit_price >= .001 && !$percent) {
317
+			return false;
318
+		}
319
+		if ($unit_price >= .001 && $percent) {
320
+			throw new EE_Error(
321
+				sprintf(
322
+					esc_html__('A Line Item can not have a unit price of (%s) AND a percent (%s)!', 'event_espresso'),
323
+					$unit_price, $percent
324
+				)
325
+			);
326
+		}
327
+		// if they're both 0, assume its not a percent item
328
+		return false;
329
+	}
330
+
331
+
332
+	/**
333
+	 * Gets percent (between 100-.001)
334
+	 *
335
+	 * @return float
336
+	 * @throws EE_Error
337
+	 */
338
+	public function percent()
339
+	{
340
+		return $this->get('LIN_percent');
341
+	}
342
+
343
+
344
+	/**
345
+	 * Sets percent (between 100-0.01)
346
+	 *
347
+	 * @param float $percent
348
+	 * @throws EE_Error
349
+	 */
350
+	public function set_percent($percent)
351
+	{
352
+		$this->set('LIN_percent', $percent);
353
+	}
354
+
355
+
356
+	/**
357
+	 * Gets total
358
+	 *
359
+	 * @return float
360
+	 * @throws EE_Error
361
+	 */
362
+	public function total()
363
+	{
364
+		return $this->get('LIN_total');
365
+	}
366
+
367
+
368
+	/**
369
+	 * Sets total
370
+	 *
371
+	 * @param float $total
372
+	 * @throws EE_Error
373
+	 */
374
+	public function set_total($total)
375
+	{
376
+		$this->set('LIN_total', $total);
377
+	}
378
+
379
+
380
+	/**
381
+	 * Gets order
382
+	 *
383
+	 * @return int
384
+	 * @throws EE_Error
385
+	 */
386
+	public function order()
387
+	{
388
+		return $this->get('LIN_order');
389
+	}
390
+
391
+
392
+	/**
393
+	 * Sets order
394
+	 *
395
+	 * @param int $order
396
+	 * @throws EE_Error
397
+	 */
398
+	public function set_order($order)
399
+	{
400
+		$this->set('LIN_order', $order);
401
+	}
402
+
403
+
404
+	/**
405
+	 * Gets parent
406
+	 *
407
+	 * @return int
408
+	 * @throws EE_Error
409
+	 */
410
+	public function parent_ID()
411
+	{
412
+		return $this->get('LIN_parent');
413
+	}
414
+
415
+
416
+	/**
417
+	 * Sets parent
418
+	 *
419
+	 * @param int $parent
420
+	 * @throws EE_Error
421
+	 */
422
+	public function set_parent_ID($parent)
423
+	{
424
+		$this->set('LIN_parent', $parent);
425
+	}
426
+
427
+
428
+	/**
429
+	 * Gets type
430
+	 *
431
+	 * @return string
432
+	 * @throws EE_Error
433
+	 */
434
+	public function type()
435
+	{
436
+		return $this->get('LIN_type');
437
+	}
438
+
439
+
440
+	/**
441
+	 * Sets type
442
+	 *
443
+	 * @param string $type
444
+	 * @throws EE_Error
445
+	 */
446
+	public function set_type($type)
447
+	{
448
+		$this->set('LIN_type', $type);
449
+	}
450
+
451
+
452
+	/**
453
+	 * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
454
+	 * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
455
+	 * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
456
+	 * or indirectly by `EE_Line_item::add_child_line_item()`)
457
+	 *
458
+	 * @return EE_Base_Class|EE_Line_Item
459
+	 * @throws EE_Error
460
+	 */
461
+	public function parent()
462
+	{
463
+		return $this->ID()
464
+			? $this->get_model()->get_one_by_ID($this->parent_ID())
465
+			: $this->_parent;
466
+	}
467
+
468
+
469
+	/**
470
+	 * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
471
+	 *
472
+	 * @return EE_Base_Class[]|EE_Line_Item[]
473
+	 * @throws EE_Error
474
+	 */
475
+	public function children()
476
+	{
477
+		if ($this->ID()) {
478
+			return $this->get_model()->get_all(
479
+				array(
480
+					array('LIN_parent' => $this->ID()),
481
+					'order_by' => array('LIN_order' => 'ASC'),
482
+				)
483
+			);
484
+		}
485
+		if (!is_array($this->_children)) {
486
+			$this->_children = array();
487
+		}
488
+		return $this->_children;
489
+	}
490
+
491
+
492
+	/**
493
+	 * Gets code
494
+	 *
495
+	 * @return string
496
+	 * @throws EE_Error
497
+	 */
498
+	public function code()
499
+	{
500
+		return $this->get('LIN_code');
501
+	}
502
+
503
+
504
+	/**
505
+	 * Sets code
506
+	 *
507
+	 * @param string $code
508
+	 * @throws EE_Error
509
+	 */
510
+	public function set_code($code)
511
+	{
512
+		$this->set('LIN_code', $code);
513
+	}
514
+
515
+
516
+	/**
517
+	 * Gets is_taxable
518
+	 *
519
+	 * @return boolean
520
+	 * @throws EE_Error
521
+	 */
522
+	public function is_taxable()
523
+	{
524
+		return $this->get('LIN_is_taxable');
525
+	}
526
+
527
+
528
+	/**
529
+	 * Sets is_taxable
530
+	 *
531
+	 * @param boolean $is_taxable
532
+	 * @throws EE_Error
533
+	 */
534
+	public function set_is_taxable($is_taxable)
535
+	{
536
+		$this->set('LIN_is_taxable', $is_taxable);
537
+	}
538
+
539
+
540
+	/**
541
+	 * Gets the object that this model-joins-to.
542
+	 * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
543
+	 * EEM_Promotion_Object
544
+	 *
545
+	 *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
546
+	 *
547
+	 * @return EE_Base_Class | NULL
548
+	 * @throws EE_Error
549
+	 */
550
+	public function get_object()
551
+	{
552
+		$model_name_of_related_obj = $this->OBJ_type();
553
+		return $this->get_model()->has_relation($model_name_of_related_obj)
554
+			? $this->get_first_related($model_name_of_related_obj)
555
+			: null;
556
+	}
557
+
558
+
559
+	/**
560
+	 * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
561
+	 * (IE, if this line item is for a price or something else, will return NULL)
562
+	 *
563
+	 * @param array $query_params
564
+	 * @return EE_Base_Class|EE_Ticket
565
+	 * @throws EE_Error
566
+	 */
567
+	public function ticket($query_params = array())
568
+	{
569
+		//we're going to assume that when this method is called we always want to receive the attached ticket EVEN if that ticket is archived.  This can be overridden via the incoming $query_params argument
570
+		$remove_defaults = array('default_where_conditions' => 'none');
571
+		$query_params = array_merge($remove_defaults, $query_params);
572
+		return $this->get_first_related('Ticket', $query_params);
573
+	}
574
+
575
+
576
+	/**
577
+	 * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
578
+	 *
579
+	 * @return EE_Datetime | NULL
580
+	 * @throws EE_Error
581
+	 */
582
+	public function get_ticket_datetime()
583
+	{
584
+		if ($this->OBJ_type() === 'Ticket') {
585
+			$ticket = $this->ticket();
586
+			if ($ticket instanceof EE_Ticket) {
587
+				$datetime = $ticket->first_datetime();
588
+				if ($datetime instanceof EE_Datetime) {
589
+					return $datetime;
590
+				}
591
+			}
592
+		}
593
+		return null;
594
+	}
595
+
596
+
597
+	/**
598
+	 * Gets the event's name that's related to the ticket, if this is for
599
+	 * a ticket
600
+	 *
601
+	 * @return string
602
+	 * @throws EE_Error
603
+	 */
604
+	public function ticket_event_name()
605
+	{
606
+		$event_name = esc_html__('Unknown', 'event_espresso');
607
+		$event = $this->ticket_event();
608
+		if ($event instanceof EE_Event) {
609
+			$event_name = $event->name();
610
+		}
611
+		return $event_name;
612
+	}
613
+
614
+
615
+	/**
616
+	 * Gets the event that's related to the ticket, if this line item represents a ticket.
617
+	 *
618
+	 * @return EE_Event|null
619
+	 * @throws EE_Error
620
+	 */
621
+	public function ticket_event()
622
+	{
623
+		$event = null;
624
+		$ticket = $this->ticket();
625
+		if ($ticket instanceof EE_Ticket) {
626
+			$datetime = $ticket->first_datetime();
627
+			if ($datetime instanceof EE_Datetime) {
628
+				$event = $datetime->event();
629
+			}
630
+		}
631
+		return $event;
632
+	}
633
+
634
+
635
+	/**
636
+	 * Gets the first datetime for this lien item, assuming it's for a ticket
637
+	 *
638
+	 * @param string $date_format
639
+	 * @param string $time_format
640
+	 * @return string
641
+	 * @throws EE_Error
642
+	 */
643
+	public function ticket_datetime_start($date_format = '', $time_format = '')
644
+	{
645
+		$first_datetime_string = esc_html__('Unknown', 'event_espresso');
646
+		$datetime = $this->get_ticket_datetime();
647
+		if ($datetime) {
648
+			$first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
649
+		}
650
+		return $first_datetime_string;
651
+	}
652
+
653
+
654
+	/**
655
+	 * Adds the line item as a child to this line item. If there is another child line
656
+	 * item with the same LIN_code, it is overwritten by this new one
657
+	 *
658
+	 * @param EEI_Line_Item $line_item
659
+	 * @param bool $set_order
660
+	 * @return bool success
661
+	 * @throws EE_Error
662
+	 */
663
+	public function add_child_line_item(EEI_Line_Item $line_item, $set_order = true)
664
+	{
665
+		// should we calculate the LIN_order for this line item ?
666
+		if ($set_order || $line_item->order() === null) {
667
+			$line_item->set_order(count($this->children()));
668
+		}
669
+		if ($this->ID()) {
670
+			//check for any duplicate line items (with the same code), if so, this replaces it
671
+			$line_item_with_same_code = $this->get_child_line_item($line_item->code());
672
+			if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
673
+				$this->delete_child_line_item($line_item_with_same_code->code());
674
+			}
675
+			$line_item->set_parent_ID($this->ID());
676
+			if ($this->TXN_ID()) {
677
+				$line_item->set_TXN_ID($this->TXN_ID());
678
+			}
679
+			return $line_item->save();
680
+		}
681
+		$this->_children[$line_item->code()] = $line_item;
682
+		if ($line_item->parent() !== $this) {
683
+			$line_item->set_parent($this);
684
+		}
685
+		return true;
686
+	}
687
+
688
+
689
+	/**
690
+	 * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
691
+	 * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
692
+	 * However, if this line item is NOT saved to the DB, this just caches the parent on
693
+	 * the EE_Line_Item::_parent property.
694
+	 *
695
+	 * @param EE_Line_Item $line_item
696
+	 * @throws EE_Error
697
+	 */
698
+	public function set_parent($line_item)
699
+	{
700
+		if ($this->ID()) {
701
+			if (!$line_item->ID()) {
702
+				$line_item->save();
703
+			}
704
+			$this->set_parent_ID($line_item->ID());
705
+			$this->save();
706
+		} else {
707
+			$this->_parent = $line_item;
708
+			$this->set_parent_ID($line_item->ID());
709
+		}
710
+	}
711
+
712
+
713
+	/**
714
+	 * Gets the child line item as specified by its code. Because this returns an object (by reference)
715
+	 * you can modify this child line item and the parent (this object) can know about them
716
+	 * because it also has a reference to that line item
717
+	 *
718
+	 * @param string $code
719
+	 * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
720
+	 * @throws EE_Error
721
+	 */
722
+	public function get_child_line_item($code)
723
+	{
724
+		if ($this->ID()) {
725
+			return $this->get_model()->get_one(
726
+				array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
727
+			);
728
+		}
729
+		return isset($this->_children[$code])
730
+			? $this->_children[$code]
731
+			: null;
732
+	}
733
+
734
+
735
+	/**
736
+	 * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
737
+	 * cached on it)
738
+	 *
739
+	 * @return int
740
+	 * @throws EE_Error
741
+	 */
742
+	public function delete_children_line_items()
743
+	{
744
+		if ($this->ID()) {
745
+			return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
746
+		}
747
+		$count = count($this->_children);
748
+		$this->_children = array();
749
+		return $count;
750
+	}
751
+
752
+
753
+	/**
754
+	 * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
755
+	 * HAS NOT been saved to the DB, removes the child line item with index $code.
756
+	 * Also searches through the child's children for a matching line item. However, once a line item has been found
757
+	 * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
758
+	 * deleted)
759
+	 *
760
+	 * @param string $code
761
+	 * @param bool $stop_search_once_found
762
+	 * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
763
+	 *             the DB yet)
764
+	 * @throws EE_Error
765
+	 */
766
+	public function delete_child_line_item($code, $stop_search_once_found = true)
767
+	{
768
+		if ($this->ID()) {
769
+			$items_deleted = 0;
770
+			if ($this->code() === $code) {
771
+				$items_deleted += EEH_Line_Item::delete_all_child_items($this);
772
+				$items_deleted += (int)$this->delete();
773
+				if ($stop_search_once_found) {
774
+					return $items_deleted;
775
+				}
776
+			}
777
+			foreach ($this->children() as $child_line_item) {
778
+				$items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
779
+			}
780
+			return $items_deleted;
781
+		}
782
+		if (isset($this->_children[$code])) {
783
+			unset($this->_children[$code]);
784
+			return 1;
785
+		}
786
+		return 0;
787
+	}
788
+
789
+
790
+	/**
791
+	 * If this line item is in the database, is of the type subtotal, and
792
+	 * has no children, why do we have it? It should be deleted so this function
793
+	 * does that
794
+	 *
795
+	 * @return boolean
796
+	 * @throws EE_Error
797
+	 */
798
+	public function delete_if_childless_subtotal()
799
+	{
800
+		if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && !$this->children()) {
801
+			return $this->delete();
802
+		}
803
+		return false;
804
+	}
805
+
806
+
807
+	/**
808
+	 * Creates a code and returns a string. doesn't assign the code to this model object
809
+	 *
810
+	 * @return string
811
+	 * @throws EE_Error
812
+	 */
813
+	public function generate_code()
814
+	{
815
+		// each line item in the cart requires a unique identifier
816
+		return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
817
+	}
818
+
819
+
820
+	/**
821
+	 * @return bool
822
+	 * @throws EE_Error
823
+	 */
824
+	public function is_tax()
825
+	{
826
+		return $this->type() === EEM_Line_Item::type_tax;
827
+	}
828
+
829
+
830
+	/**
831
+	 * @return bool
832
+	 * @throws EE_Error
833
+	 */
834
+	public function is_tax_sub_total()
835
+	{
836
+		return $this->type() === EEM_Line_Item::type_tax_sub_total;
837
+	}
838
+
839
+
840
+	/**
841
+	 * @return bool
842
+	 * @throws EE_Error
843
+	 */
844
+	public function is_line_item()
845
+	{
846
+		return $this->type() === EEM_Line_Item::type_line_item;
847
+	}
848
+
849
+
850
+	/**
851
+	 * @return bool
852
+	 * @throws EE_Error
853
+	 */
854
+	public function is_sub_line_item()
855
+	{
856
+		return $this->type() === EEM_Line_Item::type_sub_line_item;
857
+	}
858
+
859
+
860
+	/**
861
+	 * @return bool
862
+	 * @throws EE_Error
863
+	 */
864
+	public function is_sub_total()
865
+	{
866
+		return $this->type() === EEM_Line_Item::type_sub_total;
867
+	}
868
+
869
+
870
+	/**
871
+	 * Whether or not this line item is a cancellation line item
872
+	 *
873
+	 * @return boolean
874
+	 * @throws EE_Error
875
+	 */
876
+	public function is_cancellation()
877
+	{
878
+		return EEM_Line_Item::type_cancellation === $this->type();
879
+	}
880
+
881
+
882
+	/**
883
+	 * @return bool
884
+	 * @throws EE_Error
885
+	 */
886
+	public function is_total()
887
+	{
888
+		return $this->type() === EEM_Line_Item::type_total;
889
+	}
890
+
891
+
892
+	/**
893
+	 * @return bool
894
+	 * @throws EE_Error
895
+	 */
896
+	public function is_cancelled()
897
+	{
898
+		return $this->type() === EEM_Line_Item::type_cancellation;
899
+	}
900
+
901
+
902
+	/**
903
+	 * @return string like '2, 004.00', formatted according to the localized currency
904
+	 * @throws EE_Error
905
+	 */
906
+	public function unit_price_no_code()
907
+	{
908
+		return $this->get_pretty('LIN_unit_price', 'no_currency_code');
909
+	}
910
+
911
+
912
+	/**
913
+	 * @return string like '2, 004.00', formatted according to the localized currency
914
+	 * @throws EE_Error
915
+	 */
916
+	public function total_no_code()
917
+	{
918
+		return $this->get_pretty('LIN_total', 'no_currency_code');
919
+	}
920
+
921
+
922
+	/**
923
+	 * Gets the final total on this item, taking taxes into account.
924
+	 * Has the side-effect of setting the sub-total as it was just calculated.
925
+	 * If this is used on a grand-total line item, also updates the transaction's
926
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
927
+	 * want to change a persistable transaction with info from a non-persistent line item)
928
+	 *
929
+	 * @return float
930
+	 * @throws EE_Error
931
+	 * @throws InvalidArgumentException
932
+	 * @throws InvalidInterfaceException
933
+	 * @throws InvalidDataTypeException
934
+	 */
935
+	public function recalculate_total_including_taxes()
936
+	{
937
+		$pre_tax_total = $this->recalculate_pre_tax_total();
938
+		$tax_total = $this->recalculate_taxes_and_tax_total();
939
+		$total = $pre_tax_total + $tax_total;
940
+		// no negative totals plz
941
+		$total = max($total, 0);
942
+		$this->set_total($total);
943
+		//only update the related transaction's total
944
+		//if we intend to save this line item and its a grand total
945
+		if (
946
+			$this->allow_persist() && $this->type() === EEM_Line_Item::type_total
947
+			&& $this->transaction()
948
+			instanceof
949
+			EE_Transaction
950
+		) {
951
+			$this->transaction()->set_total($total);
952
+			if ($this->transaction()->ID()) {
953
+				$this->transaction()->save();
954
+			}
955
+		}
956
+		$this->maybe_save();
957
+		return $total;
958
+	}
959
+
960
+
961
+	/**
962
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
963
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
964
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
965
+	 * when this is called on the grand total
966
+	 *
967
+	 * @return float
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidInterfaceException
970
+	 * @throws InvalidDataTypeException
971
+	 * @throws EE_Error
972
+	 */
973
+	public function recalculate_pre_tax_total()
974
+	{
975
+		$total = 0;
976
+		$my_children = $this->children();
977
+		$has_children = !empty($my_children);
978
+		if ($has_children && $this->is_line_item()) {
979
+			$total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
980
+		} elseif (!$has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
981
+			$total = $this->unit_price() * $this->quantity();
982
+		} elseif ($this->is_sub_total() || $this->is_total()) {
983
+			$total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
984
+		} elseif ($this->is_tax_sub_total() || $this->is_tax() || $this->is_cancelled()) {
985
+			// completely ignore tax totals, tax sub-totals, and cancelled line items, when calculating the pre-tax-total
986
+			return 0;
987
+		}
988
+		// ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
989
+		if (
990
+			!$this->is_line_item() && !$this->is_sub_line_item() && !$this->is_cancellation()
991
+		) {
992
+			if ($this->OBJ_type() !== 'Event') {
993
+				$this->set_quantity(1);
994
+			}
995
+			if (!$this->is_percent()) {
996
+				$this->set_unit_price($total);
997
+			}
998
+		}
999
+		//we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1000
+		//so it ought to be
1001
+		if (!$this->is_total()) {
1002
+			$this->set_total($total);
1003
+			//if not a percent line item, make sure we keep the unit price in sync
1004
+			if (
1005
+				$has_children
1006
+				&& $this->is_line_item()
1007
+				&& !$this->is_percent()
1008
+			) {
1009
+				if ($this->quantity() === 0) {
1010
+					$new_unit_price = 0;
1011
+				} else {
1012
+					$new_unit_price = $this->total() / $this->quantity();
1013
+				}
1014
+				$this->set_unit_price($new_unit_price);
1015
+			}
1016
+			$this->maybe_save();
1017
+		}
1018
+		return $total;
1019
+	}
1020
+
1021
+
1022
+	/**
1023
+	 * Calculates the pretax total when this line item is a subtotal or total line item.
1024
+	 * Basically does a sum-then-round approach (ie, any percent line item that are children
1025
+	 * will calculate their total based on the un-rounded total we're working with so far, and
1026
+	 * THEN round the result; instead of rounding as we go like with sub-line-items)
1027
+	 *
1028
+	 * @param float $calculated_total_so_far
1029
+	 * @param EE_Line_Item[] $my_children
1030
+	 * @return float
1031
+	 * @throws InvalidArgumentException
1032
+	 * @throws InvalidInterfaceException
1033
+	 * @throws InvalidDataTypeException
1034
+	 * @throws EE_Error
1035
+	 */
1036
+	protected function _recalculate_pretax_total_for_subtotal($calculated_total_so_far, $my_children = null)
1037
+	{
1038
+		if ($my_children === null) {
1039
+			$my_children = $this->children();
1040
+		}
1041
+		$subtotal_quantity = 0;
1042
+		//get the total of all its children
1043
+		foreach ($my_children as $child_line_item) {
1044
+			if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1045
+				// percentage line items are based on total so far
1046
+				if ($child_line_item->is_percent()) {
1047
+					//round as we go so that the line items add up ok
1048
+					$percent_total = round(
1049
+						$calculated_total_so_far * $child_line_item->percent() / 100,
1050
+						EE_Registry::instance()->CFG->currency->dec_plc
1051
+					);
1052
+					$child_line_item->set_total($percent_total);
1053
+					//so far all percent line items should have a quantity of 1
1054
+					//(ie, no double percent discounts. Although that might be requested someday)
1055
+					$child_line_item->set_quantity(1);
1056
+					$child_line_item->maybe_save();
1057
+					$calculated_total_so_far += $percent_total;
1058
+				} else {
1059
+					//verify flat sub-line-item quantities match their parent
1060
+					if ($child_line_item->is_sub_line_item()) {
1061
+						$child_line_item->set_quantity($this->quantity());
1062
+					}
1063
+					$calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1064
+					$subtotal_quantity += $child_line_item->quantity();
1065
+				}
1066
+			}
1067
+		}
1068
+		if ($this->is_sub_total()) {
1069
+			// no negative totals plz
1070
+			$calculated_total_so_far = max($calculated_total_so_far, 0);
1071
+			$subtotal_quantity = $subtotal_quantity > 0 ? 1 : 0;
1072
+			$this->set_quantity($subtotal_quantity);
1073
+			$this->maybe_save();
1074
+		}
1075
+		return $calculated_total_so_far;
1076
+	}
1077
+
1078
+
1079
+	/**
1080
+	 * Calculates the pretax total for a normal line item, in a round-then-sum approach
1081
+	 * (where each sub-line-item is applied to the base price for the line item
1082
+	 * and the result is immediately rounded, rather than summing all the sub-line-items
1083
+	 * then rounding, like we do when recalculating pretax totals on totals and subtotals).
1084
+	 *
1085
+	 * @param float $calculated_total_so_far
1086
+	 * @param EE_Line_Item[] $my_children
1087
+	 * @return float
1088
+	 * @throws InvalidArgumentException
1089
+	 * @throws InvalidInterfaceException
1090
+	 * @throws InvalidDataTypeException
1091
+	 * @throws EE_Error
1092
+	 */
1093
+	protected function _recalculate_pretax_total_for_line_item($calculated_total_so_far, $my_children = null)
1094
+	{
1095
+		if ($my_children === null) {
1096
+			$my_children = $this->children();
1097
+		}
1098
+		//we need to keep track of the running total for a single item,
1099
+		//because we need to round as we go
1100
+		$unit_price_for_total = 0;
1101
+		$quantity_for_total = 1;
1102
+		//get the total of all its children
1103
+		foreach ($my_children as $child_line_item) {
1104
+			if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1105
+				if ($child_line_item->is_percent()) {
1106
+					//it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1107
+					//not total multiplied by percent, because that ignores rounding along-the-way
1108
+					$percent_unit_price = round(
1109
+						$unit_price_for_total * $child_line_item->percent() / 100,
1110
+						EE_Registry::instance()->CFG->currency->dec_plc
1111
+					);
1112
+					$percent_total = $percent_unit_price * $quantity_for_total;
1113
+					$child_line_item->set_total($percent_total);
1114
+					//so far all percent line items should have a quantity of 1
1115
+					//(ie, no double percent discounts. Although that might be requested someday)
1116
+					$child_line_item->set_quantity(1);
1117
+					$child_line_item->maybe_save();
1118
+					$calculated_total_so_far += $percent_total;
1119
+					$unit_price_for_total += $percent_unit_price;
1120
+				} else {
1121
+					//verify flat sub-line-item quantities match their parent
1122
+					if ($child_line_item->is_sub_line_item()) {
1123
+						$child_line_item->set_quantity($this->quantity());
1124
+					}
1125
+					$quantity_for_total = $child_line_item->quantity();
1126
+					$calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1127
+					$unit_price_for_total += $child_line_item->unit_price();
1128
+				}
1129
+			}
1130
+		}
1131
+		return $calculated_total_so_far;
1132
+	}
1133
+
1134
+
1135
+	/**
1136
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1137
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1138
+	 * and tax sub-total if already in the DB
1139
+	 *
1140
+	 * @return float
1141
+	 * @throws EE_Error
1142
+	 */
1143
+	public function recalculate_taxes_and_tax_total()
1144
+	{
1145
+		//get all taxes
1146
+		$taxes = $this->tax_descendants();
1147
+		//calculate the pretax total
1148
+		$taxable_total = $this->taxable_total();
1149
+		$tax_total = 0;
1150
+		foreach ($taxes as $tax) {
1151
+			$total_on_this_tax = $taxable_total * $tax->percent() / 100;
1152
+			//remember the total on this line item
1153
+			$tax->set_total($total_on_this_tax);
1154
+			$tax->maybe_save();
1155
+			$tax_total += $tax->total();
1156
+		}
1157
+		$this->_recalculate_tax_sub_total();
1158
+		return $tax_total;
1159
+	}
1160
+
1161
+
1162
+	/**
1163
+	 * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
1164
+	 *
1165
+	 * @return void
1166
+	 * @throws EE_Error
1167
+	 */
1168
+	private function _recalculate_tax_sub_total()
1169
+	{
1170
+		if ($this->is_tax_sub_total()) {
1171
+			$total = 0;
1172
+			$total_percent = 0;
1173
+			//simply loop through all its children (which should be taxes) and sum their total
1174
+			foreach ($this->children() as $child_tax) {
1175
+				if ($child_tax instanceof EE_Line_Item) {
1176
+					$total += $child_tax->total();
1177
+					$total_percent += $child_tax->percent();
1178
+				}
1179
+			}
1180
+			$this->set_total($total);
1181
+			$this->set_percent($total_percent);
1182
+			$this->maybe_save();
1183
+		} elseif ($this->is_total()) {
1184
+			foreach ($this->children() as $maybe_tax_subtotal) {
1185
+				if ($maybe_tax_subtotal instanceof EE_Line_Item) {
1186
+					$maybe_tax_subtotal->_recalculate_tax_sub_total();
1187
+				}
1188
+			}
1189
+		}
1190
+	}
1191
+
1192
+
1193
+	/**
1194
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
1195
+	 * recalculate_taxes_and_total
1196
+	 *
1197
+	 * @return float
1198
+	 * @throws EE_Error
1199
+	 */
1200
+	public function get_total_tax()
1201
+	{
1202
+		$this->_recalculate_tax_sub_total();
1203
+		$total = 0;
1204
+		foreach ($this->tax_descendants() as $tax_line_item) {
1205
+			if ($tax_line_item instanceof EE_Line_Item) {
1206
+				$total += $tax_line_item->total();
1207
+			}
1208
+		}
1209
+		return $total;
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * Gets the total for all the items purchased only
1215
+	 *
1216
+	 * @return float
1217
+	 * @throws EE_Error
1218
+	 */
1219
+	public function get_items_total()
1220
+	{
1221
+		//by default, let's make sure we're consistent with the existing line item
1222
+		if ($this->is_total()) {
1223
+			$pretax_subtotal_li = EEH_Line_Item::get_pre_tax_subtotal($this);
1224
+			if ($pretax_subtotal_li instanceof EE_Line_Item) {
1225
+				return $pretax_subtotal_li->total();
1226
+			}
1227
+		}
1228
+		$total = 0;
1229
+		foreach ($this->get_items() as $item) {
1230
+			if ($item instanceof EE_Line_Item) {
1231
+				$total += $item->total();
1232
+			}
1233
+		}
1234
+		return $total;
1235
+	}
1236
+
1237
+
1238
+	/**
1239
+	 * Gets all the descendants (ie, children or children of children etc) that
1240
+	 * are of the type 'tax'
1241
+	 *
1242
+	 * @return EE_Line_Item[]
1243
+	 */
1244
+	public function tax_descendants()
1245
+	{
1246
+		return EEH_Line_Item::get_tax_descendants($this);
1247
+	}
1248
+
1249
+
1250
+	/**
1251
+	 * Gets all the real items purchased which are children of this item
1252
+	 *
1253
+	 * @return EE_Line_Item[]
1254
+	 */
1255
+	public function get_items()
1256
+	{
1257
+		return EEH_Line_Item::get_line_item_descendants($this);
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * Returns the amount taxable among this line item's children (or if it has no children,
1263
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
1264
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
1265
+	 * but there is a "Taxable" discount), returns 0.
1266
+	 *
1267
+	 * @return float
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public function taxable_total()
1271
+	{
1272
+		$total = 0;
1273
+		if ($this->children()) {
1274
+			foreach ($this->children() as $child_line_item) {
1275
+				if ($child_line_item->type() === EEM_Line_Item::type_line_item && $child_line_item->is_taxable()) {
1276
+					//if it's a percent item, only take into account the percent
1277
+					//that's taxable too (the taxable total so far)
1278
+					if ($child_line_item->is_percent()) {
1279
+						$total += ($total * $child_line_item->percent() / 100);
1280
+					} else {
1281
+						$total += $child_line_item->total();
1282
+					}
1283
+				} elseif ($child_line_item->type() === EEM_Line_Item::type_sub_total) {
1284
+					$total += $child_line_item->taxable_total();
1285
+				}
1286
+			}
1287
+		}
1288
+		return max($total, 0);
1289
+	}
1290
+
1291
+
1292
+	/**
1293
+	 * Gets the transaction for this line item
1294
+	 *
1295
+	 * @return EE_Base_Class|EE_Transaction
1296
+	 * @throws EE_Error
1297
+	 */
1298
+	public function transaction()
1299
+	{
1300
+		return $this->get_first_related('Transaction');
1301
+	}
1302
+
1303
+
1304
+	/**
1305
+	 * Saves this line item to the DB, and recursively saves its descendants.
1306
+	 * Because there currently is no proper parent-child relation on the model,
1307
+	 * save_this_and_cached() will NOT save the descendants.
1308
+	 * Also sets the transaction on this line item and all its descendants before saving
1309
+	 *
1310
+	 * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1311
+	 * @return int count of items saved
1312
+	 * @throws EE_Error
1313
+	 */
1314
+	public function save_this_and_descendants_to_txn($txn_id = null)
1315
+	{
1316
+		$count = 0;
1317
+		if (!$txn_id) {
1318
+			$txn_id = $this->TXN_ID();
1319
+		}
1320
+		$this->set_TXN_ID($txn_id);
1321
+		$children = $this->children();
1322
+		$count += $this->save()
1323
+			? 1
1324
+			: 0;
1325
+		foreach ($children as $child_line_item) {
1326
+			if ($child_line_item instanceof EE_Line_Item) {
1327
+				$child_line_item->set_parent_ID($this->ID());
1328
+				$count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1329
+			}
1330
+		}
1331
+		return $count;
1332
+	}
1333
+
1334
+
1335
+	/**
1336
+	 * Saves this line item to the DB, and recursively saves its descendants.
1337
+	 *
1338
+	 * @return int count of items saved
1339
+	 * @throws EE_Error
1340
+	 */
1341
+	public function save_this_and_descendants()
1342
+	{
1343
+		$count = 0;
1344
+		$children = $this->children();
1345
+		$count += $this->save()
1346
+			? 1
1347
+			: 0;
1348
+		foreach ($children as $child_line_item) {
1349
+			if ($child_line_item instanceof EE_Line_Item) {
1350
+				$child_line_item->set_parent_ID($this->ID());
1351
+				$count += $child_line_item->save_this_and_descendants();
1352
+			}
1353
+		}
1354
+		return $count;
1355
+	}
1356
+
1357
+
1358
+	/**
1359
+	 * returns the cancellation line item if this item was cancelled
1360
+	 *
1361
+	 * @return EE_Line_Item[]
1362
+	 * @throws InvalidArgumentException
1363
+	 * @throws InvalidInterfaceException
1364
+	 * @throws InvalidDataTypeException
1365
+	 * @throws ReflectionException
1366
+	 * @throws EE_Error
1367
+	 */
1368
+	public function get_cancellations()
1369
+	{
1370
+		EE_Registry::instance()->load_helper('Line_Item');
1371
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1372
+	}
1373
+
1374
+
1375
+	/**
1376
+	 * If this item has an ID, then this saves it again to update the db
1377
+	 *
1378
+	 * @return int count of items saved
1379
+	 * @throws EE_Error
1380
+	 */
1381
+	public function maybe_save()
1382
+	{
1383
+		if ($this->ID()) {
1384
+			return $this->save();
1385
+		}
1386
+		return false;
1387
+	}
1388
+
1389
+
1390
+	/**
1391
+	 * clears the cached children and parent from the line item
1392
+	 *
1393
+	 * @return void
1394
+	 */
1395
+	public function clear_related_line_item_cache()
1396
+	{
1397
+		$this->_children = array();
1398
+		$this->_parent = null;
1399
+	}
1400
+
1401
+
1402
+	/**
1403
+	 * @param bool $raw
1404
+	 * @return int
1405
+	 * @throws EE_Error
1406
+	 */
1407
+	public function timestamp($raw = false)
1408
+	{
1409
+		return $raw
1410
+			? $this->get_raw('LIN_timestamp')
1411
+			: $this->get('LIN_timestamp');
1412
+	}
1413
+
1414
+
1415
+
1416
+
1417
+	/************************* DEPRECATED *************************/
1418
+	/**
1419
+	 * @deprecated 4.6.0
1420
+	 * @param string $type one of the constants on EEM_Line_Item
1421
+	 * @return EE_Line_Item[]
1422
+	 */
1423
+	protected function _get_descendants_of_type($type)
1424
+	{
1425
+		EE_Error::doing_it_wrong(
1426
+			'EE_Line_Item::_get_descendants_of_type()',
1427
+			__('Method replaced with EEH_Line_Item::get_descendants_of_type()', 'event_espresso'), '4.6.0'
1428
+		);
1429
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1430
+	}
1431
+
1432
+
1433
+	/**
1434
+	 * @deprecated 4.6.0
1435
+	 * @param string $type like one of the EEM_Line_Item::type_*
1436
+	 * @return EE_Line_Item
1437
+	 */
1438
+	public function get_nearest_descendant_of_type($type)
1439
+	{
1440
+		EE_Error::doing_it_wrong(
1441
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1442
+			__('Method replaced with EEH_Line_Item::get_nearest_descendant_of_type()', 'event_espresso'), '4.6.0'
1443
+		);
1444
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1445
+	}
1446 1446
 
1447 1447
 
1448 1448
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -75,7 +75,7 @@  discard block
 block discarded – undo
75 75
     protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
76 76
     {
77 77
         parent::__construct($fieldValues, $bydb, $timezone);
78
-        if (!$this->get('LIN_code')) {
78
+        if ( ! $this->get('LIN_code')) {
79 79
             $this->set_code($this->generate_code());
80 80
         }
81 81
     }
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
     public function name()
127 127
     {
128 128
         $name = $this->get('LIN_name');
129
-        if (!$name) {
129
+        if ( ! $name) {
130 130
             $name = ucwords(str_replace('-', ' ', $this->type()));
131 131
         }
132 132
         return $name;
@@ -313,7 +313,7 @@  discard block
 block discarded – undo
313 313
         if ($unit_price < .001 && $percent) {
314 314
             return true;
315 315
         }
316
-        if ($unit_price >= .001 && !$percent) {
316
+        if ($unit_price >= .001 && ! $percent) {
317 317
             return false;
318 318
         }
319 319
         if ($unit_price >= .001 && $percent) {
@@ -482,7 +482,7 @@  discard block
 block discarded – undo
482 482
                 )
483 483
             );
484 484
         }
485
-        if (!is_array($this->_children)) {
485
+        if ( ! is_array($this->_children)) {
486 486
             $this->_children = array();
487 487
         }
488 488
         return $this->_children;
@@ -698,7 +698,7 @@  discard block
 block discarded – undo
698 698
     public function set_parent($line_item)
699 699
     {
700 700
         if ($this->ID()) {
701
-            if (!$line_item->ID()) {
701
+            if ( ! $line_item->ID()) {
702 702
                 $line_item->save();
703 703
             }
704 704
             $this->set_parent_ID($line_item->ID());
@@ -769,7 +769,7 @@  discard block
 block discarded – undo
769 769
             $items_deleted = 0;
770 770
             if ($this->code() === $code) {
771 771
                 $items_deleted += EEH_Line_Item::delete_all_child_items($this);
772
-                $items_deleted += (int)$this->delete();
772
+                $items_deleted += (int) $this->delete();
773 773
                 if ($stop_search_once_found) {
774 774
                     return $items_deleted;
775 775
                 }
@@ -797,7 +797,7 @@  discard block
 block discarded – undo
797 797
      */
798 798
     public function delete_if_childless_subtotal()
799 799
     {
800
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && !$this->children()) {
800
+        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
801 801
             return $this->delete();
802 802
         }
803 803
         return false;
@@ -813,7 +813,7 @@  discard block
 block discarded – undo
813 813
     public function generate_code()
814 814
     {
815 815
         // each line item in the cart requires a unique identifier
816
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
816
+        return md5($this->get('OBJ_type').$this->get('OBJ_ID').microtime());
817 817
     }
818 818
 
819 819
 
@@ -974,10 +974,10 @@  discard block
 block discarded – undo
974 974
     {
975 975
         $total = 0;
976 976
         $my_children = $this->children();
977
-        $has_children = !empty($my_children);
977
+        $has_children = ! empty($my_children);
978 978
         if ($has_children && $this->is_line_item()) {
979 979
             $total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
980
-        } elseif (!$has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
980
+        } elseif ( ! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
981 981
             $total = $this->unit_price() * $this->quantity();
982 982
         } elseif ($this->is_sub_total() || $this->is_total()) {
983 983
             $total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
@@ -987,24 +987,24 @@  discard block
 block discarded – undo
987 987
         }
988 988
         // ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
989 989
         if (
990
-            !$this->is_line_item() && !$this->is_sub_line_item() && !$this->is_cancellation()
990
+            ! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()
991 991
         ) {
992 992
             if ($this->OBJ_type() !== 'Event') {
993 993
                 $this->set_quantity(1);
994 994
             }
995
-            if (!$this->is_percent()) {
995
+            if ( ! $this->is_percent()) {
996 996
                 $this->set_unit_price($total);
997 997
             }
998 998
         }
999 999
         //we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1000 1000
         //so it ought to be
1001
-        if (!$this->is_total()) {
1001
+        if ( ! $this->is_total()) {
1002 1002
             $this->set_total($total);
1003 1003
             //if not a percent line item, make sure we keep the unit price in sync
1004 1004
             if (
1005 1005
                 $has_children
1006 1006
                 && $this->is_line_item()
1007
-                && !$this->is_percent()
1007
+                && ! $this->is_percent()
1008 1008
             ) {
1009 1009
                 if ($this->quantity() === 0) {
1010 1010
                     $new_unit_price = 0;
@@ -1041,7 +1041,7 @@  discard block
 block discarded – undo
1041 1041
         $subtotal_quantity = 0;
1042 1042
         //get the total of all its children
1043 1043
         foreach ($my_children as $child_line_item) {
1044
-            if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1044
+            if ($child_line_item instanceof EE_Line_Item && ! $child_line_item->is_cancellation()) {
1045 1045
                 // percentage line items are based on total so far
1046 1046
                 if ($child_line_item->is_percent()) {
1047 1047
                     //round as we go so that the line items add up ok
@@ -1101,7 +1101,7 @@  discard block
 block discarded – undo
1101 1101
         $quantity_for_total = 1;
1102 1102
         //get the total of all its children
1103 1103
         foreach ($my_children as $child_line_item) {
1104
-            if ($child_line_item instanceof EE_Line_Item && !$child_line_item->is_cancellation()) {
1104
+            if ($child_line_item instanceof EE_Line_Item && ! $child_line_item->is_cancellation()) {
1105 1105
                 if ($child_line_item->is_percent()) {
1106 1106
                     //it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1107 1107
                     //not total multiplied by percent, because that ignores rounding along-the-way
@@ -1314,7 +1314,7 @@  discard block
 block discarded – undo
1314 1314
     public function save_this_and_descendants_to_txn($txn_id = null)
1315 1315
     {
1316 1316
         $count = 0;
1317
-        if (!$txn_id) {
1317
+        if ( ! $txn_id) {
1318 1318
             $txn_id = $this->TXN_ID();
1319 1319
         }
1320 1320
         $this->set_TXN_ID($txn_id);
Please login to merge, or discard this patch.
core/EE_Cart.core.php 1 patch
Indentation   +416 added lines, -416 removed lines patch added patch discarded remove patch
@@ -2,7 +2,7 @@  discard block
 block discarded – undo
2 2
 use EventEspresso\core\interfaces\ResettableInterface;
3 3
 
4 4
 if ( ! defined('EVENT_ESPRESSO_VERSION')) {
5
-    exit('No direct script access allowed');
5
+	exit('No direct script access allowed');
6 6
 }
7 7
 do_action('AHEE_log', __FILE__, __FUNCTION__, '');
8 8
 
@@ -23,421 +23,421 @@  discard block
 block discarded – undo
23 23
 class EE_Cart implements ResettableInterface
24 24
 {
25 25
 
26
-    /**
27
-     * instance of the EE_Cart object
28
-     *
29
-     * @access    private
30
-     * @var EE_Cart $_instance
31
-     */
32
-    private static $_instance;
33
-
34
-    /**
35
-     * instance of the EE_Session object
36
-     *
37
-     * @access    protected
38
-     * @var EE_Session $_session
39
-     */
40
-    protected $_session;
41
-
42
-    /**
43
-     * The total Line item which comprises all the children line-item subtotals,
44
-     * which in turn each have their line items.
45
-     * Typically, the line item structure will look like:
46
-     * grand total
47
-     * -tickets-sub-total
48
-     * --ticket1
49
-     * --ticket2
50
-     * --...
51
-     * -taxes-sub-total
52
-     * --tax1
53
-     * --tax2
54
-     *
55
-     * @var EE_Line_Item
56
-     */
57
-    private $_grand_total;
58
-
59
-
60
-
61
-    /**
62
-     * @singleton method used to instantiate class object
63
-     * @access    public
64
-     * @param EE_Line_Item $grand_total
65
-     * @param EE_Session   $session
66
-     * @return \EE_Cart
67
-     * @throws \EE_Error
68
-     */
69
-    public static function instance(EE_Line_Item $grand_total = null, EE_Session $session = null)
70
-    {
71
-        if ($grand_total instanceof EE_Line_Item && $grand_total->is_total()) {
72
-            self::$_instance = new self($grand_total, $session);
73
-        }
74
-        // or maybe retrieve an existing one ?
75
-        if ( ! self::$_instance instanceof EE_Cart) {
76
-            // try getting the cart out of the session
77
-            $saved_cart = $session instanceof EE_Session ? $session->cart() : null;
78
-            self::$_instance = $saved_cart instanceof EE_Cart ? $saved_cart : new self($grand_total, $session);
79
-            unset($saved_cart);
80
-        }
81
-        // verify that cart is ok and grand total line item exists
82
-        if ( ! self::$_instance instanceof EE_Cart || ! self::$_instance->_grand_total instanceof EE_Line_Item) {
83
-            self::$_instance = new self($grand_total, $session);
84
-        }
85
-        self::$_instance->get_grand_total();
86
-        // once everything is all said and done, save the cart to the EE_Session
87
-        add_action('shutdown', array(self::$_instance, 'save_cart'), 90);
88
-        return self::$_instance;
89
-    }
90
-
91
-
92
-
93
-    /**
94
-     * private constructor to prevent direct creation
95
-     *
96
-     * @Constructor
97
-     * @access private
98
-     * @param EE_Line_Item $grand_total
99
-     * @param EE_Session   $session
100
-     */
101
-    private function __construct(EE_Line_Item $grand_total = null, EE_Session $session = null)
102
-    {
103
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
104
-        $this->set_session($session);
105
-        if ($grand_total instanceof EE_Line_Item && $grand_total->is_total()) {
106
-            $this->set_grand_total_line_item($grand_total);
107
-        }
108
-    }
109
-
110
-
111
-
112
-    /**
113
-     * Resets the cart completely (whereas empty_cart
114
-     *
115
-     * @param EE_Line_Item $grand_total
116
-     * @param EE_Session   $session
117
-     * @return EE_Cart
118
-     * @throws \EE_Error
119
-     */
120
-    public static function reset(EE_Line_Item $grand_total = null, EE_Session $session = null)
121
-    {
122
-        remove_action('shutdown', array(self::$_instance, 'save_cart'), 90);
123
-        if ($session instanceof EE_Session) {
124
-            $session->reset_cart();
125
-        }
126
-        self::$_instance = null;
127
-        return self::instance($grand_total, $session);
128
-    }
129
-
130
-
131
-
132
-    /**
133
-     * @return \EE_Session
134
-     */
135
-    public function session()
136
-    {
137
-        if ( ! $this->_session instanceof EE_Session) {
138
-            $this->set_session();
139
-        }
140
-        return $this->_session;
141
-    }
142
-
143
-
144
-
145
-    /**
146
-     * @param EE_Session $session
147
-     */
148
-    public function set_session(EE_Session $session = null)
149
-    {
150
-        $this->_session = $session instanceof EE_Session ? $session : EE_Registry::instance()->load_core('Session');
151
-    }
152
-
153
-
154
-
155
-    /**
156
-     * Sets the cart to match the line item. Especially handy for loading an old cart where you
157
-     *  know the grand total line item on it
158
-     *
159
-     * @param EE_Line_Item $line_item
160
-     */
161
-    public function set_grand_total_line_item(EE_Line_Item $line_item)
162
-    {
163
-        $this->_grand_total = $line_item;
164
-    }
165
-
166
-
167
-
168
-    /**
169
-     * get_cart_from_reg_url_link
170
-     *
171
-     * @access public
172
-     * @param EE_Transaction $transaction
173
-     * @param EE_Session     $session
174
-     * @return \EE_Cart
175
-     * @throws \EE_Error
176
-     */
177
-    public static function get_cart_from_txn(EE_Transaction $transaction, EE_Session $session = null)
178
-    {
179
-        $grand_total = $transaction->total_line_item();
180
-        $grand_total->get_items();
181
-        $grand_total->tax_descendants();
182
-        return EE_Cart::instance($grand_total, $session);
183
-    }
184
-
185
-
186
-
187
-    /**
188
-     * Creates the total line item, and ensures it has its 'tickets' and 'taxes' sub-items
189
-     *
190
-     * @return EE_Line_Item
191
-     * @throws \EE_Error
192
-     */
193
-    private function _create_grand_total()
194
-    {
195
-        $this->_grand_total = EEH_Line_Item::create_total_line_item();
196
-        return $this->_grand_total;
197
-    }
198
-
199
-
200
-
201
-    /**
202
-     * Gets all the line items of object type Ticket
203
-     *
204
-     * @access public
205
-     * @return \EE_Line_Item[]
206
-     */
207
-    public function get_tickets()
208
-    {
209
-        if ($this->_grand_total === null ) {
210
-            return array();
211
-        }
212
-        return EEH_Line_Item::get_ticket_line_items($this->_grand_total);
213
-    }
214
-
215
-
216
-
217
-    /**
218
-     * returns the total quantity of tickets in the cart
219
-     *
220
-     * @access public
221
-     * @return int
222
-     * @throws \EE_Error
223
-     */
224
-    public function all_ticket_quantity_count()
225
-    {
226
-        $tickets = $this->get_tickets();
227
-        if (empty($tickets)) {
228
-            return 0;
229
-        }
230
-        $count = 0;
231
-        foreach ($tickets as $ticket) {
232
-            $count += $ticket->get('LIN_quantity');
233
-        }
234
-        return $count;
235
-    }
236
-
237
-
238
-
239
-    /**
240
-     * Gets all the tax line items
241
-     *
242
-     * @return \EE_Line_Item[]
243
-     * @throws \EE_Error
244
-     */
245
-    public function get_taxes()
246
-    {
247
-        return EEH_Line_Item::get_taxes_subtotal($this->_grand_total)->children();
248
-    }
249
-
250
-
251
-
252
-    /**
253
-     * Gets the total line item (which is a parent of all other line items) on this cart
254
-     *
255
-     * @return EE_Line_Item
256
-     * @throws \EE_Error
257
-     */
258
-    public function get_grand_total()
259
-    {
260
-        return $this->_grand_total instanceof EE_Line_Item ? $this->_grand_total : $this->_create_grand_total();
261
-    }
262
-
263
-
264
-
265
-    /**
266
-     * @process items for adding to cart
267
-     * @access  public
268
-     * @param EE_Ticket $ticket
269
-     * @param int       $qty
270
-     * @return TRUE on success, FALSE on fail
271
-     * @throws \EE_Error
272
-     */
273
-    public function add_ticket_to_cart(EE_Ticket $ticket, $qty = 1)
274
-    {
275
-        EEH_Line_Item::add_ticket_purchase($this->get_grand_total(), $ticket, $qty);
276
-        return $this->save_cart() ? true : false;
277
-    }
278
-
279
-
280
-
281
-    /**
282
-     * get_cart_total_before_tax
283
-     *
284
-     * @access public
285
-     * @return float
286
-     * @throws \EE_Error
287
-     */
288
-    public function get_cart_total_before_tax()
289
-    {
290
-        return $this->get_grand_total()->recalculate_pre_tax_total();
291
-    }
292
-
293
-
294
-
295
-    /**
296
-     * gets the total amount of tax paid for items in this cart
297
-     *
298
-     * @access public
299
-     * @return float
300
-     * @throws \EE_Error
301
-     */
302
-    public function get_applied_taxes()
303
-    {
304
-        return EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
305
-    }
306
-
307
-
308
-
309
-    /**
310
-     * Gets the total amount to be paid for the items in the cart, including taxes and other modifiers
311
-     *
312
-     * @access public
313
-     * @return float
314
-     * @throws \EE_Error
315
-     */
316
-    public function get_cart_grand_total()
317
-    {
318
-        EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
319
-        return $this->get_grand_total()->total();
320
-    }
321
-
322
-
323
-
324
-    /**
325
-     * Gets the total amount to be paid for the items in the cart, including taxes and other modifiers
326
-     *
327
-     * @access public
328
-     * @return float
329
-     * @throws \EE_Error
330
-     */
331
-    public function recalculate_all_cart_totals()
332
-    {
333
-        $pre_tax_total = $this->get_cart_total_before_tax();
334
-        $taxes_total = EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
335
-        $this->_grand_total->set_total($pre_tax_total + $taxes_total);
336
-        $this->_grand_total->save_this_and_descendants_to_txn();
337
-        return $this->get_grand_total()->total();
338
-    }
339
-
340
-
341
-
342
-    /**
343
-     * deletes an item from the cart
344
-     *
345
-     * @access public
346
-     * @param array|bool|string $line_item_codes
347
-     * @return int on success, FALSE on fail
348
-     * @throws \EE_Error
349
-     */
350
-    public function delete_items($line_item_codes = false)
351
-    {
352
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
353
-        return EEH_Line_Item::delete_items($this->get_grand_total(), $line_item_codes);
354
-    }
355
-
356
-
357
-
358
-    /**
359
-     * @remove ALL items from cart and zero ALL totals
360
-     * @access public
361
-     * @return bool
362
-     * @throws \EE_Error
363
-     */
364
-    public function empty_cart()
365
-    {
366
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
367
-        $this->_grand_total = $this->_create_grand_total();
368
-        return $this->save_cart(true);
369
-    }
370
-
371
-
372
-
373
-    /**
374
-     * @remove ALL items from cart and delete total as well
375
-     * @access public
376
-     * @return bool
377
-     * @throws \EE_Error
378
-     */
379
-    public function delete_cart()
380
-    {
381
-        if ($this->_grand_total instanceof EE_Line_Item) {
382
-            $deleted = EEH_Line_Item::delete_all_child_items($this->_grand_total);
383
-            if ($deleted) {
384
-                $deleted += $this->_grand_total->delete();
385
-                $this->_grand_total = null;
386
-                return true;
387
-            }
388
-        }
389
-        return false;
390
-    }
391
-
392
-
393
-
394
-    /**
395
-     * @save   cart to session
396
-     * @access public
397
-     * @param bool $apply_taxes
398
-     * @return TRUE on success, FALSE on fail
399
-     * @throws \EE_Error
400
-     */
401
-    public function save_cart($apply_taxes = true)
402
-    {
403
-        if ($apply_taxes && $this->_grand_total instanceof EE_Line_Item) {
404
-            EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
405
-            //make sure we don't cache the transaction because it can get stale
406
-            if ($this->_grand_total->get_one_from_cache('Transaction') instanceof EE_Transaction
407
-                && $this->_grand_total->get_one_from_cache('Transaction')->ID()
408
-            ) {
409
-                $this->_grand_total->clear_cache('Transaction', null, true);
410
-            }
411
-        }
412
-        if ($this->session() instanceof EE_Session) {
413
-            return $this->session()->set_cart($this);
414
-        } else {
415
-            return false;
416
-        }
417
-    }
418
-
419
-
420
-
421
-    public function __wakeup()
422
-    {
423
-        if ( ! $this->_grand_total instanceof EE_Line_Item && absint($this->_grand_total) !== 0) {
424
-            // $this->_grand_total is actually just an ID, so use it to get the object from the db
425
-            $this->_grand_total = EEM_Line_Item::instance()->get_one_by_ID($this->_grand_total);
426
-        }
427
-    }
428
-
429
-
430
-
431
-    /**
432
-     * @return array
433
-     */
434
-    public function __sleep()
435
-    {
436
-        if ($this->_grand_total instanceof EE_Line_Item && $this->_grand_total->ID()) {
437
-            $this->_grand_total = $this->_grand_total->ID();
438
-        }
439
-        return array('_grand_total');
440
-    }
26
+	/**
27
+	 * instance of the EE_Cart object
28
+	 *
29
+	 * @access    private
30
+	 * @var EE_Cart $_instance
31
+	 */
32
+	private static $_instance;
33
+
34
+	/**
35
+	 * instance of the EE_Session object
36
+	 *
37
+	 * @access    protected
38
+	 * @var EE_Session $_session
39
+	 */
40
+	protected $_session;
41
+
42
+	/**
43
+	 * The total Line item which comprises all the children line-item subtotals,
44
+	 * which in turn each have their line items.
45
+	 * Typically, the line item structure will look like:
46
+	 * grand total
47
+	 * -tickets-sub-total
48
+	 * --ticket1
49
+	 * --ticket2
50
+	 * --...
51
+	 * -taxes-sub-total
52
+	 * --tax1
53
+	 * --tax2
54
+	 *
55
+	 * @var EE_Line_Item
56
+	 */
57
+	private $_grand_total;
58
+
59
+
60
+
61
+	/**
62
+	 * @singleton method used to instantiate class object
63
+	 * @access    public
64
+	 * @param EE_Line_Item $grand_total
65
+	 * @param EE_Session   $session
66
+	 * @return \EE_Cart
67
+	 * @throws \EE_Error
68
+	 */
69
+	public static function instance(EE_Line_Item $grand_total = null, EE_Session $session = null)
70
+	{
71
+		if ($grand_total instanceof EE_Line_Item && $grand_total->is_total()) {
72
+			self::$_instance = new self($grand_total, $session);
73
+		}
74
+		// or maybe retrieve an existing one ?
75
+		if ( ! self::$_instance instanceof EE_Cart) {
76
+			// try getting the cart out of the session
77
+			$saved_cart = $session instanceof EE_Session ? $session->cart() : null;
78
+			self::$_instance = $saved_cart instanceof EE_Cart ? $saved_cart : new self($grand_total, $session);
79
+			unset($saved_cart);
80
+		}
81
+		// verify that cart is ok and grand total line item exists
82
+		if ( ! self::$_instance instanceof EE_Cart || ! self::$_instance->_grand_total instanceof EE_Line_Item) {
83
+			self::$_instance = new self($grand_total, $session);
84
+		}
85
+		self::$_instance->get_grand_total();
86
+		// once everything is all said and done, save the cart to the EE_Session
87
+		add_action('shutdown', array(self::$_instance, 'save_cart'), 90);
88
+		return self::$_instance;
89
+	}
90
+
91
+
92
+
93
+	/**
94
+	 * private constructor to prevent direct creation
95
+	 *
96
+	 * @Constructor
97
+	 * @access private
98
+	 * @param EE_Line_Item $grand_total
99
+	 * @param EE_Session   $session
100
+	 */
101
+	private function __construct(EE_Line_Item $grand_total = null, EE_Session $session = null)
102
+	{
103
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
104
+		$this->set_session($session);
105
+		if ($grand_total instanceof EE_Line_Item && $grand_total->is_total()) {
106
+			$this->set_grand_total_line_item($grand_total);
107
+		}
108
+	}
109
+
110
+
111
+
112
+	/**
113
+	 * Resets the cart completely (whereas empty_cart
114
+	 *
115
+	 * @param EE_Line_Item $grand_total
116
+	 * @param EE_Session   $session
117
+	 * @return EE_Cart
118
+	 * @throws \EE_Error
119
+	 */
120
+	public static function reset(EE_Line_Item $grand_total = null, EE_Session $session = null)
121
+	{
122
+		remove_action('shutdown', array(self::$_instance, 'save_cart'), 90);
123
+		if ($session instanceof EE_Session) {
124
+			$session->reset_cart();
125
+		}
126
+		self::$_instance = null;
127
+		return self::instance($grand_total, $session);
128
+	}
129
+
130
+
131
+
132
+	/**
133
+	 * @return \EE_Session
134
+	 */
135
+	public function session()
136
+	{
137
+		if ( ! $this->_session instanceof EE_Session) {
138
+			$this->set_session();
139
+		}
140
+		return $this->_session;
141
+	}
142
+
143
+
144
+
145
+	/**
146
+	 * @param EE_Session $session
147
+	 */
148
+	public function set_session(EE_Session $session = null)
149
+	{
150
+		$this->_session = $session instanceof EE_Session ? $session : EE_Registry::instance()->load_core('Session');
151
+	}
152
+
153
+
154
+
155
+	/**
156
+	 * Sets the cart to match the line item. Especially handy for loading an old cart where you
157
+	 *  know the grand total line item on it
158
+	 *
159
+	 * @param EE_Line_Item $line_item
160
+	 */
161
+	public function set_grand_total_line_item(EE_Line_Item $line_item)
162
+	{
163
+		$this->_grand_total = $line_item;
164
+	}
165
+
166
+
167
+
168
+	/**
169
+	 * get_cart_from_reg_url_link
170
+	 *
171
+	 * @access public
172
+	 * @param EE_Transaction $transaction
173
+	 * @param EE_Session     $session
174
+	 * @return \EE_Cart
175
+	 * @throws \EE_Error
176
+	 */
177
+	public static function get_cart_from_txn(EE_Transaction $transaction, EE_Session $session = null)
178
+	{
179
+		$grand_total = $transaction->total_line_item();
180
+		$grand_total->get_items();
181
+		$grand_total->tax_descendants();
182
+		return EE_Cart::instance($grand_total, $session);
183
+	}
184
+
185
+
186
+
187
+	/**
188
+	 * Creates the total line item, and ensures it has its 'tickets' and 'taxes' sub-items
189
+	 *
190
+	 * @return EE_Line_Item
191
+	 * @throws \EE_Error
192
+	 */
193
+	private function _create_grand_total()
194
+	{
195
+		$this->_grand_total = EEH_Line_Item::create_total_line_item();
196
+		return $this->_grand_total;
197
+	}
198
+
199
+
200
+
201
+	/**
202
+	 * Gets all the line items of object type Ticket
203
+	 *
204
+	 * @access public
205
+	 * @return \EE_Line_Item[]
206
+	 */
207
+	public function get_tickets()
208
+	{
209
+		if ($this->_grand_total === null ) {
210
+			return array();
211
+		}
212
+		return EEH_Line_Item::get_ticket_line_items($this->_grand_total);
213
+	}
214
+
215
+
216
+
217
+	/**
218
+	 * returns the total quantity of tickets in the cart
219
+	 *
220
+	 * @access public
221
+	 * @return int
222
+	 * @throws \EE_Error
223
+	 */
224
+	public function all_ticket_quantity_count()
225
+	{
226
+		$tickets = $this->get_tickets();
227
+		if (empty($tickets)) {
228
+			return 0;
229
+		}
230
+		$count = 0;
231
+		foreach ($tickets as $ticket) {
232
+			$count += $ticket->get('LIN_quantity');
233
+		}
234
+		return $count;
235
+	}
236
+
237
+
238
+
239
+	/**
240
+	 * Gets all the tax line items
241
+	 *
242
+	 * @return \EE_Line_Item[]
243
+	 * @throws \EE_Error
244
+	 */
245
+	public function get_taxes()
246
+	{
247
+		return EEH_Line_Item::get_taxes_subtotal($this->_grand_total)->children();
248
+	}
249
+
250
+
251
+
252
+	/**
253
+	 * Gets the total line item (which is a parent of all other line items) on this cart
254
+	 *
255
+	 * @return EE_Line_Item
256
+	 * @throws \EE_Error
257
+	 */
258
+	public function get_grand_total()
259
+	{
260
+		return $this->_grand_total instanceof EE_Line_Item ? $this->_grand_total : $this->_create_grand_total();
261
+	}
262
+
263
+
264
+
265
+	/**
266
+	 * @process items for adding to cart
267
+	 * @access  public
268
+	 * @param EE_Ticket $ticket
269
+	 * @param int       $qty
270
+	 * @return TRUE on success, FALSE on fail
271
+	 * @throws \EE_Error
272
+	 */
273
+	public function add_ticket_to_cart(EE_Ticket $ticket, $qty = 1)
274
+	{
275
+		EEH_Line_Item::add_ticket_purchase($this->get_grand_total(), $ticket, $qty);
276
+		return $this->save_cart() ? true : false;
277
+	}
278
+
279
+
280
+
281
+	/**
282
+	 * get_cart_total_before_tax
283
+	 *
284
+	 * @access public
285
+	 * @return float
286
+	 * @throws \EE_Error
287
+	 */
288
+	public function get_cart_total_before_tax()
289
+	{
290
+		return $this->get_grand_total()->recalculate_pre_tax_total();
291
+	}
292
+
293
+
294
+
295
+	/**
296
+	 * gets the total amount of tax paid for items in this cart
297
+	 *
298
+	 * @access public
299
+	 * @return float
300
+	 * @throws \EE_Error
301
+	 */
302
+	public function get_applied_taxes()
303
+	{
304
+		return EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
305
+	}
306
+
307
+
308
+
309
+	/**
310
+	 * Gets the total amount to be paid for the items in the cart, including taxes and other modifiers
311
+	 *
312
+	 * @access public
313
+	 * @return float
314
+	 * @throws \EE_Error
315
+	 */
316
+	public function get_cart_grand_total()
317
+	{
318
+		EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
319
+		return $this->get_grand_total()->total();
320
+	}
321
+
322
+
323
+
324
+	/**
325
+	 * Gets the total amount to be paid for the items in the cart, including taxes and other modifiers
326
+	 *
327
+	 * @access public
328
+	 * @return float
329
+	 * @throws \EE_Error
330
+	 */
331
+	public function recalculate_all_cart_totals()
332
+	{
333
+		$pre_tax_total = $this->get_cart_total_before_tax();
334
+		$taxes_total = EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
335
+		$this->_grand_total->set_total($pre_tax_total + $taxes_total);
336
+		$this->_grand_total->save_this_and_descendants_to_txn();
337
+		return $this->get_grand_total()->total();
338
+	}
339
+
340
+
341
+
342
+	/**
343
+	 * deletes an item from the cart
344
+	 *
345
+	 * @access public
346
+	 * @param array|bool|string $line_item_codes
347
+	 * @return int on success, FALSE on fail
348
+	 * @throws \EE_Error
349
+	 */
350
+	public function delete_items($line_item_codes = false)
351
+	{
352
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
353
+		return EEH_Line_Item::delete_items($this->get_grand_total(), $line_item_codes);
354
+	}
355
+
356
+
357
+
358
+	/**
359
+	 * @remove ALL items from cart and zero ALL totals
360
+	 * @access public
361
+	 * @return bool
362
+	 * @throws \EE_Error
363
+	 */
364
+	public function empty_cart()
365
+	{
366
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
367
+		$this->_grand_total = $this->_create_grand_total();
368
+		return $this->save_cart(true);
369
+	}
370
+
371
+
372
+
373
+	/**
374
+	 * @remove ALL items from cart and delete total as well
375
+	 * @access public
376
+	 * @return bool
377
+	 * @throws \EE_Error
378
+	 */
379
+	public function delete_cart()
380
+	{
381
+		if ($this->_grand_total instanceof EE_Line_Item) {
382
+			$deleted = EEH_Line_Item::delete_all_child_items($this->_grand_total);
383
+			if ($deleted) {
384
+				$deleted += $this->_grand_total->delete();
385
+				$this->_grand_total = null;
386
+				return true;
387
+			}
388
+		}
389
+		return false;
390
+	}
391
+
392
+
393
+
394
+	/**
395
+	 * @save   cart to session
396
+	 * @access public
397
+	 * @param bool $apply_taxes
398
+	 * @return TRUE on success, FALSE on fail
399
+	 * @throws \EE_Error
400
+	 */
401
+	public function save_cart($apply_taxes = true)
402
+	{
403
+		if ($apply_taxes && $this->_grand_total instanceof EE_Line_Item) {
404
+			EEH_Line_Item::ensure_taxes_applied($this->_grand_total);
405
+			//make sure we don't cache the transaction because it can get stale
406
+			if ($this->_grand_total->get_one_from_cache('Transaction') instanceof EE_Transaction
407
+				&& $this->_grand_total->get_one_from_cache('Transaction')->ID()
408
+			) {
409
+				$this->_grand_total->clear_cache('Transaction', null, true);
410
+			}
411
+		}
412
+		if ($this->session() instanceof EE_Session) {
413
+			return $this->session()->set_cart($this);
414
+		} else {
415
+			return false;
416
+		}
417
+	}
418
+
419
+
420
+
421
+	public function __wakeup()
422
+	{
423
+		if ( ! $this->_grand_total instanceof EE_Line_Item && absint($this->_grand_total) !== 0) {
424
+			// $this->_grand_total is actually just an ID, so use it to get the object from the db
425
+			$this->_grand_total = EEM_Line_Item::instance()->get_one_by_ID($this->_grand_total);
426
+		}
427
+	}
428
+
429
+
430
+
431
+	/**
432
+	 * @return array
433
+	 */
434
+	public function __sleep()
435
+	{
436
+		if ($this->_grand_total instanceof EE_Line_Item && $this->_grand_total->ID()) {
437
+			$this->_grand_total = $this->_grand_total->ID();
438
+		}
439
+		return array('_grand_total');
440
+	}
441 441
 
442 442
 
443 443
 }
Please login to merge, or discard this patch.
core/domain/services/factories/CartFactory.php 1 patch
Indentation   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -23,14 +23,14 @@
 block discarded – undo
23 23
 class CartFactory
24 24
 {
25 25
 
26
-    /**
27
-     * @return EE_Cart
28
-     * @throws InvalidArgumentException
29
-     * @throws InvalidInterfaceException
30
-     * @throws InvalidDataTypeException
31
-     */
32
-    public static function getCart()
33
-    {
34
-        return LoaderFactory::getLoader()->getShared('EE_Cart');
35
-    }
26
+	/**
27
+	 * @return EE_Cart
28
+	 * @throws InvalidArgumentException
29
+	 * @throws InvalidInterfaceException
30
+	 * @throws InvalidDataTypeException
31
+	 */
32
+	public static function getCart()
33
+	{
34
+		return LoaderFactory::getLoader()->getShared('EE_Cart');
35
+	}
36 36
 }
Please login to merge, or discard this patch.
core/domain/services/factories/ModelFactory.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -25,17 +25,17 @@
 block discarded – undo
25 25
 class ModelFactory
26 26
 {
27 27
 
28
-    /**
29
-     * @param string $model_name
30
-     * @return bool|EEM_Base
31
-     * @throws EE_Error
32
-     * @throws InvalidDataTypeException
33
-     * @throws InvalidInterfaceException
34
-     * @throws InvalidArgumentException
35
-     * @throws ReflectionException
36
-     */
37
-    public static function getModel($model_name)
38
-    {
39
-        return EE_Registry::instance()->load_model($model_name);
40
-    }
28
+	/**
29
+	 * @param string $model_name
30
+	 * @return bool|EEM_Base
31
+	 * @throws EE_Error
32
+	 * @throws InvalidDataTypeException
33
+	 * @throws InvalidInterfaceException
34
+	 * @throws InvalidArgumentException
35
+	 * @throws ReflectionException
36
+	 */
37
+	public static function getModel($model_name)
38
+	{
39
+		return EE_Registry::instance()->load_model($model_name);
40
+	}
41 41
 }
Please login to merge, or discard this patch.
modules/ticket_selector/TicketDatetimeAvailabilityTracker.php 2 patches
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -66,24 +66,24 @@  discard block
 block discarded – undo
66 66
     public function ticketDatetimeAvailability(EE_Ticket $ticket, $get_original_ticket_spaces = false)
67 67
     {
68 68
         // if the $_available_spaces array has not been set up yet...
69
-        if (! isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
69
+        if ( ! isset($this->available_spaces['tickets'][$ticket->ID()])) {
70 70
             $this->setInitialTicketDatetimeAvailability($ticket);
71 71
         }
72 72
         $available_spaces = $ticket->qty() - $ticket->sold();
73
-        if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
73
+        if (isset($this->available_spaces['tickets'][$ticket->ID()])) {
74 74
             // loop thru tickets, which will ALSO include individual ticket records AND a total
75
-            foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
75
+            foreach ($this->available_spaces['tickets'][$ticket->ID()] as $DTD_ID => $spaces) {
76 76
                 // if we want the original datetime availability BEFORE we started subtracting tickets ?
77 77
                 if ($get_original_ticket_spaces) {
78 78
                     // then grab the available spaces from the "tickets" array
79 79
                     // and compare with the above to get the lowest number
80 80
                     $available_spaces = min(
81 81
                         $available_spaces,
82
-                        $this->available_spaces['tickets'][ $ticket->ID() ][ $DTD_ID ]
82
+                        $this->available_spaces['tickets'][$ticket->ID()][$DTD_ID]
83 83
                     );
84 84
                 } else {
85 85
                     // we want the updated ticket availability as stored in the "datetimes" array
86
-                    $available_spaces = min($available_spaces, $this->available_spaces['datetimes'][ $DTD_ID ]);
86
+                    $available_spaces = min($available_spaces, $this->available_spaces['datetimes'][$DTD_ID]);
87 87
                 }
88 88
             }
89 89
         }
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
                 'order_by' => array('DTT_EVT_start' => 'ASC'),
115 115
             )
116 116
         );
117
-        if (! empty($datetimes)) {
117
+        if ( ! empty($datetimes)) {
118 118
             // now loop thru all of the datetimes
119 119
             foreach ($datetimes as $datetime) {
120 120
                 if ($datetime instanceof EE_Datetime) {
@@ -122,17 +122,17 @@  discard block
 block discarded – undo
122 122
                     $spaces_remaining = $datetime->spaces_remaining();
123 123
                     // save the total available spaces ( the lesser of the ticket qty minus the number of tickets sold
124 124
                     // or the datetime spaces remaining) to this ticket using the datetime ID as the key
125
-                    $this->available_spaces['tickets'][ $ticket->ID() ][ $datetime->ID() ] = min(
125
+                    $this->available_spaces['tickets'][$ticket->ID()][$datetime->ID()] = min(
126 126
                         $ticket->qty() - $ticket->sold(),
127 127
                         $spaces_remaining
128 128
                     );
129 129
                     // if the remaining spaces for this datetime is already set,
130 130
                     // then compare that against the datetime spaces remaining, and take the lowest number,
131 131
                     // else just take the datetime spaces remaining, and assign to the datetimes array
132
-                    $this->available_spaces['datetimes'][ $datetime->ID() ] = isset(
133
-                        $this->available_spaces['datetimes'][ $datetime->ID() ]
132
+                    $this->available_spaces['datetimes'][$datetime->ID()] = isset(
133
+                        $this->available_spaces['datetimes'][$datetime->ID()]
134 134
                     )
135
-                        ? min($this->available_spaces['datetimes'][ $datetime->ID() ], $spaces_remaining)
135
+                        ? min($this->available_spaces['datetimes'][$datetime->ID()], $spaces_remaining)
136 136
                         : $spaces_remaining;
137 137
                 }
138 138
             }
@@ -148,11 +148,11 @@  discard block
 block discarded – undo
148 148
      */
149 149
     public function recalculateTicketDatetimeAvailability(EE_Ticket $ticket, $qty = 0)
150 150
     {
151
-        if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
151
+        if (isset($this->available_spaces['tickets'][$ticket->ID()])) {
152 152
             // loop thru tickets, which will ALSO include individual ticket records AND a total
153
-            foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
153
+            foreach ($this->available_spaces['tickets'][$ticket->ID()] as $DTD_ID => $spaces) {
154 154
                 // subtract the qty of selected tickets from each datetime's available spaces this ticket has access to,
155
-                $this->available_spaces['datetimes'][ $DTD_ID ] -= $qty;
155
+                $this->available_spaces['datetimes'][$DTD_ID] -= $qty;
156 156
             }
157 157
         }
158 158
     }
Please login to merge, or discard this patch.
Indentation   +211 added lines, -211 removed lines patch added patch discarded remove patch
@@ -26,215 +26,215 @@
 block discarded – undo
26 26
 class TicketDatetimeAvailabilityTracker
27 27
 {
28 28
 
29
-    /**
30
-     * array of datetimes and the spaces available for them
31
-     *
32
-     * @var array[][]
33
-     */
34
-    private $available_spaces = array();
35
-
36
-    /**
37
-     * @var EEM_Datetime $datetime_model
38
-     */
39
-    private $datetime_model;
40
-
41
-
42
-    /**
43
-     * TicketDatetimeAvailabilityTracker constructor.
44
-     *
45
-     * @param EEM_Datetime $datetime_model
46
-     */
47
-    public function __construct(EEM_Datetime $datetime_model)
48
-    {
49
-        $this->datetime_model = $datetime_model;
50
-    }
51
-
52
-
53
-    /**
54
-     * ticketDatetimeAvailability
55
-     * creates an array of tickets plus all of the datetimes available to each ticket
56
-     * and tracks the spaces remaining for each of those datetimes
57
-     *
58
-     * @param EE_Ticket $ticket - selected ticket
59
-     * @param bool      $get_original_ticket_spaces
60
-     * @return int
61
-     * @throws EE_Error
62
-     * @throws InvalidArgumentException
63
-     * @throws InvalidDataTypeException
64
-     * @throws InvalidInterfaceException
65
-     */
66
-    public function ticketDatetimeAvailability(EE_Ticket $ticket, $get_original_ticket_spaces = false)
67
-    {
68
-        // if the $_available_spaces array has not been set up yet...
69
-        if (! isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
70
-            $this->setInitialTicketDatetimeAvailability($ticket);
71
-        }
72
-        $available_spaces = $ticket->qty() - $ticket->sold();
73
-        if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
74
-            // loop thru tickets, which will ALSO include individual ticket records AND a total
75
-            foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
76
-                // if we want the original datetime availability BEFORE we started subtracting tickets ?
77
-                if ($get_original_ticket_spaces) {
78
-                    // then grab the available spaces from the "tickets" array
79
-                    // and compare with the above to get the lowest number
80
-                    $available_spaces = min(
81
-                        $available_spaces,
82
-                        $this->available_spaces['tickets'][ $ticket->ID() ][ $DTD_ID ]
83
-                    );
84
-                } else {
85
-                    // we want the updated ticket availability as stored in the "datetimes" array
86
-                    $available_spaces = min($available_spaces, $this->available_spaces['datetimes'][ $DTD_ID ]);
87
-                }
88
-            }
89
-        }
90
-        return $available_spaces;
91
-    }
92
-
93
-
94
-    /**
95
-     * @param EE_Ticket $ticket
96
-     * @return void
97
-     * @throws InvalidArgumentException
98
-     * @throws InvalidInterfaceException
99
-     * @throws InvalidDataTypeException
100
-     * @throws EE_Error
101
-     */
102
-    private function setInitialTicketDatetimeAvailability(EE_Ticket $ticket)
103
-    {
104
-        // first, get all of the datetimes that are available to this ticket
105
-        $datetimes = $ticket->get_many_related(
106
-            'Datetime',
107
-            array(
108
-                array(
109
-                    'DTT_EVT_end' => array(
110
-                        '>=',
111
-                        $this->datetime_model->current_time_for_query('DTT_EVT_end'),
112
-                    ),
113
-                ),
114
-                'order_by' => array('DTT_EVT_start' => 'ASC'),
115
-            )
116
-        );
117
-        if (! empty($datetimes)) {
118
-            // now loop thru all of the datetimes
119
-            foreach ($datetimes as $datetime) {
120
-                if ($datetime instanceof EE_Datetime) {
121
-                    // the number of spaces available for the datetime without considering individual ticket quantities
122
-                    $spaces_remaining = $datetime->spaces_remaining();
123
-                    // save the total available spaces ( the lesser of the ticket qty minus the number of tickets sold
124
-                    // or the datetime spaces remaining) to this ticket using the datetime ID as the key
125
-                    $this->available_spaces['tickets'][ $ticket->ID() ][ $datetime->ID() ] = min(
126
-                        $ticket->qty() - $ticket->sold(),
127
-                        $spaces_remaining
128
-                    );
129
-                    // if the remaining spaces for this datetime is already set,
130
-                    // then compare that against the datetime spaces remaining, and take the lowest number,
131
-                    // else just take the datetime spaces remaining, and assign to the datetimes array
132
-                    $this->available_spaces['datetimes'][ $datetime->ID() ] = isset(
133
-                        $this->available_spaces['datetimes'][ $datetime->ID() ]
134
-                    )
135
-                        ? min($this->available_spaces['datetimes'][ $datetime->ID() ], $spaces_remaining)
136
-                        : $spaces_remaining;
137
-                }
138
-            }
139
-        }
140
-    }
141
-
142
-
143
-    /**
144
-     * @param    EE_Ticket $ticket
145
-     * @param    int       $qty
146
-     * @return    void
147
-     * @throws EE_Error
148
-     */
149
-    public function recalculateTicketDatetimeAvailability(EE_Ticket $ticket, $qty = 0)
150
-    {
151
-        if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
152
-            // loop thru tickets, which will ALSO include individual ticket records AND a total
153
-            foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
154
-                // subtract the qty of selected tickets from each datetime's available spaces this ticket has access to,
155
-                $this->available_spaces['datetimes'][ $DTD_ID ] -= $qty;
156
-            }
157
-        }
158
-    }
159
-
160
-
161
-    /**
162
-     * @param EE_Ticket $ticket
163
-     * @param           $qty
164
-     * @param int       $total_ticket_count
165
-     * @throws EE_Error
166
-     * @throws InvalidArgumentException
167
-     * @throws InvalidDataTypeException
168
-     * @throws InvalidInterfaceException
169
-     */
170
-    public function processAvailabilityError(EE_Ticket $ticket, $qty, $total_ticket_count = 1)
171
-    {
172
-        // tickets can not be purchased but let's find the exact number left
173
-        // for the last ticket selected PRIOR to subtracting tickets
174
-        $available_spaces = $this->ticketDatetimeAvailability($ticket, true);
175
-        // greedy greedy greedy eh?
176
-        if ($available_spaces > 0) {
177
-            if (
178
-            apply_filters(
179
-                'FHEE__EE_Ticket_Selector___add_ticket_to_cart__allow_display_availability_error',
180
-                true,
181
-                $ticket,
182
-                $qty,
183
-                $available_spaces
184
-            )
185
-            ) {
186
-                $this->availabilityError(
187
-                    $available_spaces,
188
-                    $total_ticket_count
189
-                );
190
-            }
191
-        } else {
192
-            EE_Error::add_error(
193
-                esc_html__(
194
-                    'We\'re sorry, but there are no available spaces left for this event at this particular date and time.',
195
-                    'event_espresso'
196
-                ),
197
-                __FILE__, __FUNCTION__, __LINE__
198
-            );
199
-        }
200
-    }
201
-
202
-
203
-    /**
204
-     * @param int $available_spaces
205
-     * @param int $total_ticket_count
206
-     */
207
-    private function availabilityError($available_spaces = 1, $total_ticket_count = 1)
208
-    {
209
-        // add error messaging - we're using the _n function that will generate
210
-        // the appropriate singular or plural message based on the number of $available_spaces
211
-        if ($total_ticket_count) {
212
-            $msg = sprintf(
213
-                esc_html(
214
-                    _n(
215
-                        'We\'re sorry, but there is only %1$s available space left for this event at this particular date and time. Please select a different number (or different combination) of tickets by cancelling the current selection and choosing again, or proceed to registration.',
216
-                        'We\'re sorry, but there are only %1$s available spaces left for this event at this particular date and time. Please select a different number (or different combination) of tickets by cancelling the current selection and choosing again, or proceed to registration.',
217
-                        $available_spaces,
218
-                        'event_espresso'
219
-                    )
220
-                ),
221
-                $available_spaces,
222
-                '<br />'
223
-            );
224
-        } else {
225
-            $msg = sprintf(
226
-                esc_html(
227
-                    _n(
228
-                        'We\'re sorry, but there is only %1$s available space left for this event at this particular date and time. Please select a different number (or different combination) of tickets.',
229
-                        'We\'re sorry, but there are only %1$s available spaces left for this event at this particular date and time. Please select a different number (or different combination) of tickets.',
230
-                        $available_spaces,
231
-                        'event_espresso'
232
-                    )
233
-                ),
234
-                $available_spaces,
235
-                '<br />'
236
-            );
237
-        }
238
-        EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
239
-    }
29
+	/**
30
+	 * array of datetimes and the spaces available for them
31
+	 *
32
+	 * @var array[][]
33
+	 */
34
+	private $available_spaces = array();
35
+
36
+	/**
37
+	 * @var EEM_Datetime $datetime_model
38
+	 */
39
+	private $datetime_model;
40
+
41
+
42
+	/**
43
+	 * TicketDatetimeAvailabilityTracker constructor.
44
+	 *
45
+	 * @param EEM_Datetime $datetime_model
46
+	 */
47
+	public function __construct(EEM_Datetime $datetime_model)
48
+	{
49
+		$this->datetime_model = $datetime_model;
50
+	}
51
+
52
+
53
+	/**
54
+	 * ticketDatetimeAvailability
55
+	 * creates an array of tickets plus all of the datetimes available to each ticket
56
+	 * and tracks the spaces remaining for each of those datetimes
57
+	 *
58
+	 * @param EE_Ticket $ticket - selected ticket
59
+	 * @param bool      $get_original_ticket_spaces
60
+	 * @return int
61
+	 * @throws EE_Error
62
+	 * @throws InvalidArgumentException
63
+	 * @throws InvalidDataTypeException
64
+	 * @throws InvalidInterfaceException
65
+	 */
66
+	public function ticketDatetimeAvailability(EE_Ticket $ticket, $get_original_ticket_spaces = false)
67
+	{
68
+		// if the $_available_spaces array has not been set up yet...
69
+		if (! isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
70
+			$this->setInitialTicketDatetimeAvailability($ticket);
71
+		}
72
+		$available_spaces = $ticket->qty() - $ticket->sold();
73
+		if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
74
+			// loop thru tickets, which will ALSO include individual ticket records AND a total
75
+			foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
76
+				// if we want the original datetime availability BEFORE we started subtracting tickets ?
77
+				if ($get_original_ticket_spaces) {
78
+					// then grab the available spaces from the "tickets" array
79
+					// and compare with the above to get the lowest number
80
+					$available_spaces = min(
81
+						$available_spaces,
82
+						$this->available_spaces['tickets'][ $ticket->ID() ][ $DTD_ID ]
83
+					);
84
+				} else {
85
+					// we want the updated ticket availability as stored in the "datetimes" array
86
+					$available_spaces = min($available_spaces, $this->available_spaces['datetimes'][ $DTD_ID ]);
87
+				}
88
+			}
89
+		}
90
+		return $available_spaces;
91
+	}
92
+
93
+
94
+	/**
95
+	 * @param EE_Ticket $ticket
96
+	 * @return void
97
+	 * @throws InvalidArgumentException
98
+	 * @throws InvalidInterfaceException
99
+	 * @throws InvalidDataTypeException
100
+	 * @throws EE_Error
101
+	 */
102
+	private function setInitialTicketDatetimeAvailability(EE_Ticket $ticket)
103
+	{
104
+		// first, get all of the datetimes that are available to this ticket
105
+		$datetimes = $ticket->get_many_related(
106
+			'Datetime',
107
+			array(
108
+				array(
109
+					'DTT_EVT_end' => array(
110
+						'>=',
111
+						$this->datetime_model->current_time_for_query('DTT_EVT_end'),
112
+					),
113
+				),
114
+				'order_by' => array('DTT_EVT_start' => 'ASC'),
115
+			)
116
+		);
117
+		if (! empty($datetimes)) {
118
+			// now loop thru all of the datetimes
119
+			foreach ($datetimes as $datetime) {
120
+				if ($datetime instanceof EE_Datetime) {
121
+					// the number of spaces available for the datetime without considering individual ticket quantities
122
+					$spaces_remaining = $datetime->spaces_remaining();
123
+					// save the total available spaces ( the lesser of the ticket qty minus the number of tickets sold
124
+					// or the datetime spaces remaining) to this ticket using the datetime ID as the key
125
+					$this->available_spaces['tickets'][ $ticket->ID() ][ $datetime->ID() ] = min(
126
+						$ticket->qty() - $ticket->sold(),
127
+						$spaces_remaining
128
+					);
129
+					// if the remaining spaces for this datetime is already set,
130
+					// then compare that against the datetime spaces remaining, and take the lowest number,
131
+					// else just take the datetime spaces remaining, and assign to the datetimes array
132
+					$this->available_spaces['datetimes'][ $datetime->ID() ] = isset(
133
+						$this->available_spaces['datetimes'][ $datetime->ID() ]
134
+					)
135
+						? min($this->available_spaces['datetimes'][ $datetime->ID() ], $spaces_remaining)
136
+						: $spaces_remaining;
137
+				}
138
+			}
139
+		}
140
+	}
141
+
142
+
143
+	/**
144
+	 * @param    EE_Ticket $ticket
145
+	 * @param    int       $qty
146
+	 * @return    void
147
+	 * @throws EE_Error
148
+	 */
149
+	public function recalculateTicketDatetimeAvailability(EE_Ticket $ticket, $qty = 0)
150
+	{
151
+		if (isset($this->available_spaces['tickets'][ $ticket->ID() ])) {
152
+			// loop thru tickets, which will ALSO include individual ticket records AND a total
153
+			foreach ($this->available_spaces['tickets'][ $ticket->ID() ] as $DTD_ID => $spaces) {
154
+				// subtract the qty of selected tickets from each datetime's available spaces this ticket has access to,
155
+				$this->available_spaces['datetimes'][ $DTD_ID ] -= $qty;
156
+			}
157
+		}
158
+	}
159
+
160
+
161
+	/**
162
+	 * @param EE_Ticket $ticket
163
+	 * @param           $qty
164
+	 * @param int       $total_ticket_count
165
+	 * @throws EE_Error
166
+	 * @throws InvalidArgumentException
167
+	 * @throws InvalidDataTypeException
168
+	 * @throws InvalidInterfaceException
169
+	 */
170
+	public function processAvailabilityError(EE_Ticket $ticket, $qty, $total_ticket_count = 1)
171
+	{
172
+		// tickets can not be purchased but let's find the exact number left
173
+		// for the last ticket selected PRIOR to subtracting tickets
174
+		$available_spaces = $this->ticketDatetimeAvailability($ticket, true);
175
+		// greedy greedy greedy eh?
176
+		if ($available_spaces > 0) {
177
+			if (
178
+			apply_filters(
179
+				'FHEE__EE_Ticket_Selector___add_ticket_to_cart__allow_display_availability_error',
180
+				true,
181
+				$ticket,
182
+				$qty,
183
+				$available_spaces
184
+			)
185
+			) {
186
+				$this->availabilityError(
187
+					$available_spaces,
188
+					$total_ticket_count
189
+				);
190
+			}
191
+		} else {
192
+			EE_Error::add_error(
193
+				esc_html__(
194
+					'We\'re sorry, but there are no available spaces left for this event at this particular date and time.',
195
+					'event_espresso'
196
+				),
197
+				__FILE__, __FUNCTION__, __LINE__
198
+			);
199
+		}
200
+	}
201
+
202
+
203
+	/**
204
+	 * @param int $available_spaces
205
+	 * @param int $total_ticket_count
206
+	 */
207
+	private function availabilityError($available_spaces = 1, $total_ticket_count = 1)
208
+	{
209
+		// add error messaging - we're using the _n function that will generate
210
+		// the appropriate singular or plural message based on the number of $available_spaces
211
+		if ($total_ticket_count) {
212
+			$msg = sprintf(
213
+				esc_html(
214
+					_n(
215
+						'We\'re sorry, but there is only %1$s available space left for this event at this particular date and time. Please select a different number (or different combination) of tickets by cancelling the current selection and choosing again, or proceed to registration.',
216
+						'We\'re sorry, but there are only %1$s available spaces left for this event at this particular date and time. Please select a different number (or different combination) of tickets by cancelling the current selection and choosing again, or proceed to registration.',
217
+						$available_spaces,
218
+						'event_espresso'
219
+					)
220
+				),
221
+				$available_spaces,
222
+				'<br />'
223
+			);
224
+		} else {
225
+			$msg = sprintf(
226
+				esc_html(
227
+					_n(
228
+						'We\'re sorry, but there is only %1$s available space left for this event at this particular date and time. Please select a different number (or different combination) of tickets.',
229
+						'We\'re sorry, but there are only %1$s available spaces left for this event at this particular date and time. Please select a different number (or different combination) of tickets.',
230
+						$available_spaces,
231
+						'event_espresso'
232
+					)
233
+				),
234
+				$available_spaces,
235
+				'<br />'
236
+			);
237
+		}
238
+		EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
239
+	}
240 240
 }
Please login to merge, or discard this patch.
modules/ticket_selector/EED_Ticket_Selector.module.php 2 patches
Indentation   +464 added lines, -464 removed lines patch added patch discarded remove patch
@@ -20,470 +20,470 @@
 block discarded – undo
20 20
 class EED_Ticket_Selector extends EED_Module
21 21
 {
22 22
 
23
-    /**
24
-     * @var DisplayTicketSelector $ticket_selector
25
-     */
26
-    private static $ticket_selector;
27
-
28
-    /**
29
-     * @var TicketSelectorIframeEmbedButton $iframe_embed_button
30
-     */
31
-    private static $iframe_embed_button;
32
-
33
-
34
-
35
-    /**
36
-     * @return EED_Module|EED_Ticket_Selector
37
-     */
38
-    public static function instance()
39
-    {
40
-        return parent::get_instance(__CLASS__);
41
-    }
42
-
43
-
44
-
45
-    /**
46
-     * @return void
47
-     */
48
-    protected function set_config()
49
-    {
50
-        $this->set_config_section('template_settings');
51
-        $this->set_config_class('EE_Ticket_Selector_Config');
52
-        $this->set_config_name('EED_Ticket_Selector');
53
-    }
54
-
55
-
56
-
57
-    /**
58
-     *    set_hooks - for hooking into EE Core, other modules, etc
59
-     *
60
-     * @return void
61
-     */
62
-    public static function set_hooks()
63
-    {
64
-        // routing
65
-        EE_Config::register_route(
66
-            'iframe',
67
-            'EED_Ticket_Selector',
68
-            'ticket_selector_iframe',
69
-            'ticket_selector'
70
-        );
71
-        EE_Config::register_route(
72
-            'process_ticket_selections',
73
-            'EED_Ticket_Selector',
74
-            'process_ticket_selections'
75
-        );
76
-        EE_Config::register_route(
77
-            'cancel_ticket_selections',
78
-            'EED_Ticket_Selector',
79
-            'cancel_ticket_selections'
80
-        );
81
-        add_action('wp_loaded', array('EED_Ticket_Selector', 'set_definitions'), 2);
82
-        add_action('AHEE_event_details_header_bottom', array('EED_Ticket_Selector', 'display_ticket_selector'), 10, 1);
83
-        add_action('wp_enqueue_scripts', array('EED_Ticket_Selector', 'translate_js_strings'), 0);
84
-        add_action('wp_enqueue_scripts', array('EED_Ticket_Selector', 'load_tckt_slctr_assets'), 10);
85
-        EED_Ticket_Selector::loadIframeAssets();
86
-    }
87
-
88
-
89
-
90
-    /**
91
-     *    set_hooks_admin - for hooking into EE Admin Core, other modules, etc
92
-     *
93
-     * @return void
94
-     */
95
-    public static function set_hooks_admin()
96
-    {
97
-        // hook into the end of the \EE_Admin_Page::_load_page_dependencies()
98
-        // to load assets for "espresso_events" page on the "edit" route (action)
99
-        add_action(
100
-            'FHEE__EE_Admin_Page___load_page_dependencies__after_load__espresso_events__edit',
101
-            array('EED_Ticket_Selector', 'ticket_selector_iframe_embed_button'),
102
-            10
103
-        );
104
-        /**
105
-         * Make sure assets for the ticket selector are loaded on the espresso registrations route so  admin side
106
-         * registrations work.
107
-         */
108
-        add_action(
109
-            'FHEE__EE_Admin_Page___load_page_dependencies__after_load__espresso_registrations__new_registration',
110
-            array('EED_Ticket_Selector', 'set_definitions'),
111
-            10
112
-        );
113
-    }
114
-
115
-
116
-
117
-    /**
118
-     *    set_definitions
119
-     *
120
-     * @return void
121
-     * @throws InvalidArgumentException
122
-     * @throws InvalidDataTypeException
123
-     * @throws InvalidInterfaceException
124
-     */
125
-    public static function set_definitions()
126
-    {
127
-        // don't do this twice
128
-        if (defined('TICKET_SELECTOR_ASSETS_URL')) {
129
-            return;
130
-        }
131
-        define('TICKET_SELECTOR_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets' . DS);
132
-        define(
133
-            'TICKET_SELECTOR_TEMPLATES_PATH',
134
-            str_replace('\\', DS, plugin_dir_path(__FILE__)) . 'templates' . DS
135
-        );
136
-        //if config is not set, initialize
137
-        if (
138
-            ! EE_Registry::instance()->CFG->template_settings->EED_Ticket_Selector instanceof EE_Ticket_Selector_Config
139
-        ) {
140
-            EED_Ticket_Selector::instance()->set_config();
141
-            EE_Registry::instance()->CFG->template_settings->EED_Ticket_Selector = EED_Ticket_Selector::instance()->config();
142
-        }
143
-    }
144
-
145
-
146
-
147
-    /**
148
-     * @return DisplayTicketSelector
149
-     */
150
-    public static function ticketSelector()
151
-    {
152
-        if (! EED_Ticket_Selector::$ticket_selector instanceof DisplayTicketSelector) {
153
-            EED_Ticket_Selector::$ticket_selector = new DisplayTicketSelector(EED_Events_Archive::is_iframe());
154
-        }
155
-        return EED_Ticket_Selector::$ticket_selector;
156
-    }
157
-
158
-
159
-    /**
160
-     * gets the ball rolling
161
-     *
162
-     * @param WP $WP
163
-     * @return void
164
-     */
165
-    public function run($WP)
166
-    {
167
-    }
168
-
169
-
170
-
171
-    /**
172
-     * @return TicketSelectorIframeEmbedButton
173
-     */
174
-    public static function getIframeEmbedButton()
175
-    {
176
-        if (! self::$iframe_embed_button instanceof TicketSelectorIframeEmbedButton) {
177
-            self::$iframe_embed_button = new TicketSelectorIframeEmbedButton();
178
-        }
179
-        return self::$iframe_embed_button;
180
-    }
181
-
182
-
183
-
184
-    /**
185
-     * ticket_selector_iframe_embed_button
186
-     *
187
-     * @return void
188
-     * @throws EE_Error
189
-     */
190
-    public static function ticket_selector_iframe_embed_button()
191
-    {
192
-        $iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
193
-        $iframe_embed_button->addEventEditorIframeEmbedButton();
194
-    }
195
-
196
-
197
-
198
-    /**
199
-     * ticket_selector_iframe
200
-     *
201
-     * @return void
202
-     * @throws DomainException
203
-     * @throws EE_Error
204
-     */
205
-    public function ticket_selector_iframe()
206
-    {
207
-        $ticket_selector_iframe = new TicketSelectorIframe();
208
-        $ticket_selector_iframe->display();
209
-    }
210
-
211
-
212
-
213
-    /**
214
-     * creates buttons for selecting number of attendees for an event
215
-     *
216
-     * @param  WP_Post|int $event
217
-     * @param  bool        $view_details
218
-     * @return string
219
-     * @throws EE_Error
220
-     */
221
-    public static function display_ticket_selector($event = null, $view_details = false)
222
-    {
223
-        return EED_Ticket_Selector::ticketSelector()->display($event, $view_details);
224
-    }
225
-
226
-
227
-    /**
228
-     * @return array  or FALSE
229
-     * @throws \ReflectionException
230
-     * @throws \EE_Error
231
-     * @throws InvalidArgumentException
232
-     * @throws InvalidInterfaceException
233
-     * @throws InvalidDataTypeException
234
-     */
235
-    public function process_ticket_selections()
236
-    {
237
-        /** @var EventEspresso\modules\ticket_selector\ProcessTicketSelector $form */
238
-        $form = LoaderFactory::getLoader()->getShared('EventEspresso\modules\ticket_selector\ProcessTicketSelector');
239
-        return $form->processTicketSelections();
240
-    }
241
-
242
-
243
-    /**
244
-     * @return string
245
-     * @throws InvalidArgumentException
246
-     * @throws InvalidInterfaceException
247
-     * @throws InvalidDataTypeException
248
-     * @throws EE_Error
249
-     */
250
-    public static function cancel_ticket_selections()
251
-    {
252
-        /** @var EventEspresso\modules\ticket_selector\ProcessTicketSelector $form */
253
-        $form = LoaderFactory::getLoader()->getShared('EventEspresso\modules\ticket_selector\ProcessTicketSelector');
254
-        return $form->cancelTicketSelections();
255
-    }
256
-
257
-
258
-
259
-    /**
260
-     * @return void
261
-     */
262
-    public static function translate_js_strings()
263
-    {
264
-        EE_Registry::$i18n_js_strings['please_select_date_filter_notice'] = esc_html__(
265
-            'please select a datetime',
266
-            'event_espresso'
267
-        );
268
-    }
269
-
270
-
271
-
272
-    /**
273
-     * @return void
274
-     */
275
-    public static function load_tckt_slctr_assets()
276
-    {
277
-        if (apply_filters('FHEE__EED_Ticket_Selector__load_tckt_slctr_assets', false)) {
278
-            // add some style
279
-            wp_register_style(
280
-                'ticket_selector',
281
-                TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.css',
282
-                array(),
283
-                EVENT_ESPRESSO_VERSION
284
-            );
285
-            wp_enqueue_style('ticket_selector');
286
-            // make it dance
287
-            wp_register_script(
288
-                'ticket_selector',
289
-                TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.js',
290
-                array('espresso_core'),
291
-                EVENT_ESPRESSO_VERSION,
292
-                true
293
-            );
294
-            wp_enqueue_script('ticket_selector');
295
-            require_once EE_LIBRARIES
296
-                         . 'form_sections/strategies/display/EE_Checkbox_Dropdown_Selector_Display_Strategy.strategy.php';
297
-            \EE_Checkbox_Dropdown_Selector_Display_Strategy::enqueue_styles_and_scripts();
298
-        }
299
-    }
300
-
301
-
302
-
303
-    /**
304
-     * @return void
305
-     */
306
-    public static function loadIframeAssets()
307
-    {
308
-        // for event lists
309
-        add_filter(
310
-            'FHEE__EventEspresso_modules_events_archive_EventsArchiveIframe__display__css',
311
-            array('EED_Ticket_Selector', 'iframeCss')
312
-        );
313
-        add_filter(
314
-            'FHEE__EventEspresso_modules_events_archive_EventsArchiveIframe__display__js',
315
-            array('EED_Ticket_Selector', 'iframeJs')
316
-        );
317
-        // for ticket selectors
318
-        add_filter(
319
-            'FHEE__EED_Ticket_Selector__ticket_selector_iframe__css',
320
-            array('EED_Ticket_Selector', 'iframeCss')
321
-        );
322
-        add_filter(
323
-            'FHEE__EED_Ticket_Selector__ticket_selector_iframe__js',
324
-            array('EED_Ticket_Selector', 'iframeJs')
325
-        );
326
-    }
327
-
328
-
329
-
330
-    /**
331
-     * Informs the rest of the forms system what CSS and JS is needed to display the input
332
-     *
333
-     * @param array $iframe_css
334
-     * @return array
335
-     */
336
-    public static function iframeCss(array $iframe_css)
337
-    {
338
-        $iframe_css['ticket_selector'] = TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.css';
339
-        return $iframe_css;
340
-    }
341
-
342
-
343
-
344
-    /**
345
-     * Informs the rest of the forms system what CSS and JS is needed to display the input
346
-     *
347
-     * @param array $iframe_js
348
-     * @return array
349
-     */
350
-    public static function iframeJs(array $iframe_js)
351
-    {
352
-        $iframe_js['ticket_selector'] = TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.js';
353
-        return $iframe_js;
354
-    }
355
-
356
-
357
-    /****************************** DEPRECATED ******************************/
358
-
359
-
360
-
361
-    /**
362
-     * @deprecated
363
-     * @return string
364
-     * @throws EE_Error
365
-     */
366
-    public static function display_view_details_btn()
367
-    {
368
-        // todo add doing_it_wrong() notice during next major version
369
-        return EED_Ticket_Selector::ticketSelector()->displayViewDetailsButton();
370
-    }
371
-
372
-
373
-
374
-    /**
375
-     * @deprecated
376
-     * @return string
377
-     * @throws EE_Error
378
-     */
379
-    public static function display_ticket_selector_submit()
380
-    {
381
-        // todo add doing_it_wrong() notice during next major version
382
-        return EED_Ticket_Selector::ticketSelector()->displaySubmitButton();
383
-    }
384
-
385
-
386
-
387
-    /**
388
-     * @deprecated
389
-     * @param string $permalink_string
390
-     * @param int    $id
391
-     * @param string $new_title
392
-     * @param string $new_slug
393
-     * @return string
394
-     * @throws InvalidArgumentException
395
-     * @throws InvalidDataTypeException
396
-     * @throws InvalidInterfaceException
397
-     */
398
-    public static function iframe_code_button($permalink_string, $id, $new_title = '', $new_slug = '')
399
-    {
400
-        // todo add doing_it_wrong() notice during next major version
401
-        if (
402
-            EE_Registry::instance()->REQ->get('page') === 'espresso_events'
403
-            && EE_Registry::instance()->REQ->get('action') === 'edit'
404
-        ) {
405
-            $iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
406
-            $iframe_embed_button->addEventEditorIframeEmbedButton();
407
-        }
408
-        return '';
409
-    }
410
-
411
-
412
-
413
-    /**
414
-     * @deprecated
415
-     * @param int    $ID
416
-     * @param string $external_url
417
-     * @return string
418
-     */
419
-    public static function ticket_selector_form_open($ID = 0, $external_url = '')
420
-    {
421
-        // todo add doing_it_wrong() notice during next major version
422
-        return EED_Ticket_Selector::ticketSelector()->formOpen($ID, $external_url);
423
-    }
424
-
425
-
426
-
427
-    /**
428
-     * @deprecated
429
-     * @return string
430
-     */
431
-    public static function ticket_selector_form_close()
432
-    {
433
-        // todo add doing_it_wrong() notice during next major version
434
-        return EED_Ticket_Selector::ticketSelector()->formClose();
435
-    }
436
-
437
-
438
-
439
-    /**
440
-     * @deprecated
441
-     * @return string
442
-     */
443
-    public static function no_tkt_slctr_end_dv()
444
-    {
445
-        // todo add doing_it_wrong() notice during next major version
446
-        return EED_Ticket_Selector::ticketSelector()->ticketSelectorEndDiv();
447
-    }
448
-
449
-
450
-
451
-    /**
452
-     * @deprecated 4.9.13
453
-     * @return string
454
-     */
455
-    public static function tkt_slctr_end_dv()
456
-    {
457
-        return EED_Ticket_Selector::ticketSelector()->clearTicketSelector();
458
-    }
459
-
460
-
461
-
462
-    /**
463
-     * @deprecated
464
-     * @return string
465
-     */
466
-    public static function clear_tkt_slctr()
467
-    {
468
-        return EED_Ticket_Selector::ticketSelector()->clearTicketSelector();
469
-    }
470
-
471
-
472
-
473
-    /**
474
-     * @deprecated
475
-     */
476
-    public static function load_tckt_slctr_assets_admin()
477
-    {
478
-        // todo add doing_it_wrong() notice during next major version
479
-        if (
480
-            EE_Registry::instance()->REQ->get('page') === 'espresso_events'
481
-            && EE_Registry::instance()->REQ->get('action') === 'edit'
482
-        ) {
483
-            $iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
484
-            $iframe_embed_button->embedButtonAssets();
485
-        }
486
-    }
23
+	/**
24
+	 * @var DisplayTicketSelector $ticket_selector
25
+	 */
26
+	private static $ticket_selector;
27
+
28
+	/**
29
+	 * @var TicketSelectorIframeEmbedButton $iframe_embed_button
30
+	 */
31
+	private static $iframe_embed_button;
32
+
33
+
34
+
35
+	/**
36
+	 * @return EED_Module|EED_Ticket_Selector
37
+	 */
38
+	public static function instance()
39
+	{
40
+		return parent::get_instance(__CLASS__);
41
+	}
42
+
43
+
44
+
45
+	/**
46
+	 * @return void
47
+	 */
48
+	protected function set_config()
49
+	{
50
+		$this->set_config_section('template_settings');
51
+		$this->set_config_class('EE_Ticket_Selector_Config');
52
+		$this->set_config_name('EED_Ticket_Selector');
53
+	}
54
+
55
+
56
+
57
+	/**
58
+	 *    set_hooks - for hooking into EE Core, other modules, etc
59
+	 *
60
+	 * @return void
61
+	 */
62
+	public static function set_hooks()
63
+	{
64
+		// routing
65
+		EE_Config::register_route(
66
+			'iframe',
67
+			'EED_Ticket_Selector',
68
+			'ticket_selector_iframe',
69
+			'ticket_selector'
70
+		);
71
+		EE_Config::register_route(
72
+			'process_ticket_selections',
73
+			'EED_Ticket_Selector',
74
+			'process_ticket_selections'
75
+		);
76
+		EE_Config::register_route(
77
+			'cancel_ticket_selections',
78
+			'EED_Ticket_Selector',
79
+			'cancel_ticket_selections'
80
+		);
81
+		add_action('wp_loaded', array('EED_Ticket_Selector', 'set_definitions'), 2);
82
+		add_action('AHEE_event_details_header_bottom', array('EED_Ticket_Selector', 'display_ticket_selector'), 10, 1);
83
+		add_action('wp_enqueue_scripts', array('EED_Ticket_Selector', 'translate_js_strings'), 0);
84
+		add_action('wp_enqueue_scripts', array('EED_Ticket_Selector', 'load_tckt_slctr_assets'), 10);
85
+		EED_Ticket_Selector::loadIframeAssets();
86
+	}
87
+
88
+
89
+
90
+	/**
91
+	 *    set_hooks_admin - for hooking into EE Admin Core, other modules, etc
92
+	 *
93
+	 * @return void
94
+	 */
95
+	public static function set_hooks_admin()
96
+	{
97
+		// hook into the end of the \EE_Admin_Page::_load_page_dependencies()
98
+		// to load assets for "espresso_events" page on the "edit" route (action)
99
+		add_action(
100
+			'FHEE__EE_Admin_Page___load_page_dependencies__after_load__espresso_events__edit',
101
+			array('EED_Ticket_Selector', 'ticket_selector_iframe_embed_button'),
102
+			10
103
+		);
104
+		/**
105
+		 * Make sure assets for the ticket selector are loaded on the espresso registrations route so  admin side
106
+		 * registrations work.
107
+		 */
108
+		add_action(
109
+			'FHEE__EE_Admin_Page___load_page_dependencies__after_load__espresso_registrations__new_registration',
110
+			array('EED_Ticket_Selector', 'set_definitions'),
111
+			10
112
+		);
113
+	}
114
+
115
+
116
+
117
+	/**
118
+	 *    set_definitions
119
+	 *
120
+	 * @return void
121
+	 * @throws InvalidArgumentException
122
+	 * @throws InvalidDataTypeException
123
+	 * @throws InvalidInterfaceException
124
+	 */
125
+	public static function set_definitions()
126
+	{
127
+		// don't do this twice
128
+		if (defined('TICKET_SELECTOR_ASSETS_URL')) {
129
+			return;
130
+		}
131
+		define('TICKET_SELECTOR_ASSETS_URL', plugin_dir_url(__FILE__) . 'assets' . DS);
132
+		define(
133
+			'TICKET_SELECTOR_TEMPLATES_PATH',
134
+			str_replace('\\', DS, plugin_dir_path(__FILE__)) . 'templates' . DS
135
+		);
136
+		//if config is not set, initialize
137
+		if (
138
+			! EE_Registry::instance()->CFG->template_settings->EED_Ticket_Selector instanceof EE_Ticket_Selector_Config
139
+		) {
140
+			EED_Ticket_Selector::instance()->set_config();
141
+			EE_Registry::instance()->CFG->template_settings->EED_Ticket_Selector = EED_Ticket_Selector::instance()->config();
142
+		}
143
+	}
144
+
145
+
146
+
147
+	/**
148
+	 * @return DisplayTicketSelector
149
+	 */
150
+	public static function ticketSelector()
151
+	{
152
+		if (! EED_Ticket_Selector::$ticket_selector instanceof DisplayTicketSelector) {
153
+			EED_Ticket_Selector::$ticket_selector = new DisplayTicketSelector(EED_Events_Archive::is_iframe());
154
+		}
155
+		return EED_Ticket_Selector::$ticket_selector;
156
+	}
157
+
158
+
159
+	/**
160
+	 * gets the ball rolling
161
+	 *
162
+	 * @param WP $WP
163
+	 * @return void
164
+	 */
165
+	public function run($WP)
166
+	{
167
+	}
168
+
169
+
170
+
171
+	/**
172
+	 * @return TicketSelectorIframeEmbedButton
173
+	 */
174
+	public static function getIframeEmbedButton()
175
+	{
176
+		if (! self::$iframe_embed_button instanceof TicketSelectorIframeEmbedButton) {
177
+			self::$iframe_embed_button = new TicketSelectorIframeEmbedButton();
178
+		}
179
+		return self::$iframe_embed_button;
180
+	}
181
+
182
+
183
+
184
+	/**
185
+	 * ticket_selector_iframe_embed_button
186
+	 *
187
+	 * @return void
188
+	 * @throws EE_Error
189
+	 */
190
+	public static function ticket_selector_iframe_embed_button()
191
+	{
192
+		$iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
193
+		$iframe_embed_button->addEventEditorIframeEmbedButton();
194
+	}
195
+
196
+
197
+
198
+	/**
199
+	 * ticket_selector_iframe
200
+	 *
201
+	 * @return void
202
+	 * @throws DomainException
203
+	 * @throws EE_Error
204
+	 */
205
+	public function ticket_selector_iframe()
206
+	{
207
+		$ticket_selector_iframe = new TicketSelectorIframe();
208
+		$ticket_selector_iframe->display();
209
+	}
210
+
211
+
212
+
213
+	/**
214
+	 * creates buttons for selecting number of attendees for an event
215
+	 *
216
+	 * @param  WP_Post|int $event
217
+	 * @param  bool        $view_details
218
+	 * @return string
219
+	 * @throws EE_Error
220
+	 */
221
+	public static function display_ticket_selector($event = null, $view_details = false)
222
+	{
223
+		return EED_Ticket_Selector::ticketSelector()->display($event, $view_details);
224
+	}
225
+
226
+
227
+	/**
228
+	 * @return array  or FALSE
229
+	 * @throws \ReflectionException
230
+	 * @throws \EE_Error
231
+	 * @throws InvalidArgumentException
232
+	 * @throws InvalidInterfaceException
233
+	 * @throws InvalidDataTypeException
234
+	 */
235
+	public function process_ticket_selections()
236
+	{
237
+		/** @var EventEspresso\modules\ticket_selector\ProcessTicketSelector $form */
238
+		$form = LoaderFactory::getLoader()->getShared('EventEspresso\modules\ticket_selector\ProcessTicketSelector');
239
+		return $form->processTicketSelections();
240
+	}
241
+
242
+
243
+	/**
244
+	 * @return string
245
+	 * @throws InvalidArgumentException
246
+	 * @throws InvalidInterfaceException
247
+	 * @throws InvalidDataTypeException
248
+	 * @throws EE_Error
249
+	 */
250
+	public static function cancel_ticket_selections()
251
+	{
252
+		/** @var EventEspresso\modules\ticket_selector\ProcessTicketSelector $form */
253
+		$form = LoaderFactory::getLoader()->getShared('EventEspresso\modules\ticket_selector\ProcessTicketSelector');
254
+		return $form->cancelTicketSelections();
255
+	}
256
+
257
+
258
+
259
+	/**
260
+	 * @return void
261
+	 */
262
+	public static function translate_js_strings()
263
+	{
264
+		EE_Registry::$i18n_js_strings['please_select_date_filter_notice'] = esc_html__(
265
+			'please select a datetime',
266
+			'event_espresso'
267
+		);
268
+	}
269
+
270
+
271
+
272
+	/**
273
+	 * @return void
274
+	 */
275
+	public static function load_tckt_slctr_assets()
276
+	{
277
+		if (apply_filters('FHEE__EED_Ticket_Selector__load_tckt_slctr_assets', false)) {
278
+			// add some style
279
+			wp_register_style(
280
+				'ticket_selector',
281
+				TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.css',
282
+				array(),
283
+				EVENT_ESPRESSO_VERSION
284
+			);
285
+			wp_enqueue_style('ticket_selector');
286
+			// make it dance
287
+			wp_register_script(
288
+				'ticket_selector',
289
+				TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.js',
290
+				array('espresso_core'),
291
+				EVENT_ESPRESSO_VERSION,
292
+				true
293
+			);
294
+			wp_enqueue_script('ticket_selector');
295
+			require_once EE_LIBRARIES
296
+						 . 'form_sections/strategies/display/EE_Checkbox_Dropdown_Selector_Display_Strategy.strategy.php';
297
+			\EE_Checkbox_Dropdown_Selector_Display_Strategy::enqueue_styles_and_scripts();
298
+		}
299
+	}
300
+
301
+
302
+
303
+	/**
304
+	 * @return void
305
+	 */
306
+	public static function loadIframeAssets()
307
+	{
308
+		// for event lists
309
+		add_filter(
310
+			'FHEE__EventEspresso_modules_events_archive_EventsArchiveIframe__display__css',
311
+			array('EED_Ticket_Selector', 'iframeCss')
312
+		);
313
+		add_filter(
314
+			'FHEE__EventEspresso_modules_events_archive_EventsArchiveIframe__display__js',
315
+			array('EED_Ticket_Selector', 'iframeJs')
316
+		);
317
+		// for ticket selectors
318
+		add_filter(
319
+			'FHEE__EED_Ticket_Selector__ticket_selector_iframe__css',
320
+			array('EED_Ticket_Selector', 'iframeCss')
321
+		);
322
+		add_filter(
323
+			'FHEE__EED_Ticket_Selector__ticket_selector_iframe__js',
324
+			array('EED_Ticket_Selector', 'iframeJs')
325
+		);
326
+	}
327
+
328
+
329
+
330
+	/**
331
+	 * Informs the rest of the forms system what CSS and JS is needed to display the input
332
+	 *
333
+	 * @param array $iframe_css
334
+	 * @return array
335
+	 */
336
+	public static function iframeCss(array $iframe_css)
337
+	{
338
+		$iframe_css['ticket_selector'] = TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.css';
339
+		return $iframe_css;
340
+	}
341
+
342
+
343
+
344
+	/**
345
+	 * Informs the rest of the forms system what CSS and JS is needed to display the input
346
+	 *
347
+	 * @param array $iframe_js
348
+	 * @return array
349
+	 */
350
+	public static function iframeJs(array $iframe_js)
351
+	{
352
+		$iframe_js['ticket_selector'] = TICKET_SELECTOR_ASSETS_URL . 'ticket_selector.js';
353
+		return $iframe_js;
354
+	}
355
+
356
+
357
+	/****************************** DEPRECATED ******************************/
358
+
359
+
360
+
361
+	/**
362
+	 * @deprecated
363
+	 * @return string
364
+	 * @throws EE_Error
365
+	 */
366
+	public static function display_view_details_btn()
367
+	{
368
+		// todo add doing_it_wrong() notice during next major version
369
+		return EED_Ticket_Selector::ticketSelector()->displayViewDetailsButton();
370
+	}
371
+
372
+
373
+
374
+	/**
375
+	 * @deprecated
376
+	 * @return string
377
+	 * @throws EE_Error
378
+	 */
379
+	public static function display_ticket_selector_submit()
380
+	{
381
+		// todo add doing_it_wrong() notice during next major version
382
+		return EED_Ticket_Selector::ticketSelector()->displaySubmitButton();
383
+	}
384
+
385
+
386
+
387
+	/**
388
+	 * @deprecated
389
+	 * @param string $permalink_string
390
+	 * @param int    $id
391
+	 * @param string $new_title
392
+	 * @param string $new_slug
393
+	 * @return string
394
+	 * @throws InvalidArgumentException
395
+	 * @throws InvalidDataTypeException
396
+	 * @throws InvalidInterfaceException
397
+	 */
398
+	public static function iframe_code_button($permalink_string, $id, $new_title = '', $new_slug = '')
399
+	{
400
+		// todo add doing_it_wrong() notice during next major version
401
+		if (
402
+			EE_Registry::instance()->REQ->get('page') === 'espresso_events'
403
+			&& EE_Registry::instance()->REQ->get('action') === 'edit'
404
+		) {
405
+			$iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
406
+			$iframe_embed_button->addEventEditorIframeEmbedButton();
407
+		}
408
+		return '';
409
+	}
410
+
411
+
412
+
413
+	/**
414
+	 * @deprecated
415
+	 * @param int    $ID
416
+	 * @param string $external_url
417
+	 * @return string
418
+	 */
419
+	public static function ticket_selector_form_open($ID = 0, $external_url = '')
420
+	{
421
+		// todo add doing_it_wrong() notice during next major version
422
+		return EED_Ticket_Selector::ticketSelector()->formOpen($ID, $external_url);
423
+	}
424
+
425
+
426
+
427
+	/**
428
+	 * @deprecated
429
+	 * @return string
430
+	 */
431
+	public static function ticket_selector_form_close()
432
+	{
433
+		// todo add doing_it_wrong() notice during next major version
434
+		return EED_Ticket_Selector::ticketSelector()->formClose();
435
+	}
436
+
437
+
438
+
439
+	/**
440
+	 * @deprecated
441
+	 * @return string
442
+	 */
443
+	public static function no_tkt_slctr_end_dv()
444
+	{
445
+		// todo add doing_it_wrong() notice during next major version
446
+		return EED_Ticket_Selector::ticketSelector()->ticketSelectorEndDiv();
447
+	}
448
+
449
+
450
+
451
+	/**
452
+	 * @deprecated 4.9.13
453
+	 * @return string
454
+	 */
455
+	public static function tkt_slctr_end_dv()
456
+	{
457
+		return EED_Ticket_Selector::ticketSelector()->clearTicketSelector();
458
+	}
459
+
460
+
461
+
462
+	/**
463
+	 * @deprecated
464
+	 * @return string
465
+	 */
466
+	public static function clear_tkt_slctr()
467
+	{
468
+		return EED_Ticket_Selector::ticketSelector()->clearTicketSelector();
469
+	}
470
+
471
+
472
+
473
+	/**
474
+	 * @deprecated
475
+	 */
476
+	public static function load_tckt_slctr_assets_admin()
477
+	{
478
+		// todo add doing_it_wrong() notice during next major version
479
+		if (
480
+			EE_Registry::instance()->REQ->get('page') === 'espresso_events'
481
+			&& EE_Registry::instance()->REQ->get('action') === 'edit'
482
+		) {
483
+			$iframe_embed_button = EED_Ticket_Selector::getIframeEmbedButton();
484
+			$iframe_embed_button->embedButtonAssets();
485
+		}
486
+	}
487 487
 
488 488
 
489 489
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -241,7 +241,7 @@
 block discarded – undo
241 241
 
242 242
 
243 243
     /**
244
-     * @return string
244
+     * @return boolean
245 245
      * @throws InvalidArgumentException
246 246
      * @throws InvalidInterfaceException
247 247
      * @throws InvalidDataTypeException
Please login to merge, or discard this patch.
attendee_information/EE_SPCO_Reg_Step_Attendee_Information.class.php 3 patches
Doc Comments   +2 added lines, -1 removed lines patch added patch discarded remove patch
@@ -211,7 +211,7 @@  discard block
 block discarded – undo
211 211
 
212 212
     /**
213 213
      * @param EE_Registration $registration
214
-     * @return EE_Form_Section_Base
214
+     * @return EE_Form_Section_Proper|null
215 215
      * @throws EE_Error
216 216
      * @throws InvalidArgumentException
217 217
      * @throws \EventEspresso\core\exceptions\EntityNotFoundException
@@ -610,6 +610,7 @@  discard block
 block discarded – undo
610 610
      * @param EE_Registration $registration
611 611
      * @param EE_Question     $question
612 612
      * @param                 mixed EE_Answer|NULL      $answer
613
+     * @param EE_Answer $answer
613 614
      * @return EE_Form_Input_Base
614 615
      * @throws \EE_Error
615 616
      */
Please login to merge, or discard this patch.
Indentation   +1338 added lines, -1338 removed lines patch added patch discarded remove patch
@@ -17,1346 +17,1346 @@
 block discarded – undo
17 17
 class EE_SPCO_Reg_Step_Attendee_Information extends EE_SPCO_Reg_Step
18 18
 {
19 19
 
20
-    /**
21
-     * @type bool $_print_copy_info
22
-     */
23
-    private $_print_copy_info = false;
24
-
25
-    /**
26
-     * @type array $_attendee_data
27
-     */
28
-    private $_attendee_data = array();
29
-
30
-    /**
31
-     * @type array $_required_questions
32
-     */
33
-    private $_required_questions = array();
34
-
35
-    /**
36
-     * @type array $_registration_answers
37
-     */
38
-    private $_registration_answers = array();
39
-
40
-
41
-    /**
42
-     *    class constructor
43
-     *
44
-     * @access    public
45
-     * @param    EE_Checkout $checkout
46
-     */
47
-    public function __construct(EE_Checkout $checkout)
48
-    {
49
-        $this->_slug     = 'attendee_information';
50
-        $this->_name     = esc_html__('Attendee Information', 'event_espresso');
51
-        $this->_template = SPCO_REG_STEPS_PATH . $this->_slug . DS . 'attendee_info_main.template.php';
52
-        $this->checkout  = $checkout;
53
-        $this->_reset_success_message();
54
-        $this->set_instructions(
55
-            esc_html__('Please answer the following registration questions before proceeding.', 'event_espresso')
56
-        );
57
-    }
58
-
59
-
60
-    public function translate_js_strings()
61
-    {
62
-        EE_Registry::$i18n_js_strings['required_field']            = esc_html__(
63
-            ' is a required question.',
64
-            'event_espresso'
65
-        );
66
-        EE_Registry::$i18n_js_strings['required_multi_field']      = esc_html__(
67
-            ' is a required question. Please enter a value for at least one of the options.',
68
-            'event_espresso'
69
-        );
70
-        EE_Registry::$i18n_js_strings['answer_required_questions'] = esc_html__(
71
-            'Please answer all required questions correctly before proceeding.',
72
-            'event_espresso'
73
-        );
74
-        EE_Registry::$i18n_js_strings['attendee_info_copied']      = sprintf(
75
-            esc_html__(
76
-                'The attendee information was successfully copied.%sPlease ensure the rest of the registration form is completed before proceeding.',
77
-                'event_espresso'
78
-            ),
79
-            '<br/>'
80
-        );
81
-        EE_Registry::$i18n_js_strings['attendee_info_copy_error']  = esc_html__(
82
-            'An unknown error occurred on the server while attempting to copy the attendee information. Please refresh the page and try again.',
83
-            'event_espresso'
84
-        );
85
-        EE_Registry::$i18n_js_strings['enter_valid_email']         = esc_html__(
86
-            'You must enter a valid email address.',
87
-            'event_espresso'
88
-        );
89
-        EE_Registry::$i18n_js_strings['valid_email_and_questions'] = esc_html__(
90
-            'You must enter a valid email address and answer all other required questions before you can proceed.',
91
-            'event_espresso'
92
-        );
93
-    }
94
-
95
-
96
-    public function enqueue_styles_and_scripts()
97
-    {
98
-    }
99
-
100
-
101
-    /**
102
-     * @return boolean
103
-     */
104
-    public function initialize_reg_step()
105
-    {
106
-        return true;
107
-    }
108
-
109
-
110
-    /**
111
-     * @return EE_Form_Section_Proper
112
-     * @throws EE_Error
113
-     * @throws InvalidArgumentException
114
-     * @throws \EventEspresso\core\exceptions\EntityNotFoundException
115
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
116
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
117
-     */
118
-    public function generate_reg_form()
119
-    {
120
-        $this->_print_copy_info = false;
121
-        $primary_registrant     = null;
122
-        // autoload Line_Item_Display classes
123
-        EEH_Autoloader::register_line_item_display_autoloaders();
124
-        $Line_Item_Display = new EE_Line_Item_Display();
125
-        // calculate taxes
126
-        $Line_Item_Display->display_line_item(
127
-            $this->checkout->cart->get_grand_total(),
128
-            array('set_tax_rate' => true)
129
-        );
130
-        /** @var $subsections EE_Form_Section_Proper[] */
131
-        $subsections   = array(
132
-            'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
133
-        );
134
-        $template_args = array(
135
-            'revisit'       => $this->checkout->revisit,
136
-            'registrations' => array(),
137
-            'ticket_count'  => array(),
138
-        );
139
-        // grab the saved registrations from the transaction
140
-        $registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
141
-        if ($registrations) {
142
-            foreach ($registrations as $registration) {
143
-                // can this registration be processed during this visit ?
144
-                if ($registration instanceof EE_Registration
145
-                    && $this->checkout->visit_allows_processing_of_this_registration($registration)
146
-                ) {
147
-                    $subsection = $this->_registrations_reg_form($registration);
148
-                    if(!$subsection instanceof EE_Form_Section_Proper) {
149
-                        continue;
150
-                    }
151
-                    $subsections[ $registration->reg_url_link() ] = $subsection;
152
-                    if (! $this->checkout->admin_request) {
153
-                        $template_args['registrations'][$registration->reg_url_link()]    = $registration;
154
-                        $template_args['ticket_count'][$registration->ticket()->ID()]     = isset(
155
-                            $template_args['ticket_count'][$registration->ticket()->ID()]
156
-                        )
157
-                            ? $template_args['ticket_count'][$registration->ticket()->ID()] + 1
158
-                            : 1;
159
-                        $ticket_line_item = EEH_Line_Item::get_line_items_by_object_type_and_IDs(
160
-                            $this->checkout->cart->get_grand_total(),
161
-                            'Ticket',
162
-                            array($registration->ticket()->ID())
163
-                        );
164
-                        $ticket_line_item = is_array($ticket_line_item)
165
-                            ? reset($ticket_line_item)
166
-                            : $ticket_line_item;
167
-                        $template_args['ticket_line_item'][$registration->ticket()->ID()] =
168
-                            $Line_Item_Display->display_line_item($ticket_line_item);
169
-                    }
170
-                    if ($registration->is_primary_registrant()) {
171
-                        $primary_registrant = $registration->reg_url_link();
172
-                    }
173
-                }
174
-            }
175
-            // print_copy_info ?
176
-            if ($primary_registrant && ! $this->checkout->admin_request && count($registrations) > 1) {
177
-                // TODO: add admin option for toggling copy attendee info,
178
-                // then use that value to change $this->_print_copy_info
179
-                $copy_options['spco_copy_attendee_chk'] = $this->_print_copy_info
180
-                    ? $this->_copy_attendee_info_form()
181
-                    : $this->_auto_copy_attendee_info();
182
-                // generate hidden input
183
-                if (isset($subsections[$primary_registrant])
184
-                    && $subsections[$primary_registrant] instanceof EE_Form_Section_Proper
185
-                ) {
186
-                    $subsections[$primary_registrant]->add_subsections(
187
-                        $copy_options,
188
-                        'primary_registrant',
189
-                        false
190
-                    );
191
-                }
192
-            }
193
-        }
194
-        return new EE_Form_Section_Proper(
195
-            array(
196
-                'name'            => $this->reg_form_name(),
197
-                'html_id'         => $this->reg_form_name(),
198
-                'subsections'     => $subsections,
199
-                'layout_strategy' => $this->checkout->admin_request ?
200
-                    new EE_Div_Per_Section_Layout() :
201
-                    new EE_Template_Layout(
202
-                        array(
203
-                            'layout_template_file' => $this->_template, // layout_template
204
-                            'template_args'        => $template_args,
205
-                        )
206
-                    ),
207
-            )
208
-        );
209
-    }
210
-
211
-
212
-    /**
213
-     * @param EE_Registration $registration
214
-     * @return EE_Form_Section_Base
215
-     * @throws EE_Error
216
-     * @throws InvalidArgumentException
217
-     * @throws \EventEspresso\core\exceptions\EntityNotFoundException
218
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
219
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
220
-     */
221
-    private function _registrations_reg_form(EE_Registration $registration)
222
-    {
223
-        static $attendee_nmbr = 1;
224
-        $form_args = array();
225
-        // verify that registration has valid event
226
-        if ($registration->event() instanceof EE_Event) {
227
-            $question_groups = $registration->event()->question_groups(
228
-                apply_filters(
229
-                    'FHEE__EE_SPCO_Reg_Step_Attendee_Information___registrations_reg_form__question_groups_query_parameters',
230
-                    array(
231
-                        array(
232
-                            'Event.EVT_ID'                     => $registration->event()->ID(),
233
-                            'Event_Question_Group.EQG_primary' => $registration->count() === 1 ? true : false,
234
-                        ),
235
-                        'order_by' => array('QSG_order' => 'ASC'),
236
-                    ),
237
-                    $registration,
238
-                    $this
239
-                )
240
-            );
241
-            if ($question_groups) {
242
-                // array of params to pass to parent constructor
243
-                $form_args = array(
244
-                    'html_id'         => 'ee-registration-' . $registration->reg_url_link(),
245
-                    'html_class'      => 'ee-reg-form-attendee-dv',
246
-                    'html_style'      => $this->checkout->admin_request
247
-                        ? 'padding:0em 2em 1em; margin:3em 0 0; border:1px solid #ddd;'
248
-                        : '',
249
-                    'subsections'     => array(),
250
-                    'layout_strategy' => new EE_Fieldset_Section_Layout(
251
-                        array(
252
-                            'legend_class' => 'spco-attendee-lgnd smaller-text lt-grey-text',
253
-                            'legend_text'  => sprintf(__('Attendee %d', 'event_espresso'), $attendee_nmbr),
254
-                        )
255
-                    ),
256
-                );
257
-                foreach ($question_groups as $question_group) {
258
-                    if ($question_group instanceof EE_Question_Group) {
259
-                        $form_args['subsections'][$question_group->identifier()] = $this->_question_group_reg_form(
260
-                            $registration,
261
-                            $question_group
262
-                        );
263
-                    }
264
-                }
265
-                // add hidden input
266
-                $form_args['subsections']['additional_attendee_reg_info'] = $this->_additional_attendee_reg_info_input(
267
-                    $registration
268
-                );
269
-                // if we have question groups for additional attendees, then display the copy options
270
-                $this->_print_copy_info = $attendee_nmbr > 1 ? true : $this->_print_copy_info;
271
-                if ($registration->is_primary_registrant()) {
272
-                    // generate hidden input
273
-                    $form_args['subsections']['primary_registrant'] = $this->_additional_primary_registrant_inputs(
274
-                        $registration
275
-                    );
276
-                }
277
-            }
278
-        }
279
-        $attendee_nmbr++;
280
-        return ! empty($form_args) ? new EE_Form_Section_Proper($form_args) : null;
281
-    }
282
-
283
-
284
-    /**
285
-     * _additional_attendee_reg_info_input
286
-     *
287
-     * @access public
288
-     * @param EE_Registration $registration
289
-     * @param bool            $additional_attendee_reg_info
290
-     * @return    EE_Form_Input_Base
291
-     * @throws \EE_Error
292
-     */
293
-    private function _additional_attendee_reg_info_input(
294
-        EE_Registration $registration,
295
-        $additional_attendee_reg_info = true
296
-    ) {
297
-        // generate hidden input
298
-        return new EE_Hidden_Input(
299
-            array(
300
-                'html_id' => 'additional-attendee-reg-info-' . $registration->reg_url_link(),
301
-                'default' => $additional_attendee_reg_info,
302
-            )
303
-        );
304
-    }
305
-
306
-
307
-    /**
308
-     * @param EE_Registration   $registration
309
-     * @param EE_Question_Group $question_group
310
-     * @return EE_Form_Section_Proper
311
-     * @throws EE_Error
312
-     * @throws InvalidArgumentException
313
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
314
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
315
-     */
316
-    private function _question_group_reg_form(EE_Registration $registration, EE_Question_Group $question_group)
317
-    {
318
-        // array of params to pass to parent constructor
319
-        $form_args = array(
320
-            'html_id'         => 'ee-reg-form-qstn-grp-' . $question_group->identifier() . '-' . $registration->ID(),
321
-            'html_class'      => $this->checkout->admin_request
322
-                ? 'form-table ee-reg-form-qstn-grp-dv'
323
-                : 'ee-reg-form-qstn-grp-dv',
324
-            'html_label_id'   => 'ee-reg-form-qstn-grp-' . $question_group->identifier() .  '-' . $registration->ID() . '-lbl',
325
-            'subsections'     => array(
326
-                'reg_form_qstn_grp_hdr' => $this->_question_group_header($question_group),
327
-            ),
328
-            'layout_strategy' => $this->checkout->admin_request
329
-                ? new EE_Admin_Two_Column_Layout()
330
-                : new EE_Div_Per_Section_Layout(),
331
-        );
332
-        // where params
333
-        $query_params = array('QST_deleted' => 0);
334
-        // don't load admin only questions on the frontend
335
-        if (! $this->checkout->admin_request) {
336
-            $query_params['QST_admin_only'] = array('!=', true);
337
-        }
338
-        $questions = $question_group->get_many_related(
339
-            'Question',
340
-            apply_filters(
341
-                'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__related_questions_query_params',
342
-                array(
343
-                    $query_params,
344
-                    'order_by' => array(
345
-                        'Question_Group_Question.QGQ_order' => 'ASC',
346
-                    ),
347
-                ),
348
-                $question_group,
349
-                $registration,
350
-                $this
351
-            )
352
-        );
353
-        // filter for additional content before questions
354
-        $form_args['subsections']['reg_form_questions_before'] = new EE_Form_Section_HTML(
355
-            apply_filters(
356
-                'FHEE__EEH_Form_Fields__generate_question_groups_html__before_question_group_questions',
357
-                '',
358
-                $registration,
359
-                $question_group,
360
-                $this
361
-            )
362
-        );
363
-        // loop thru questions
364
-        foreach ($questions as $question) {
365
-            if ($question instanceof EE_Question) {
366
-                $identifier                            = $question->is_system_question()
367
-                    ? $question->system_ID()
368
-                    : $question->ID();
369
-                $form_args['subsections'][$identifier] = $this->reg_form_question($registration, $question);
370
-            }
371
-        }
372
-        $form_args['subsections'] = apply_filters(
373
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information__question_group_reg_form__subsections_array',
374
-            $form_args['subsections'],
375
-            $registration,
376
-            $question_group,
377
-            $this
378
-        );
379
-        // filter for additional content after questions
380
-        $form_args['subsections']['reg_form_questions_after'] = new EE_Form_Section_HTML(
381
-            apply_filters(
382
-                'FHEE__EEH_Form_Fields__generate_question_groups_html__after_question_group_questions',
383
-                '',
384
-                $registration,
385
-                $question_group,
386
-                $this
387
-            )
388
-        );
20
+	/**
21
+	 * @type bool $_print_copy_info
22
+	 */
23
+	private $_print_copy_info = false;
24
+
25
+	/**
26
+	 * @type array $_attendee_data
27
+	 */
28
+	private $_attendee_data = array();
29
+
30
+	/**
31
+	 * @type array $_required_questions
32
+	 */
33
+	private $_required_questions = array();
34
+
35
+	/**
36
+	 * @type array $_registration_answers
37
+	 */
38
+	private $_registration_answers = array();
39
+
40
+
41
+	/**
42
+	 *    class constructor
43
+	 *
44
+	 * @access    public
45
+	 * @param    EE_Checkout $checkout
46
+	 */
47
+	public function __construct(EE_Checkout $checkout)
48
+	{
49
+		$this->_slug     = 'attendee_information';
50
+		$this->_name     = esc_html__('Attendee Information', 'event_espresso');
51
+		$this->_template = SPCO_REG_STEPS_PATH . $this->_slug . DS . 'attendee_info_main.template.php';
52
+		$this->checkout  = $checkout;
53
+		$this->_reset_success_message();
54
+		$this->set_instructions(
55
+			esc_html__('Please answer the following registration questions before proceeding.', 'event_espresso')
56
+		);
57
+	}
58
+
59
+
60
+	public function translate_js_strings()
61
+	{
62
+		EE_Registry::$i18n_js_strings['required_field']            = esc_html__(
63
+			' is a required question.',
64
+			'event_espresso'
65
+		);
66
+		EE_Registry::$i18n_js_strings['required_multi_field']      = esc_html__(
67
+			' is a required question. Please enter a value for at least one of the options.',
68
+			'event_espresso'
69
+		);
70
+		EE_Registry::$i18n_js_strings['answer_required_questions'] = esc_html__(
71
+			'Please answer all required questions correctly before proceeding.',
72
+			'event_espresso'
73
+		);
74
+		EE_Registry::$i18n_js_strings['attendee_info_copied']      = sprintf(
75
+			esc_html__(
76
+				'The attendee information was successfully copied.%sPlease ensure the rest of the registration form is completed before proceeding.',
77
+				'event_espresso'
78
+			),
79
+			'<br/>'
80
+		);
81
+		EE_Registry::$i18n_js_strings['attendee_info_copy_error']  = esc_html__(
82
+			'An unknown error occurred on the server while attempting to copy the attendee information. Please refresh the page and try again.',
83
+			'event_espresso'
84
+		);
85
+		EE_Registry::$i18n_js_strings['enter_valid_email']         = esc_html__(
86
+			'You must enter a valid email address.',
87
+			'event_espresso'
88
+		);
89
+		EE_Registry::$i18n_js_strings['valid_email_and_questions'] = esc_html__(
90
+			'You must enter a valid email address and answer all other required questions before you can proceed.',
91
+			'event_espresso'
92
+		);
93
+	}
94
+
95
+
96
+	public function enqueue_styles_and_scripts()
97
+	{
98
+	}
99
+
100
+
101
+	/**
102
+	 * @return boolean
103
+	 */
104
+	public function initialize_reg_step()
105
+	{
106
+		return true;
107
+	}
108
+
109
+
110
+	/**
111
+	 * @return EE_Form_Section_Proper
112
+	 * @throws EE_Error
113
+	 * @throws InvalidArgumentException
114
+	 * @throws \EventEspresso\core\exceptions\EntityNotFoundException
115
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
116
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
117
+	 */
118
+	public function generate_reg_form()
119
+	{
120
+		$this->_print_copy_info = false;
121
+		$primary_registrant     = null;
122
+		// autoload Line_Item_Display classes
123
+		EEH_Autoloader::register_line_item_display_autoloaders();
124
+		$Line_Item_Display = new EE_Line_Item_Display();
125
+		// calculate taxes
126
+		$Line_Item_Display->display_line_item(
127
+			$this->checkout->cart->get_grand_total(),
128
+			array('set_tax_rate' => true)
129
+		);
130
+		/** @var $subsections EE_Form_Section_Proper[] */
131
+		$subsections   = array(
132
+			'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
133
+		);
134
+		$template_args = array(
135
+			'revisit'       => $this->checkout->revisit,
136
+			'registrations' => array(),
137
+			'ticket_count'  => array(),
138
+		);
139
+		// grab the saved registrations from the transaction
140
+		$registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
141
+		if ($registrations) {
142
+			foreach ($registrations as $registration) {
143
+				// can this registration be processed during this visit ?
144
+				if ($registration instanceof EE_Registration
145
+					&& $this->checkout->visit_allows_processing_of_this_registration($registration)
146
+				) {
147
+					$subsection = $this->_registrations_reg_form($registration);
148
+					if(!$subsection instanceof EE_Form_Section_Proper) {
149
+						continue;
150
+					}
151
+					$subsections[ $registration->reg_url_link() ] = $subsection;
152
+					if (! $this->checkout->admin_request) {
153
+						$template_args['registrations'][$registration->reg_url_link()]    = $registration;
154
+						$template_args['ticket_count'][$registration->ticket()->ID()]     = isset(
155
+							$template_args['ticket_count'][$registration->ticket()->ID()]
156
+						)
157
+							? $template_args['ticket_count'][$registration->ticket()->ID()] + 1
158
+							: 1;
159
+						$ticket_line_item = EEH_Line_Item::get_line_items_by_object_type_and_IDs(
160
+							$this->checkout->cart->get_grand_total(),
161
+							'Ticket',
162
+							array($registration->ticket()->ID())
163
+						);
164
+						$ticket_line_item = is_array($ticket_line_item)
165
+							? reset($ticket_line_item)
166
+							: $ticket_line_item;
167
+						$template_args['ticket_line_item'][$registration->ticket()->ID()] =
168
+							$Line_Item_Display->display_line_item($ticket_line_item);
169
+					}
170
+					if ($registration->is_primary_registrant()) {
171
+						$primary_registrant = $registration->reg_url_link();
172
+					}
173
+				}
174
+			}
175
+			// print_copy_info ?
176
+			if ($primary_registrant && ! $this->checkout->admin_request && count($registrations) > 1) {
177
+				// TODO: add admin option for toggling copy attendee info,
178
+				// then use that value to change $this->_print_copy_info
179
+				$copy_options['spco_copy_attendee_chk'] = $this->_print_copy_info
180
+					? $this->_copy_attendee_info_form()
181
+					: $this->_auto_copy_attendee_info();
182
+				// generate hidden input
183
+				if (isset($subsections[$primary_registrant])
184
+					&& $subsections[$primary_registrant] instanceof EE_Form_Section_Proper
185
+				) {
186
+					$subsections[$primary_registrant]->add_subsections(
187
+						$copy_options,
188
+						'primary_registrant',
189
+						false
190
+					);
191
+				}
192
+			}
193
+		}
194
+		return new EE_Form_Section_Proper(
195
+			array(
196
+				'name'            => $this->reg_form_name(),
197
+				'html_id'         => $this->reg_form_name(),
198
+				'subsections'     => $subsections,
199
+				'layout_strategy' => $this->checkout->admin_request ?
200
+					new EE_Div_Per_Section_Layout() :
201
+					new EE_Template_Layout(
202
+						array(
203
+							'layout_template_file' => $this->_template, // layout_template
204
+							'template_args'        => $template_args,
205
+						)
206
+					),
207
+			)
208
+		);
209
+	}
210
+
211
+
212
+	/**
213
+	 * @param EE_Registration $registration
214
+	 * @return EE_Form_Section_Base
215
+	 * @throws EE_Error
216
+	 * @throws InvalidArgumentException
217
+	 * @throws \EventEspresso\core\exceptions\EntityNotFoundException
218
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
219
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
220
+	 */
221
+	private function _registrations_reg_form(EE_Registration $registration)
222
+	{
223
+		static $attendee_nmbr = 1;
224
+		$form_args = array();
225
+		// verify that registration has valid event
226
+		if ($registration->event() instanceof EE_Event) {
227
+			$question_groups = $registration->event()->question_groups(
228
+				apply_filters(
229
+					'FHEE__EE_SPCO_Reg_Step_Attendee_Information___registrations_reg_form__question_groups_query_parameters',
230
+					array(
231
+						array(
232
+							'Event.EVT_ID'                     => $registration->event()->ID(),
233
+							'Event_Question_Group.EQG_primary' => $registration->count() === 1 ? true : false,
234
+						),
235
+						'order_by' => array('QSG_order' => 'ASC'),
236
+					),
237
+					$registration,
238
+					$this
239
+				)
240
+			);
241
+			if ($question_groups) {
242
+				// array of params to pass to parent constructor
243
+				$form_args = array(
244
+					'html_id'         => 'ee-registration-' . $registration->reg_url_link(),
245
+					'html_class'      => 'ee-reg-form-attendee-dv',
246
+					'html_style'      => $this->checkout->admin_request
247
+						? 'padding:0em 2em 1em; margin:3em 0 0; border:1px solid #ddd;'
248
+						: '',
249
+					'subsections'     => array(),
250
+					'layout_strategy' => new EE_Fieldset_Section_Layout(
251
+						array(
252
+							'legend_class' => 'spco-attendee-lgnd smaller-text lt-grey-text',
253
+							'legend_text'  => sprintf(__('Attendee %d', 'event_espresso'), $attendee_nmbr),
254
+						)
255
+					),
256
+				);
257
+				foreach ($question_groups as $question_group) {
258
+					if ($question_group instanceof EE_Question_Group) {
259
+						$form_args['subsections'][$question_group->identifier()] = $this->_question_group_reg_form(
260
+							$registration,
261
+							$question_group
262
+						);
263
+					}
264
+				}
265
+				// add hidden input
266
+				$form_args['subsections']['additional_attendee_reg_info'] = $this->_additional_attendee_reg_info_input(
267
+					$registration
268
+				);
269
+				// if we have question groups for additional attendees, then display the copy options
270
+				$this->_print_copy_info = $attendee_nmbr > 1 ? true : $this->_print_copy_info;
271
+				if ($registration->is_primary_registrant()) {
272
+					// generate hidden input
273
+					$form_args['subsections']['primary_registrant'] = $this->_additional_primary_registrant_inputs(
274
+						$registration
275
+					);
276
+				}
277
+			}
278
+		}
279
+		$attendee_nmbr++;
280
+		return ! empty($form_args) ? new EE_Form_Section_Proper($form_args) : null;
281
+	}
282
+
283
+
284
+	/**
285
+	 * _additional_attendee_reg_info_input
286
+	 *
287
+	 * @access public
288
+	 * @param EE_Registration $registration
289
+	 * @param bool            $additional_attendee_reg_info
290
+	 * @return    EE_Form_Input_Base
291
+	 * @throws \EE_Error
292
+	 */
293
+	private function _additional_attendee_reg_info_input(
294
+		EE_Registration $registration,
295
+		$additional_attendee_reg_info = true
296
+	) {
297
+		// generate hidden input
298
+		return new EE_Hidden_Input(
299
+			array(
300
+				'html_id' => 'additional-attendee-reg-info-' . $registration->reg_url_link(),
301
+				'default' => $additional_attendee_reg_info,
302
+			)
303
+		);
304
+	}
305
+
306
+
307
+	/**
308
+	 * @param EE_Registration   $registration
309
+	 * @param EE_Question_Group $question_group
310
+	 * @return EE_Form_Section_Proper
311
+	 * @throws EE_Error
312
+	 * @throws InvalidArgumentException
313
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
314
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
315
+	 */
316
+	private function _question_group_reg_form(EE_Registration $registration, EE_Question_Group $question_group)
317
+	{
318
+		// array of params to pass to parent constructor
319
+		$form_args = array(
320
+			'html_id'         => 'ee-reg-form-qstn-grp-' . $question_group->identifier() . '-' . $registration->ID(),
321
+			'html_class'      => $this->checkout->admin_request
322
+				? 'form-table ee-reg-form-qstn-grp-dv'
323
+				: 'ee-reg-form-qstn-grp-dv',
324
+			'html_label_id'   => 'ee-reg-form-qstn-grp-' . $question_group->identifier() .  '-' . $registration->ID() . '-lbl',
325
+			'subsections'     => array(
326
+				'reg_form_qstn_grp_hdr' => $this->_question_group_header($question_group),
327
+			),
328
+			'layout_strategy' => $this->checkout->admin_request
329
+				? new EE_Admin_Two_Column_Layout()
330
+				: new EE_Div_Per_Section_Layout(),
331
+		);
332
+		// where params
333
+		$query_params = array('QST_deleted' => 0);
334
+		// don't load admin only questions on the frontend
335
+		if (! $this->checkout->admin_request) {
336
+			$query_params['QST_admin_only'] = array('!=', true);
337
+		}
338
+		$questions = $question_group->get_many_related(
339
+			'Question',
340
+			apply_filters(
341
+				'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__related_questions_query_params',
342
+				array(
343
+					$query_params,
344
+					'order_by' => array(
345
+						'Question_Group_Question.QGQ_order' => 'ASC',
346
+					),
347
+				),
348
+				$question_group,
349
+				$registration,
350
+				$this
351
+			)
352
+		);
353
+		// filter for additional content before questions
354
+		$form_args['subsections']['reg_form_questions_before'] = new EE_Form_Section_HTML(
355
+			apply_filters(
356
+				'FHEE__EEH_Form_Fields__generate_question_groups_html__before_question_group_questions',
357
+				'',
358
+				$registration,
359
+				$question_group,
360
+				$this
361
+			)
362
+		);
363
+		// loop thru questions
364
+		foreach ($questions as $question) {
365
+			if ($question instanceof EE_Question) {
366
+				$identifier                            = $question->is_system_question()
367
+					? $question->system_ID()
368
+					: $question->ID();
369
+				$form_args['subsections'][$identifier] = $this->reg_form_question($registration, $question);
370
+			}
371
+		}
372
+		$form_args['subsections'] = apply_filters(
373
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information__question_group_reg_form__subsections_array',
374
+			$form_args['subsections'],
375
+			$registration,
376
+			$question_group,
377
+			$this
378
+		);
379
+		// filter for additional content after questions
380
+		$form_args['subsections']['reg_form_questions_after'] = new EE_Form_Section_HTML(
381
+			apply_filters(
382
+				'FHEE__EEH_Form_Fields__generate_question_groups_html__after_question_group_questions',
383
+				'',
384
+				$registration,
385
+				$question_group,
386
+				$this
387
+			)
388
+		);
389 389
 //		d( $form_args );
390
-        $question_group_reg_form = new EE_Form_Section_Proper($form_args);
391
-        return apply_filters(
392
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
393
-            $question_group_reg_form,
394
-            $registration,
395
-            $question_group,
396
-            $this
397
-        );
398
-    }
399
-
400
-
401
-    /**
402
-     * @access public
403
-     * @param EE_Question_Group $question_group
404
-     * @return    EE_Form_Section_HTML
405
-     */
406
-    private function _question_group_header(EE_Question_Group $question_group)
407
-    {
408
-        $html = '';
409
-        // group_name
410
-        if ($question_group->show_group_name() && $question_group->name() !== '') {
411
-            if ($this->checkout->admin_request) {
412
-                $html .= EEH_HTML::br();
413
-                $html .= EEH_HTML::h3(
414
-                    $question_group->name(),
415
-                    '',
416
-                    'ee-reg-form-qstn-grp-title title',
417
-                    'font-size: 1.3em; padding-left:0;'
418
-                );
419
-            } else {
420
-                $html .= EEH_HTML::h4(
421
-                    $question_group->name(),
422
-                    '',
423
-                    'ee-reg-form-qstn-grp-title section-title'
424
-                );
425
-            }
426
-        }
427
-        // group_desc
428
-        if ($question_group->show_group_desc() && $question_group->desc() !== '') {
429
-            $html .= EEH_HTML::p(
430
-                $question_group->desc(),
431
-                '',
432
-                $this->checkout->admin_request
433
-                    ? 'ee-reg-form-qstn-grp-desc-pg'
434
-                    : 'ee-reg-form-qstn-grp-desc-pg small-text lt-grey-text'
435
-            );
436
-        }
437
-        return new EE_Form_Section_HTML($html);
438
-    }
439
-
440
-
441
-    /**
442
-     * @access public
443
-     * @return    EE_Form_Section_Proper
444
-     * @throws \EE_Error
445
-     */
446
-    private function _copy_attendee_info_form()
447
-    {
448
-        // array of params to pass to parent constructor
449
-        return new EE_Form_Section_Proper(
450
-            array(
451
-                'subsections'     => $this->_copy_attendee_info_inputs(),
452
-                'layout_strategy' => new EE_Template_Layout(
453
-                    array(
454
-                        'layout_template_file'     => SPCO_REG_STEPS_PATH
455
-                                                      . $this->_slug
456
-                                                      . DS
457
-                                                      . 'copy_attendee_info.template.php',
458
-                        'begin_template_file'      => null,
459
-                        'input_template_file'      => null,
460
-                        'subsection_template_file' => null,
461
-                        'end_template_file'        => null,
462
-                    )
463
-                ),
464
-            )
465
-        );
466
-    }
467
-
468
-
469
-    /**
470
-     * _auto_copy_attendee_info
471
-     *
472
-     * @access public
473
-     * @return EE_Form_Section_HTML
474
-     */
475
-    private function _auto_copy_attendee_info()
476
-    {
477
-        return new EE_Form_Section_HTML(
478
-            EEH_Template::locate_template(
479
-                SPCO_REG_STEPS_PATH . $this->_slug . DS . '_auto_copy_attendee_info.template.php',
480
-                apply_filters(
481
-                    'FHEE__EE_SPCO_Reg_Step_Attendee_Information__auto_copy_attendee_info__template_args',
482
-                    array()
483
-                ),
484
-                true,
485
-                true
486
-            )
487
-        );
488
-    }
489
-
490
-
491
-    /**
492
-     * _copy_attendee_info_inputs
493
-     *
494
-     * @access public
495
-     * @return array
496
-     * @throws \EE_Error
497
-     */
498
-    private function _copy_attendee_info_inputs()
499
-    {
500
-        $copy_attendee_info_inputs = array();
501
-        $prev_ticket               = null;
502
-        // grab the saved registrations from the transaction
503
-        $registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
504
-        foreach ($registrations as $registration) {
505
-            // for all  attendees other than the primary attendee
506
-            if ($registration instanceof EE_Registration && ! $registration->is_primary_registrant()) {
507
-                // if this is a new ticket OR if this is the very first additional attendee after the primary attendee
508
-                if ($registration->ticket()->ID() !== $prev_ticket) {
509
-                    $item_name = $registration->ticket()->name();
510
-                    $item_name .= $registration->ticket()->description() !== ''
511
-                        ? ' - ' . $registration->ticket()->description()
512
-                        : '';
513
-                    $copy_attendee_info_inputs['spco_copy_attendee_chk[ticket-' . $registration->ticket()->ID() . ']'] =
514
-                        new EE_Form_Section_HTML(
515
-                            '<h6 class="spco-copy-attendee-event-hdr">' . $item_name . '</h6>'
516
-                        );
517
-                    $prev_ticket = $registration->ticket()->ID();
518
-                }
519
-
520
-                $copy_attendee_info_inputs['spco_copy_attendee_chk[' . $registration->ID() . ']'] =
521
-                    new EE_Checkbox_Multi_Input(
522
-                        array(
523
-                            $registration->ID() => sprintf(
524
-                                esc_html__('Attendee #%s', 'event_espresso'),
525
-                                $registration->count()
526
-                            ),
527
-                        ),
528
-                        array(
529
-                            'html_id'                 => 'spco-copy-attendee-chk-' . $registration->reg_url_link(),
530
-                            'html_class'              => 'spco-copy-attendee-chk ee-do-not-validate',
531
-                            'display_html_label_text' => false,
532
-                        )
533
-                    );
534
-            }
535
-        }
536
-        return $copy_attendee_info_inputs;
537
-    }
538
-
539
-
540
-    /**
541
-     * _additional_primary_registrant_inputs
542
-     *
543
-     * @access public
544
-     * @param EE_Registration $registration
545
-     * @return    EE_Form_Input_Base
546
-     * @throws \EE_Error
547
-     */
548
-    private function _additional_primary_registrant_inputs(EE_Registration $registration)
549
-    {
550
-        // generate hidden input
551
-        return new EE_Hidden_Input(
552
-            array(
553
-                'html_id' => 'primary_registrant',
554
-                'default' => $registration->reg_url_link(),
555
-            )
556
-        );
557
-    }
558
-
559
-
560
-    /**
561
-     * @access public
562
-     * @param EE_Registration $registration
563
-     * @param EE_Question     $question
564
-     * @return EE_Form_Input_Base
565
-     * @throws EE_Error
566
-     * @throws InvalidArgumentException
567
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
568
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
569
-     */
570
-    public function reg_form_question(EE_Registration $registration, EE_Question $question)
571
-    {
572
-
573
-        // if this question was for an attendee detail, then check for that answer
574
-        $answer_value = EEM_Answer::instance()->get_attendee_property_answer_value(
575
-            $registration,
576
-            $question->system_ID()
577
-        );
578
-        $answer       = $answer_value === null
579
-            ? EEM_Answer::instance()->get_one(
580
-                array(array('QST_ID' => $question->ID(), 'REG_ID' => $registration->ID()))
581
-            )
582
-            : null;
583
-        // if NOT returning to edit an existing registration
584
-        // OR if this question is for an attendee property
585
-        // OR we still don't have an EE_Answer object
586
-        if ($answer_value || ! $answer instanceof EE_Answer || ! $registration->reg_url_link()) {
587
-            // create an EE_Answer object for storing everything in
588
-            $answer = EE_Answer::new_instance(array(
589
-                'QST_ID' => $question->ID(),
590
-                'REG_ID' => $registration->ID(),
591
-            ));
592
-        }
593
-        // verify instance
594
-        if ($answer instanceof EE_Answer) {
595
-            if (! empty($answer_value)) {
596
-                $answer->set('ANS_value', $answer_value);
597
-            }
598
-            $answer->cache('Question', $question);
599
-            //remember system ID had a bug where sometimes it could be null
600
-            $answer_cache_id = $question->is_system_question()
601
-                ? $question->system_ID() . '-' . $registration->reg_url_link()
602
-                : $question->ID() . '-' . $registration->reg_url_link();
603
-            $registration->cache('Answer', $answer, $answer_cache_id);
604
-        }
605
-        return $this->_generate_question_input($registration, $question, $answer);
606
-    }
607
-
608
-
609
-    /**
610
-     * @param EE_Registration $registration
611
-     * @param EE_Question     $question
612
-     * @param                 mixed EE_Answer|NULL      $answer
613
-     * @return EE_Form_Input_Base
614
-     * @throws \EE_Error
615
-     */
616
-    private function _generate_question_input(EE_Registration $registration, EE_Question $question, $answer)
617
-    {
618
-        $identifier                             = $question->is_system_question()
619
-            ? $question->system_ID()
620
-            : $question->ID();
621
-        $this->_required_questions[$identifier] = $question->required() ? true : false;
622
-        add_filter(
623
-            'FHEE__EE_Question__generate_form_input__country_options',
624
-            array($this, 'use_cached_countries_for_form_input'),
625
-            10,
626
-            4
627
-        );
628
-        add_filter(
629
-            'FHEE__EE_Question__generate_form_input__state_options',
630
-            array($this, 'use_cached_states_for_form_input'),
631
-            10,
632
-            4
633
-        );
634
-        $input_constructor_args                  = array(
635
-            'html_name'        => 'ee_reg_qstn[' . $registration->ID() . '][' . $identifier . ']',
636
-            'html_id'          => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
637
-            'html_class'       => 'ee-reg-qstn ee-reg-qstn-' . $identifier,
638
-            'html_label_id'    => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
639
-            'html_label_class' => 'ee-reg-qstn',
640
-        );
641
-        $input_constructor_args['html_label_id'] .= '-lbl';
642
-        if ($answer instanceof EE_Answer && $answer->ID()) {
643
-            $input_constructor_args['html_name']     .= '[' . $answer->ID() . ']';
644
-            $input_constructor_args['html_id']       .= '-' . $answer->ID();
645
-            $input_constructor_args['html_label_id'] .= '-' . $answer->ID();
646
-        }
647
-        $form_input = $question->generate_form_input(
648
-            $registration,
649
-            $answer,
650
-            $input_constructor_args
651
-        );
652
-        remove_filter(
653
-            'FHEE__EE_Question__generate_form_input__country_options',
654
-            array($this, 'use_cached_countries_for_form_input')
655
-        );
656
-        remove_filter(
657
-            'FHEE__EE_Question__generate_form_input__state_options',
658
-            array($this, 'use_cached_states_for_form_input')
659
-        );
660
-        return $form_input;
661
-    }
662
-
663
-
664
-    /**
665
-     * Gets the list of countries for the form input
666
-     *
667
-     * @param array|null       $countries_list
668
-     * @param \EE_Question     $question
669
-     * @param \EE_Registration $registration
670
-     * @param \EE_Answer       $answer
671
-     * @return array 2d keys are country IDs, values are their names
672
-     * @throws EE_Error
673
-     * @throws InvalidArgumentException
674
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
675
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
676
-     */
677
-    public function use_cached_countries_for_form_input(
678
-        $countries_list,
679
-        \EE_Question $question = null,
680
-        \EE_Registration $registration = null,
681
-        \EE_Answer $answer = null
682
-    ) {
683
-        $country_options = array('' => '');
684
-        // get possibly cached list of countries
685
-        $countries = $this->checkout->action === 'process_reg_step'
686
-            ? EEM_Country::instance()->get_all_countries()
687
-            : EEM_Country::instance()->get_all_active_countries();
688
-        if (! empty($countries)) {
689
-            foreach ($countries as $country) {
690
-                if ($country instanceof EE_Country) {
691
-                    $country_options[$country->ID()] = $country->name();
692
-                }
693
-            }
694
-        }
695
-        if ($question instanceof EE_Question
696
-            && $registration instanceof EE_Registration) {
697
-            $answer = EEM_Answer::instance()->get_one(
698
-                array(array('QST_ID' => $question->ID(), 'REG_ID' => $registration->ID()))
699
-            );
700
-        } else {
701
-            $answer = EE_Answer::new_instance();
702
-        }
703
-        $country_options = apply_filters(
704
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
705
-            $country_options,
706
-            $this,
707
-            $registration,
708
-            $question,
709
-            $answer
710
-        );
711
-        return $country_options;
712
-    }
713
-
714
-
715
-    /**
716
-     * Gets the list of states for the form input
717
-     *
718
-     * @param array|null       $states_list
719
-     * @param \EE_Question     $question
720
-     * @param \EE_Registration $registration
721
-     * @param \EE_Answer       $answer
722
-     * @return array 2d keys are state IDs, values are their names
723
-     * @throws EE_Error
724
-     * @throws InvalidArgumentException
725
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
726
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
727
-     */
728
-    public function use_cached_states_for_form_input(
729
-        $states_list,
730
-        \EE_Question $question = null,
731
-        \EE_Registration $registration = null,
732
-        \EE_Answer $answer = null
733
-    ) {
734
-        $state_options = array('' => array('' => ''));
735
-        $states        = $this->checkout->action === 'process_reg_step'
736
-            ? EEM_State::instance()->get_all_states()
737
-            : EEM_State::instance()->get_all_active_states();
738
-        if (! empty($states)) {
739
-            foreach ($states as $state) {
740
-                if ($state instanceof EE_State) {
741
-                    $state_options[$state->country()->name()][$state->ID()] = $state->name();
742
-                }
743
-            }
744
-        }
745
-        $state_options = apply_filters(
746
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
747
-            $state_options,
748
-            $this,
749
-            $registration,
750
-            $question,
751
-            $answer
752
-        );
753
-        return $state_options;
754
-    }
755
-
756
-
757
-
758
-
759
-
760
-
761
-    /********************************************************************************************************/
762
-    /****************************************  PROCESS REG STEP  ****************************************/
763
-    /********************************************************************************************************/
764
-    /**
765
-     * @return bool
766
-     * @throws EE_Error
767
-     * @throws InvalidArgumentException
768
-     * @throws ReflectionException
769
-     * @throws RuntimeException
770
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
771
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
772
-     */
773
-    public function process_reg_step()
774
-    {
775
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
776
-        // grab validated data from form
777
-        $valid_data = $this->checkout->current_step->valid_data();
778
-        // EEH_Debug_Tools::printr( $_REQUEST, '$_REQUEST', __FILE__, __LINE__ );
779
-        // EEH_Debug_Tools::printr( $valid_data, '$valid_data', __FILE__, __LINE__ );
780
-        // if we don't have any $valid_data then something went TERRIBLY WRONG !!!
781
-        if (empty($valid_data)) {
782
-            EE_Error::add_error(
783
-                esc_html__('No valid question responses were received.', 'event_espresso'),
784
-                __FILE__,
785
-                __FUNCTION__,
786
-                __LINE__
787
-            );
788
-            return false;
789
-        }
790
-        if (! $this->checkout->transaction instanceof EE_Transaction || ! $this->checkout->continue_reg) {
791
-            EE_Error::add_error(
792
-                esc_html__(
793
-                    'A valid transaction could not be initiated for processing your registrations.',
794
-                    'event_espresso'
795
-                ),
796
-                __FILE__,
797
-                __FUNCTION__,
798
-                __LINE__
799
-            );
800
-            return false;
801
-        }
802
-        // get cached registrations
803
-        $registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
804
-        // verify we got the goods
805
-        if (empty($registrations)) {
806
-            //combine the old translated string with a new one, in order to not break translations
807
-            $error_message = esc_html__( 'Your form data could not be applied to any valid registrations.', 'event_espresso' )
808
-                             . sprintf(
809
-                                 esc_html__('%3$sThis can sometimes happen if too much time has been taken to complete the registration process.%3$sPlease return to the %1$sEvent List%2$s and reselect your tickets. If the problem continues, please contact the site administrator.', 'event_espresso'),
810
-                                 '<a href="' . get_post_type_archive_link('espresso_events') . '" >',
811
-                                 '</a>',
812
-                                 '<br />'
813
-                             );
814
-            EE_Error::add_error(
815
-                $error_message,
816
-                __FILE__,
817
-                __FUNCTION__,
818
-                __LINE__
819
-            );
820
-            return false;
821
-        }
822
-        // extract attendee info from form data and save to model objects
823
-        $registrations_processed = $this->_process_registrations($registrations, $valid_data);
824
-        // if first pass thru SPCO,
825
-        // then let's check processed registrations against the total number of tickets in the cart
826
-        if ($registrations_processed === false) {
827
-            // but return immediately if the previous step exited early due to errors
828
-            return false;
829
-        } elseif (! $this->checkout->revisit && $registrations_processed !== $this->checkout->total_ticket_count) {
830
-            // generate a correctly translated string for all possible singular/plural combinations
831
-            if ($this->checkout->total_ticket_count === 1 && $registrations_processed !== 1) {
832
-                $error_msg = sprintf(
833
-                    esc_html__(
834
-                        'There was %1$d ticket in the Event Queue, but %2$ds registrations were processed',
835
-                        'event_espresso'
836
-                    ),
837
-                    $this->checkout->total_ticket_count,
838
-                    $registrations_processed
839
-                );
840
-            } elseif ($this->checkout->total_ticket_count !== 1 && $registrations_processed === 1) {
841
-                $error_msg = sprintf(
842
-                    esc_html__(
843
-                        'There was a total of %1$d tickets in the Event Queue, but only %2$ds registration was processed',
844
-                        'event_espresso'
845
-                    ),
846
-                    $this->checkout->total_ticket_count,
847
-                    $registrations_processed
848
-                );
849
-            } else {
850
-                $error_msg = sprintf(
851
-                    esc_html__(
852
-                        'There was a total of %1$d tickets in the Event Queue, but %2$ds registrations were processed',
853
-                        'event_espresso'
854
-                    ),
855
-                    $this->checkout->total_ticket_count,
856
-                    $registrations_processed
857
-                );
858
-            }
859
-            EE_Error::add_error($error_msg, __FILE__, __FUNCTION__, __LINE__);
860
-            return false;
861
-        }
862
-        // mark this reg step as completed
863
-        $this->set_completed();
864
-        $this->_set_success_message(
865
-            esc_html__('The Attendee Information Step has been successfully completed.', 'event_espresso')
866
-        );
867
-        //do action in case a plugin wants to do something with the data submitted in step 1.
868
-        //passes EE_Single_Page_Checkout, and it's posted data
869
-        do_action('AHEE__EE_Single_Page_Checkout__process_attendee_information__end', $this, $valid_data);
870
-        return true;
871
-    }
872
-
873
-
874
-    /**
875
-     *    _process_registrations
876
-     *
877
-     * @param EE_Registration[] $registrations
878
-     * @param array             $valid_data
879
-     * @return bool|int
880
-     * @throws \EventEspresso\core\exceptions\EntityNotFoundException
881
-     * @throws EE_Error
882
-     * @throws InvalidArgumentException
883
-     * @throws ReflectionException
884
-     * @throws RuntimeException
885
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
886
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
887
-     */
888
-    private function _process_registrations($registrations = array(), $valid_data = array())
889
-    {
890
-        // load resources and set some defaults
891
-        EE_Registry::instance()->load_model('Attendee');
892
-        // holder for primary registrant attendee object
893
-        $this->checkout->primary_attendee_obj = null;
894
-        // array for tracking reg form data for the primary registrant
895
-        $primary_registrant = array(
896
-            'line_item_id' => null,
897
-        );
898
-        $copy_primary       = false;
899
-        // reg form sections that do not contain inputs
900
-        $non_input_form_sections = array(
901
-            'primary_registrant',
902
-            'additional_attendee_reg_info',
903
-            'spco_copy_attendee_chk',
904
-        );
905
-        // attendee counter
906
-        $att_nmbr = 0;
907
-        // grab the saved registrations from the transaction
908
-        foreach ($registrations as $registration) {
909
-            // verify EE_Registration object
910
-            if (! $registration instanceof EE_Registration) {
911
-                EE_Error::add_error(
912
-                    esc_html__(
913
-                        'An invalid Registration object was discovered when attempting to process your registration information.',
914
-                        'event_espresso'
915
-                    ),
916
-                    __FILE__,
917
-                    __FUNCTION__,
918
-                    __LINE__
919
-                );
920
-                return false;
921
-            }
922
-            /** @var string $reg_url_link */
923
-            $reg_url_link = $registration->reg_url_link();
924
-            // reg_url_link exists ?
925
-            if (! empty($reg_url_link)) {
926
-                // should this registration be processed during this visit ?
927
-                if ($this->checkout->visit_allows_processing_of_this_registration($registration)) {
928
-                    // if NOT revisiting, then let's save the registration now,
929
-                    // so that we have a REG_ID to use when generating other objects
930
-                    if (! $this->checkout->revisit) {
931
-                        $registration->save();
932
-                    }
933
-                    /**
934
-                     * This allows plugins to trigger a fail on processing of a
935
-                     * registration for any conditions they may have for it to pass.
936
-                     *
937
-                     * @var bool   if true is returned by the plugin then the
938
-                     *            registration processing is halted.
939
-                     */
940
-                    if (apply_filters(
941
-                        'FHEE__EE_SPCO_Reg_Step_Attendee_Information___process_registrations__pre_registration_process',
942
-                        false,
943
-                        $att_nmbr,
944
-                        $registration,
945
-                        $registrations,
946
-                        $valid_data,
947
-                        $this
948
-                    )) {
949
-                        return false;
950
-                    }
951
-
952
-                    // Houston, we have a registration!
953
-                    $att_nmbr++;
954
-                    $this->_attendee_data[$reg_url_link] = array();
955
-                    // grab any existing related answer objects
956
-                    $this->_registration_answers = $registration->answers();
957
-                    // unset( $valid_data[ $reg_url_link ]['additional_attendee_reg_info'] );
958
-                    if (isset($valid_data[$reg_url_link])) {
959
-                        // do we need to copy basic info from primary attendee ?
960
-                        $copy_primary = isset($valid_data[$reg_url_link]['additional_attendee_reg_info'])
961
-                                        && absint($valid_data[$reg_url_link]['additional_attendee_reg_info']) === 0
962
-                            ? true
963
-                            : false;
964
-                        // filter form input data for this registration
965
-                        $valid_data[$reg_url_link] = (array)apply_filters(
966
-                            'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
967
-                            $valid_data[$reg_url_link]
968
-                        );
969
-                        if (isset($valid_data['primary_attendee'])) {
970
-                            $primary_registrant['line_item_id'] = ! empty($valid_data['primary_attendee'])
971
-                                ? $valid_data['primary_attendee']
972
-                                : false;
973
-                            unset($valid_data['primary_attendee']);
974
-                        }
975
-                        // now loop through our array of valid post data && process attendee reg forms
976
-                        foreach ($valid_data[$reg_url_link] as $form_section => $form_inputs) {
977
-                            if (! in_array($form_section, $non_input_form_sections)) {
978
-                                foreach ($form_inputs as $form_input => $input_value) {
979
-                                    // \EEH_Debug_Tools::printr( $input_value, $form_input, __FILE__, __LINE__ );
980
-                                    // check for critical inputs
981
-                                    if (! $this->_verify_critical_attendee_details_are_set_and_validate_email(
982
-                                        $form_input,
983
-                                        $input_value
984
-                                    )
985
-                                    ) {
986
-                                        return false;
987
-                                    }
988
-                                    // store a bit of data about the primary attendee
989
-                                    if ($att_nmbr === 1
990
-                                        && ! empty($input_value)
991
-                                        && $reg_url_link === $primary_registrant['line_item_id']
992
-                                    ) {
993
-                                        $primary_registrant[$form_input] = $input_value;
994
-                                    } elseif ($copy_primary
995
-                                        && $input_value === null
996
-                                        && isset($primary_registrant[$form_input])
997
-                                    ) {
998
-                                        $input_value = $primary_registrant[$form_input];
999
-                                    }
1000
-                                    // now attempt to save the input data
1001
-                                    if (! $this->_save_registration_form_input(
1002
-                                        $registration,
1003
-                                        $form_input,
1004
-                                        $input_value
1005
-                                    )
1006
-                                    ) {
1007
-                                        EE_Error::add_error(
1008
-                                            sprintf(
1009
-                                                esc_html__(
1010
-                                                    'Unable to save registration form data for the form input: "%1$s" with the submitted value: "%2$s"',
1011
-                                                    'event_espresso'
1012
-                                                ),
1013
-                                                $form_input,
1014
-                                                $input_value
1015
-                                            ),
1016
-                                            __FILE__,
1017
-                                            __FUNCTION__,
1018
-                                            __LINE__
1019
-                                        );
1020
-                                        return false;
1021
-                                    }
1022
-                                }
1023
-                            }
1024
-                        }  // end of foreach ( $valid_data[ $reg_url_link ] as $form_section => $form_inputs )
1025
-                    }
1026
-                    //EEH_Debug_Tools::printr( $this->_attendee_data, '$this->_attendee_data', __FILE__, __LINE__ );
1027
-                    // this registration does not require additional attendee information ?
1028
-                    if ($copy_primary
1029
-                        && $att_nmbr > 1
1030
-                        && $this->checkout->primary_attendee_obj instanceof EE_Attendee
1031
-                    ) {
1032
-                        // just copy the primary registrant
1033
-                        $attendee = $this->checkout->primary_attendee_obj;
1034
-                    } else {
1035
-                        // ensure critical details are set for additional attendees
1036
-                        $this->_attendee_data[$reg_url_link] = $att_nmbr > 1
1037
-                            ? $this->_copy_critical_attendee_details_from_primary_registrant(
1038
-                                $this->_attendee_data[$reg_url_link]
1039
-                            )
1040
-                            : $this->_attendee_data[$reg_url_link];
1041
-                        // execute create attendee command (which may return an existing attendee)
1042
-                        $attendee = EE_Registry::instance()->BUS->execute(
1043
-                            new CreateAttendeeCommand(
1044
-                                $this->_attendee_data[$reg_url_link],
1045
-                                $registration
1046
-                            )
1047
-                        );
1048
-                        // who's #1 ?
1049
-                        if ($att_nmbr === 1) {
1050
-                            $this->checkout->primary_attendee_obj = $attendee;
1051
-                        }
1052
-                    }
1053
-                    // EEH_Debug_Tools::printr( $attendee, '$attendee', __FILE__, __LINE__ );
1054
-                    // add relation to registration, set attendee ID, and cache attendee
1055
-                    $this->_associate_attendee_with_registration($registration, $attendee);
1056
-                    // \EEH_Debug_Tools::printr( $registration, '$registration', __FILE__, __LINE__ );
1057
-                    if (! $registration->attendee() instanceof EE_Attendee) {
1058
-                        EE_Error::add_error(
1059
-                            sprintf(
1060
-                                esc_html__(
1061
-                                    'Registration %s has an invalid or missing Attendee object.',
1062
-                                    'event_espresso'
1063
-                                ),
1064
-                                $reg_url_link
1065
-                            ),
1066
-                            __FILE__,
1067
-                            __FUNCTION__,
1068
-                            __LINE__
1069
-                        );
1070
-                        return false;
1071
-                    }
1072
-                    /** @type EE_Registration_Processor $registration_processor */
1073
-                    $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
1074
-                    // at this point, we should have enough details about the registrant to consider the registration
1075
-                    // NOT incomplete
1076
-                    $registration_processor->toggle_incomplete_registration_status_to_default(
1077
-                        $registration,
1078
-                        false,
1079
-                        new Context(
1080
-                            'spco_reg_step_attendee_information_process_registrations',
1081
-                            esc_html__(
1082
-                                'Finished populating registration with details from the registration form after submitting the Attendee Information Reg Step.',
1083
-                                'event_espresso'
1084
-                            )
1085
-                        )
1086
-                    );
1087
-                    // we can also consider the TXN to not have been failed, so temporarily upgrade it's status to
1088
-                    // abandoned
1089
-                    $this->checkout->transaction->toggle_failed_transaction_status();
1090
-                    // if we've gotten this far, then let's save what we have
1091
-                    $registration->save();
1092
-                    // add relation between TXN and registration
1093
-                    $this->_associate_registration_with_transaction($registration);
1094
-                }
1095
-            } else {
1096
-                EE_Error::add_error(
1097
-                    esc_html__(
1098
-                        'An invalid or missing line item ID was encountered while attempting to process the registration form.',
1099
-                        'event_espresso'
1100
-                    ),
1101
-                    __FILE__,
1102
-                    __FUNCTION__,
1103
-                    __LINE__
1104
-                );
1105
-                // remove malformed data
1106
-                unset($valid_data[$reg_url_link]);
1107
-                return false;
1108
-            }
1109
-
1110
-        } // end of foreach ( $this->checkout->transaction->registrations()  as $registration )
1111
-        return $att_nmbr;
1112
-    }
1113
-
1114
-
1115
-    /**
1116
-     *    _save_registration_form_input
1117
-     *
1118
-     * @param EE_Registration $registration
1119
-     * @param string          $form_input
1120
-     * @param string          $input_value
1121
-     * @return bool
1122
-     * @throws EE_Error
1123
-     * @throws InvalidArgumentException
1124
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
1125
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
1126
-     */
1127
-    private function _save_registration_form_input(
1128
-        EE_Registration $registration,
1129
-        $form_input = '',
1130
-        $input_value = ''
1131
-    ) {
1132
-        // \EEH_Debug_Tools::printr( __FUNCTION__, __CLASS__, __FILE__, __LINE__, 2 );
1133
-        // \EEH_Debug_Tools::printr( $form_input, '$form_input', __FILE__, __LINE__ );
1134
-        // \EEH_Debug_Tools::printr( $input_value, '$input_value', __FILE__, __LINE__ );
1135
-        // allow for plugins to hook in and do their own processing of the form input.
1136
-        // For plugins to bypass normal processing here, they just need to return a boolean value.
1137
-        if (apply_filters(
1138
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information___save_registration_form_input',
1139
-            false,
1140
-            $registration,
1141
-            $form_input,
1142
-            $input_value,
1143
-            $this
1144
-        )) {
1145
-            return true;
1146
-        }
1147
-        /*
390
+		$question_group_reg_form = new EE_Form_Section_Proper($form_args);
391
+		return apply_filters(
392
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___question_group_reg_form__question_group_reg_form',
393
+			$question_group_reg_form,
394
+			$registration,
395
+			$question_group,
396
+			$this
397
+		);
398
+	}
399
+
400
+
401
+	/**
402
+	 * @access public
403
+	 * @param EE_Question_Group $question_group
404
+	 * @return    EE_Form_Section_HTML
405
+	 */
406
+	private function _question_group_header(EE_Question_Group $question_group)
407
+	{
408
+		$html = '';
409
+		// group_name
410
+		if ($question_group->show_group_name() && $question_group->name() !== '') {
411
+			if ($this->checkout->admin_request) {
412
+				$html .= EEH_HTML::br();
413
+				$html .= EEH_HTML::h3(
414
+					$question_group->name(),
415
+					'',
416
+					'ee-reg-form-qstn-grp-title title',
417
+					'font-size: 1.3em; padding-left:0;'
418
+				);
419
+			} else {
420
+				$html .= EEH_HTML::h4(
421
+					$question_group->name(),
422
+					'',
423
+					'ee-reg-form-qstn-grp-title section-title'
424
+				);
425
+			}
426
+		}
427
+		// group_desc
428
+		if ($question_group->show_group_desc() && $question_group->desc() !== '') {
429
+			$html .= EEH_HTML::p(
430
+				$question_group->desc(),
431
+				'',
432
+				$this->checkout->admin_request
433
+					? 'ee-reg-form-qstn-grp-desc-pg'
434
+					: 'ee-reg-form-qstn-grp-desc-pg small-text lt-grey-text'
435
+			);
436
+		}
437
+		return new EE_Form_Section_HTML($html);
438
+	}
439
+
440
+
441
+	/**
442
+	 * @access public
443
+	 * @return    EE_Form_Section_Proper
444
+	 * @throws \EE_Error
445
+	 */
446
+	private function _copy_attendee_info_form()
447
+	{
448
+		// array of params to pass to parent constructor
449
+		return new EE_Form_Section_Proper(
450
+			array(
451
+				'subsections'     => $this->_copy_attendee_info_inputs(),
452
+				'layout_strategy' => new EE_Template_Layout(
453
+					array(
454
+						'layout_template_file'     => SPCO_REG_STEPS_PATH
455
+													  . $this->_slug
456
+													  . DS
457
+													  . 'copy_attendee_info.template.php',
458
+						'begin_template_file'      => null,
459
+						'input_template_file'      => null,
460
+						'subsection_template_file' => null,
461
+						'end_template_file'        => null,
462
+					)
463
+				),
464
+			)
465
+		);
466
+	}
467
+
468
+
469
+	/**
470
+	 * _auto_copy_attendee_info
471
+	 *
472
+	 * @access public
473
+	 * @return EE_Form_Section_HTML
474
+	 */
475
+	private function _auto_copy_attendee_info()
476
+	{
477
+		return new EE_Form_Section_HTML(
478
+			EEH_Template::locate_template(
479
+				SPCO_REG_STEPS_PATH . $this->_slug . DS . '_auto_copy_attendee_info.template.php',
480
+				apply_filters(
481
+					'FHEE__EE_SPCO_Reg_Step_Attendee_Information__auto_copy_attendee_info__template_args',
482
+					array()
483
+				),
484
+				true,
485
+				true
486
+			)
487
+		);
488
+	}
489
+
490
+
491
+	/**
492
+	 * _copy_attendee_info_inputs
493
+	 *
494
+	 * @access public
495
+	 * @return array
496
+	 * @throws \EE_Error
497
+	 */
498
+	private function _copy_attendee_info_inputs()
499
+	{
500
+		$copy_attendee_info_inputs = array();
501
+		$prev_ticket               = null;
502
+		// grab the saved registrations from the transaction
503
+		$registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
504
+		foreach ($registrations as $registration) {
505
+			// for all  attendees other than the primary attendee
506
+			if ($registration instanceof EE_Registration && ! $registration->is_primary_registrant()) {
507
+				// if this is a new ticket OR if this is the very first additional attendee after the primary attendee
508
+				if ($registration->ticket()->ID() !== $prev_ticket) {
509
+					$item_name = $registration->ticket()->name();
510
+					$item_name .= $registration->ticket()->description() !== ''
511
+						? ' - ' . $registration->ticket()->description()
512
+						: '';
513
+					$copy_attendee_info_inputs['spco_copy_attendee_chk[ticket-' . $registration->ticket()->ID() . ']'] =
514
+						new EE_Form_Section_HTML(
515
+							'<h6 class="spco-copy-attendee-event-hdr">' . $item_name . '</h6>'
516
+						);
517
+					$prev_ticket = $registration->ticket()->ID();
518
+				}
519
+
520
+				$copy_attendee_info_inputs['spco_copy_attendee_chk[' . $registration->ID() . ']'] =
521
+					new EE_Checkbox_Multi_Input(
522
+						array(
523
+							$registration->ID() => sprintf(
524
+								esc_html__('Attendee #%s', 'event_espresso'),
525
+								$registration->count()
526
+							),
527
+						),
528
+						array(
529
+							'html_id'                 => 'spco-copy-attendee-chk-' . $registration->reg_url_link(),
530
+							'html_class'              => 'spco-copy-attendee-chk ee-do-not-validate',
531
+							'display_html_label_text' => false,
532
+						)
533
+					);
534
+			}
535
+		}
536
+		return $copy_attendee_info_inputs;
537
+	}
538
+
539
+
540
+	/**
541
+	 * _additional_primary_registrant_inputs
542
+	 *
543
+	 * @access public
544
+	 * @param EE_Registration $registration
545
+	 * @return    EE_Form_Input_Base
546
+	 * @throws \EE_Error
547
+	 */
548
+	private function _additional_primary_registrant_inputs(EE_Registration $registration)
549
+	{
550
+		// generate hidden input
551
+		return new EE_Hidden_Input(
552
+			array(
553
+				'html_id' => 'primary_registrant',
554
+				'default' => $registration->reg_url_link(),
555
+			)
556
+		);
557
+	}
558
+
559
+
560
+	/**
561
+	 * @access public
562
+	 * @param EE_Registration $registration
563
+	 * @param EE_Question     $question
564
+	 * @return EE_Form_Input_Base
565
+	 * @throws EE_Error
566
+	 * @throws InvalidArgumentException
567
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
568
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
569
+	 */
570
+	public function reg_form_question(EE_Registration $registration, EE_Question $question)
571
+	{
572
+
573
+		// if this question was for an attendee detail, then check for that answer
574
+		$answer_value = EEM_Answer::instance()->get_attendee_property_answer_value(
575
+			$registration,
576
+			$question->system_ID()
577
+		);
578
+		$answer       = $answer_value === null
579
+			? EEM_Answer::instance()->get_one(
580
+				array(array('QST_ID' => $question->ID(), 'REG_ID' => $registration->ID()))
581
+			)
582
+			: null;
583
+		// if NOT returning to edit an existing registration
584
+		// OR if this question is for an attendee property
585
+		// OR we still don't have an EE_Answer object
586
+		if ($answer_value || ! $answer instanceof EE_Answer || ! $registration->reg_url_link()) {
587
+			// create an EE_Answer object for storing everything in
588
+			$answer = EE_Answer::new_instance(array(
589
+				'QST_ID' => $question->ID(),
590
+				'REG_ID' => $registration->ID(),
591
+			));
592
+		}
593
+		// verify instance
594
+		if ($answer instanceof EE_Answer) {
595
+			if (! empty($answer_value)) {
596
+				$answer->set('ANS_value', $answer_value);
597
+			}
598
+			$answer->cache('Question', $question);
599
+			//remember system ID had a bug where sometimes it could be null
600
+			$answer_cache_id = $question->is_system_question()
601
+				? $question->system_ID() . '-' . $registration->reg_url_link()
602
+				: $question->ID() . '-' . $registration->reg_url_link();
603
+			$registration->cache('Answer', $answer, $answer_cache_id);
604
+		}
605
+		return $this->_generate_question_input($registration, $question, $answer);
606
+	}
607
+
608
+
609
+	/**
610
+	 * @param EE_Registration $registration
611
+	 * @param EE_Question     $question
612
+	 * @param                 mixed EE_Answer|NULL      $answer
613
+	 * @return EE_Form_Input_Base
614
+	 * @throws \EE_Error
615
+	 */
616
+	private function _generate_question_input(EE_Registration $registration, EE_Question $question, $answer)
617
+	{
618
+		$identifier                             = $question->is_system_question()
619
+			? $question->system_ID()
620
+			: $question->ID();
621
+		$this->_required_questions[$identifier] = $question->required() ? true : false;
622
+		add_filter(
623
+			'FHEE__EE_Question__generate_form_input__country_options',
624
+			array($this, 'use_cached_countries_for_form_input'),
625
+			10,
626
+			4
627
+		);
628
+		add_filter(
629
+			'FHEE__EE_Question__generate_form_input__state_options',
630
+			array($this, 'use_cached_states_for_form_input'),
631
+			10,
632
+			4
633
+		);
634
+		$input_constructor_args                  = array(
635
+			'html_name'        => 'ee_reg_qstn[' . $registration->ID() . '][' . $identifier . ']',
636
+			'html_id'          => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
637
+			'html_class'       => 'ee-reg-qstn ee-reg-qstn-' . $identifier,
638
+			'html_label_id'    => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
639
+			'html_label_class' => 'ee-reg-qstn',
640
+		);
641
+		$input_constructor_args['html_label_id'] .= '-lbl';
642
+		if ($answer instanceof EE_Answer && $answer->ID()) {
643
+			$input_constructor_args['html_name']     .= '[' . $answer->ID() . ']';
644
+			$input_constructor_args['html_id']       .= '-' . $answer->ID();
645
+			$input_constructor_args['html_label_id'] .= '-' . $answer->ID();
646
+		}
647
+		$form_input = $question->generate_form_input(
648
+			$registration,
649
+			$answer,
650
+			$input_constructor_args
651
+		);
652
+		remove_filter(
653
+			'FHEE__EE_Question__generate_form_input__country_options',
654
+			array($this, 'use_cached_countries_for_form_input')
655
+		);
656
+		remove_filter(
657
+			'FHEE__EE_Question__generate_form_input__state_options',
658
+			array($this, 'use_cached_states_for_form_input')
659
+		);
660
+		return $form_input;
661
+	}
662
+
663
+
664
+	/**
665
+	 * Gets the list of countries for the form input
666
+	 *
667
+	 * @param array|null       $countries_list
668
+	 * @param \EE_Question     $question
669
+	 * @param \EE_Registration $registration
670
+	 * @param \EE_Answer       $answer
671
+	 * @return array 2d keys are country IDs, values are their names
672
+	 * @throws EE_Error
673
+	 * @throws InvalidArgumentException
674
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
675
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
676
+	 */
677
+	public function use_cached_countries_for_form_input(
678
+		$countries_list,
679
+		\EE_Question $question = null,
680
+		\EE_Registration $registration = null,
681
+		\EE_Answer $answer = null
682
+	) {
683
+		$country_options = array('' => '');
684
+		// get possibly cached list of countries
685
+		$countries = $this->checkout->action === 'process_reg_step'
686
+			? EEM_Country::instance()->get_all_countries()
687
+			: EEM_Country::instance()->get_all_active_countries();
688
+		if (! empty($countries)) {
689
+			foreach ($countries as $country) {
690
+				if ($country instanceof EE_Country) {
691
+					$country_options[$country->ID()] = $country->name();
692
+				}
693
+			}
694
+		}
695
+		if ($question instanceof EE_Question
696
+			&& $registration instanceof EE_Registration) {
697
+			$answer = EEM_Answer::instance()->get_one(
698
+				array(array('QST_ID' => $question->ID(), 'REG_ID' => $registration->ID()))
699
+			);
700
+		} else {
701
+			$answer = EE_Answer::new_instance();
702
+		}
703
+		$country_options = apply_filters(
704
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__country_options',
705
+			$country_options,
706
+			$this,
707
+			$registration,
708
+			$question,
709
+			$answer
710
+		);
711
+		return $country_options;
712
+	}
713
+
714
+
715
+	/**
716
+	 * Gets the list of states for the form input
717
+	 *
718
+	 * @param array|null       $states_list
719
+	 * @param \EE_Question     $question
720
+	 * @param \EE_Registration $registration
721
+	 * @param \EE_Answer       $answer
722
+	 * @return array 2d keys are state IDs, values are their names
723
+	 * @throws EE_Error
724
+	 * @throws InvalidArgumentException
725
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
726
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
727
+	 */
728
+	public function use_cached_states_for_form_input(
729
+		$states_list,
730
+		\EE_Question $question = null,
731
+		\EE_Registration $registration = null,
732
+		\EE_Answer $answer = null
733
+	) {
734
+		$state_options = array('' => array('' => ''));
735
+		$states        = $this->checkout->action === 'process_reg_step'
736
+			? EEM_State::instance()->get_all_states()
737
+			: EEM_State::instance()->get_all_active_states();
738
+		if (! empty($states)) {
739
+			foreach ($states as $state) {
740
+				if ($state instanceof EE_State) {
741
+					$state_options[$state->country()->name()][$state->ID()] = $state->name();
742
+				}
743
+			}
744
+		}
745
+		$state_options = apply_filters(
746
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___generate_question_input__state_options',
747
+			$state_options,
748
+			$this,
749
+			$registration,
750
+			$question,
751
+			$answer
752
+		);
753
+		return $state_options;
754
+	}
755
+
756
+
757
+
758
+
759
+
760
+
761
+	/********************************************************************************************************/
762
+	/****************************************  PROCESS REG STEP  ****************************************/
763
+	/********************************************************************************************************/
764
+	/**
765
+	 * @return bool
766
+	 * @throws EE_Error
767
+	 * @throws InvalidArgumentException
768
+	 * @throws ReflectionException
769
+	 * @throws RuntimeException
770
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
771
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
772
+	 */
773
+	public function process_reg_step()
774
+	{
775
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
776
+		// grab validated data from form
777
+		$valid_data = $this->checkout->current_step->valid_data();
778
+		// EEH_Debug_Tools::printr( $_REQUEST, '$_REQUEST', __FILE__, __LINE__ );
779
+		// EEH_Debug_Tools::printr( $valid_data, '$valid_data', __FILE__, __LINE__ );
780
+		// if we don't have any $valid_data then something went TERRIBLY WRONG !!!
781
+		if (empty($valid_data)) {
782
+			EE_Error::add_error(
783
+				esc_html__('No valid question responses were received.', 'event_espresso'),
784
+				__FILE__,
785
+				__FUNCTION__,
786
+				__LINE__
787
+			);
788
+			return false;
789
+		}
790
+		if (! $this->checkout->transaction instanceof EE_Transaction || ! $this->checkout->continue_reg) {
791
+			EE_Error::add_error(
792
+				esc_html__(
793
+					'A valid transaction could not be initiated for processing your registrations.',
794
+					'event_espresso'
795
+				),
796
+				__FILE__,
797
+				__FUNCTION__,
798
+				__LINE__
799
+			);
800
+			return false;
801
+		}
802
+		// get cached registrations
803
+		$registrations = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
804
+		// verify we got the goods
805
+		if (empty($registrations)) {
806
+			//combine the old translated string with a new one, in order to not break translations
807
+			$error_message = esc_html__( 'Your form data could not be applied to any valid registrations.', 'event_espresso' )
808
+							 . sprintf(
809
+								 esc_html__('%3$sThis can sometimes happen if too much time has been taken to complete the registration process.%3$sPlease return to the %1$sEvent List%2$s and reselect your tickets. If the problem continues, please contact the site administrator.', 'event_espresso'),
810
+								 '<a href="' . get_post_type_archive_link('espresso_events') . '" >',
811
+								 '</a>',
812
+								 '<br />'
813
+							 );
814
+			EE_Error::add_error(
815
+				$error_message,
816
+				__FILE__,
817
+				__FUNCTION__,
818
+				__LINE__
819
+			);
820
+			return false;
821
+		}
822
+		// extract attendee info from form data and save to model objects
823
+		$registrations_processed = $this->_process_registrations($registrations, $valid_data);
824
+		// if first pass thru SPCO,
825
+		// then let's check processed registrations against the total number of tickets in the cart
826
+		if ($registrations_processed === false) {
827
+			// but return immediately if the previous step exited early due to errors
828
+			return false;
829
+		} elseif (! $this->checkout->revisit && $registrations_processed !== $this->checkout->total_ticket_count) {
830
+			// generate a correctly translated string for all possible singular/plural combinations
831
+			if ($this->checkout->total_ticket_count === 1 && $registrations_processed !== 1) {
832
+				$error_msg = sprintf(
833
+					esc_html__(
834
+						'There was %1$d ticket in the Event Queue, but %2$ds registrations were processed',
835
+						'event_espresso'
836
+					),
837
+					$this->checkout->total_ticket_count,
838
+					$registrations_processed
839
+				);
840
+			} elseif ($this->checkout->total_ticket_count !== 1 && $registrations_processed === 1) {
841
+				$error_msg = sprintf(
842
+					esc_html__(
843
+						'There was a total of %1$d tickets in the Event Queue, but only %2$ds registration was processed',
844
+						'event_espresso'
845
+					),
846
+					$this->checkout->total_ticket_count,
847
+					$registrations_processed
848
+				);
849
+			} else {
850
+				$error_msg = sprintf(
851
+					esc_html__(
852
+						'There was a total of %1$d tickets in the Event Queue, but %2$ds registrations were processed',
853
+						'event_espresso'
854
+					),
855
+					$this->checkout->total_ticket_count,
856
+					$registrations_processed
857
+				);
858
+			}
859
+			EE_Error::add_error($error_msg, __FILE__, __FUNCTION__, __LINE__);
860
+			return false;
861
+		}
862
+		// mark this reg step as completed
863
+		$this->set_completed();
864
+		$this->_set_success_message(
865
+			esc_html__('The Attendee Information Step has been successfully completed.', 'event_espresso')
866
+		);
867
+		//do action in case a plugin wants to do something with the data submitted in step 1.
868
+		//passes EE_Single_Page_Checkout, and it's posted data
869
+		do_action('AHEE__EE_Single_Page_Checkout__process_attendee_information__end', $this, $valid_data);
870
+		return true;
871
+	}
872
+
873
+
874
+	/**
875
+	 *    _process_registrations
876
+	 *
877
+	 * @param EE_Registration[] $registrations
878
+	 * @param array             $valid_data
879
+	 * @return bool|int
880
+	 * @throws \EventEspresso\core\exceptions\EntityNotFoundException
881
+	 * @throws EE_Error
882
+	 * @throws InvalidArgumentException
883
+	 * @throws ReflectionException
884
+	 * @throws RuntimeException
885
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
886
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
887
+	 */
888
+	private function _process_registrations($registrations = array(), $valid_data = array())
889
+	{
890
+		// load resources and set some defaults
891
+		EE_Registry::instance()->load_model('Attendee');
892
+		// holder for primary registrant attendee object
893
+		$this->checkout->primary_attendee_obj = null;
894
+		// array for tracking reg form data for the primary registrant
895
+		$primary_registrant = array(
896
+			'line_item_id' => null,
897
+		);
898
+		$copy_primary       = false;
899
+		// reg form sections that do not contain inputs
900
+		$non_input_form_sections = array(
901
+			'primary_registrant',
902
+			'additional_attendee_reg_info',
903
+			'spco_copy_attendee_chk',
904
+		);
905
+		// attendee counter
906
+		$att_nmbr = 0;
907
+		// grab the saved registrations from the transaction
908
+		foreach ($registrations as $registration) {
909
+			// verify EE_Registration object
910
+			if (! $registration instanceof EE_Registration) {
911
+				EE_Error::add_error(
912
+					esc_html__(
913
+						'An invalid Registration object was discovered when attempting to process your registration information.',
914
+						'event_espresso'
915
+					),
916
+					__FILE__,
917
+					__FUNCTION__,
918
+					__LINE__
919
+				);
920
+				return false;
921
+			}
922
+			/** @var string $reg_url_link */
923
+			$reg_url_link = $registration->reg_url_link();
924
+			// reg_url_link exists ?
925
+			if (! empty($reg_url_link)) {
926
+				// should this registration be processed during this visit ?
927
+				if ($this->checkout->visit_allows_processing_of_this_registration($registration)) {
928
+					// if NOT revisiting, then let's save the registration now,
929
+					// so that we have a REG_ID to use when generating other objects
930
+					if (! $this->checkout->revisit) {
931
+						$registration->save();
932
+					}
933
+					/**
934
+					 * This allows plugins to trigger a fail on processing of a
935
+					 * registration for any conditions they may have for it to pass.
936
+					 *
937
+					 * @var bool   if true is returned by the plugin then the
938
+					 *            registration processing is halted.
939
+					 */
940
+					if (apply_filters(
941
+						'FHEE__EE_SPCO_Reg_Step_Attendee_Information___process_registrations__pre_registration_process',
942
+						false,
943
+						$att_nmbr,
944
+						$registration,
945
+						$registrations,
946
+						$valid_data,
947
+						$this
948
+					)) {
949
+						return false;
950
+					}
951
+
952
+					// Houston, we have a registration!
953
+					$att_nmbr++;
954
+					$this->_attendee_data[$reg_url_link] = array();
955
+					// grab any existing related answer objects
956
+					$this->_registration_answers = $registration->answers();
957
+					// unset( $valid_data[ $reg_url_link ]['additional_attendee_reg_info'] );
958
+					if (isset($valid_data[$reg_url_link])) {
959
+						// do we need to copy basic info from primary attendee ?
960
+						$copy_primary = isset($valid_data[$reg_url_link]['additional_attendee_reg_info'])
961
+										&& absint($valid_data[$reg_url_link]['additional_attendee_reg_info']) === 0
962
+							? true
963
+							: false;
964
+						// filter form input data for this registration
965
+						$valid_data[$reg_url_link] = (array)apply_filters(
966
+							'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
967
+							$valid_data[$reg_url_link]
968
+						);
969
+						if (isset($valid_data['primary_attendee'])) {
970
+							$primary_registrant['line_item_id'] = ! empty($valid_data['primary_attendee'])
971
+								? $valid_data['primary_attendee']
972
+								: false;
973
+							unset($valid_data['primary_attendee']);
974
+						}
975
+						// now loop through our array of valid post data && process attendee reg forms
976
+						foreach ($valid_data[$reg_url_link] as $form_section => $form_inputs) {
977
+							if (! in_array($form_section, $non_input_form_sections)) {
978
+								foreach ($form_inputs as $form_input => $input_value) {
979
+									// \EEH_Debug_Tools::printr( $input_value, $form_input, __FILE__, __LINE__ );
980
+									// check for critical inputs
981
+									if (! $this->_verify_critical_attendee_details_are_set_and_validate_email(
982
+										$form_input,
983
+										$input_value
984
+									)
985
+									) {
986
+										return false;
987
+									}
988
+									// store a bit of data about the primary attendee
989
+									if ($att_nmbr === 1
990
+										&& ! empty($input_value)
991
+										&& $reg_url_link === $primary_registrant['line_item_id']
992
+									) {
993
+										$primary_registrant[$form_input] = $input_value;
994
+									} elseif ($copy_primary
995
+										&& $input_value === null
996
+										&& isset($primary_registrant[$form_input])
997
+									) {
998
+										$input_value = $primary_registrant[$form_input];
999
+									}
1000
+									// now attempt to save the input data
1001
+									if (! $this->_save_registration_form_input(
1002
+										$registration,
1003
+										$form_input,
1004
+										$input_value
1005
+									)
1006
+									) {
1007
+										EE_Error::add_error(
1008
+											sprintf(
1009
+												esc_html__(
1010
+													'Unable to save registration form data for the form input: "%1$s" with the submitted value: "%2$s"',
1011
+													'event_espresso'
1012
+												),
1013
+												$form_input,
1014
+												$input_value
1015
+											),
1016
+											__FILE__,
1017
+											__FUNCTION__,
1018
+											__LINE__
1019
+										);
1020
+										return false;
1021
+									}
1022
+								}
1023
+							}
1024
+						}  // end of foreach ( $valid_data[ $reg_url_link ] as $form_section => $form_inputs )
1025
+					}
1026
+					//EEH_Debug_Tools::printr( $this->_attendee_data, '$this->_attendee_data', __FILE__, __LINE__ );
1027
+					// this registration does not require additional attendee information ?
1028
+					if ($copy_primary
1029
+						&& $att_nmbr > 1
1030
+						&& $this->checkout->primary_attendee_obj instanceof EE_Attendee
1031
+					) {
1032
+						// just copy the primary registrant
1033
+						$attendee = $this->checkout->primary_attendee_obj;
1034
+					} else {
1035
+						// ensure critical details are set for additional attendees
1036
+						$this->_attendee_data[$reg_url_link] = $att_nmbr > 1
1037
+							? $this->_copy_critical_attendee_details_from_primary_registrant(
1038
+								$this->_attendee_data[$reg_url_link]
1039
+							)
1040
+							: $this->_attendee_data[$reg_url_link];
1041
+						// execute create attendee command (which may return an existing attendee)
1042
+						$attendee = EE_Registry::instance()->BUS->execute(
1043
+							new CreateAttendeeCommand(
1044
+								$this->_attendee_data[$reg_url_link],
1045
+								$registration
1046
+							)
1047
+						);
1048
+						// who's #1 ?
1049
+						if ($att_nmbr === 1) {
1050
+							$this->checkout->primary_attendee_obj = $attendee;
1051
+						}
1052
+					}
1053
+					// EEH_Debug_Tools::printr( $attendee, '$attendee', __FILE__, __LINE__ );
1054
+					// add relation to registration, set attendee ID, and cache attendee
1055
+					$this->_associate_attendee_with_registration($registration, $attendee);
1056
+					// \EEH_Debug_Tools::printr( $registration, '$registration', __FILE__, __LINE__ );
1057
+					if (! $registration->attendee() instanceof EE_Attendee) {
1058
+						EE_Error::add_error(
1059
+							sprintf(
1060
+								esc_html__(
1061
+									'Registration %s has an invalid or missing Attendee object.',
1062
+									'event_espresso'
1063
+								),
1064
+								$reg_url_link
1065
+							),
1066
+							__FILE__,
1067
+							__FUNCTION__,
1068
+							__LINE__
1069
+						);
1070
+						return false;
1071
+					}
1072
+					/** @type EE_Registration_Processor $registration_processor */
1073
+					$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
1074
+					// at this point, we should have enough details about the registrant to consider the registration
1075
+					// NOT incomplete
1076
+					$registration_processor->toggle_incomplete_registration_status_to_default(
1077
+						$registration,
1078
+						false,
1079
+						new Context(
1080
+							'spco_reg_step_attendee_information_process_registrations',
1081
+							esc_html__(
1082
+								'Finished populating registration with details from the registration form after submitting the Attendee Information Reg Step.',
1083
+								'event_espresso'
1084
+							)
1085
+						)
1086
+					);
1087
+					// we can also consider the TXN to not have been failed, so temporarily upgrade it's status to
1088
+					// abandoned
1089
+					$this->checkout->transaction->toggle_failed_transaction_status();
1090
+					// if we've gotten this far, then let's save what we have
1091
+					$registration->save();
1092
+					// add relation between TXN and registration
1093
+					$this->_associate_registration_with_transaction($registration);
1094
+				}
1095
+			} else {
1096
+				EE_Error::add_error(
1097
+					esc_html__(
1098
+						'An invalid or missing line item ID was encountered while attempting to process the registration form.',
1099
+						'event_espresso'
1100
+					),
1101
+					__FILE__,
1102
+					__FUNCTION__,
1103
+					__LINE__
1104
+				);
1105
+				// remove malformed data
1106
+				unset($valid_data[$reg_url_link]);
1107
+				return false;
1108
+			}
1109
+
1110
+		} // end of foreach ( $this->checkout->transaction->registrations()  as $registration )
1111
+		return $att_nmbr;
1112
+	}
1113
+
1114
+
1115
+	/**
1116
+	 *    _save_registration_form_input
1117
+	 *
1118
+	 * @param EE_Registration $registration
1119
+	 * @param string          $form_input
1120
+	 * @param string          $input_value
1121
+	 * @return bool
1122
+	 * @throws EE_Error
1123
+	 * @throws InvalidArgumentException
1124
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
1125
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
1126
+	 */
1127
+	private function _save_registration_form_input(
1128
+		EE_Registration $registration,
1129
+		$form_input = '',
1130
+		$input_value = ''
1131
+	) {
1132
+		// \EEH_Debug_Tools::printr( __FUNCTION__, __CLASS__, __FILE__, __LINE__, 2 );
1133
+		// \EEH_Debug_Tools::printr( $form_input, '$form_input', __FILE__, __LINE__ );
1134
+		// \EEH_Debug_Tools::printr( $input_value, '$input_value', __FILE__, __LINE__ );
1135
+		// allow for plugins to hook in and do their own processing of the form input.
1136
+		// For plugins to bypass normal processing here, they just need to return a boolean value.
1137
+		if (apply_filters(
1138
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information___save_registration_form_input',
1139
+			false,
1140
+			$registration,
1141
+			$form_input,
1142
+			$input_value,
1143
+			$this
1144
+		)) {
1145
+			return true;
1146
+		}
1147
+		/*
1148 1148
          * $answer_cache_id is the key used to find the EE_Answer we want
1149 1149
          * @see https://events.codebasehq.com/projects/event-espresso/tickets/10477
1150 1150
          */
1151
-        $answer_cache_id = $this->checkout->reg_url_link
1152
-            ? $form_input . '-' . $registration->reg_url_link()
1153
-            : $form_input;
1154
-        $answer_is_obj   = isset($this->_registration_answers[$answer_cache_id])
1155
-                           && $this->_registration_answers[$answer_cache_id] instanceof EE_Answer
1156
-            ? true
1157
-            : false;
1158
-        //rename form_inputs if they are EE_Attendee properties
1159
-        switch ((string) $form_input) {
1160
-            case 'state':
1161
-            case 'STA_ID':
1162
-                $attendee_property = true;
1163
-                $form_input        = 'STA_ID';
1164
-                break;
1165
-
1166
-            case 'country':
1167
-            case 'CNT_ISO':
1168
-                $attendee_property = true;
1169
-                $form_input        = 'CNT_ISO';
1170
-                break;
1171
-
1172
-            default:
1173
-                $ATT_input = 'ATT_' . $form_input;
1174
-                //EEH_Debug_Tools::printr( $ATT_input, '$ATT_input', __FILE__, __LINE__ );
1175
-                $attendee_property = EEM_Attendee::instance()->has_field($ATT_input) ? true : false;
1176
-                $form_input        = $attendee_property ? 'ATT_' . $form_input : $form_input;
1177
-        }
1178
-        // EEH_Debug_Tools::printr( $answer_cache_id, '$answer_cache_id', __FILE__, __LINE__ );
1179
-        // EEH_Debug_Tools::printr( $attendee_property, '$attendee_property', __FILE__, __LINE__ );
1180
-        // EEH_Debug_Tools::printr( $answer_is_obj, '$answer_is_obj', __FILE__, __LINE__ );
1181
-        // if this form input has a corresponding attendee property
1182
-        if ($attendee_property) {
1183
-            $this->_attendee_data[$registration->reg_url_link()][$form_input] = $input_value;
1184
-            if ($answer_is_obj) {
1185
-                // and delete the corresponding answer since we won't be storing this data in that object
1186
-                $registration->_remove_relation_to($this->_registration_answers[$answer_cache_id], 'Answer');
1187
-                $this->_registration_answers[$answer_cache_id]->delete_permanently();
1188
-            }
1189
-            return true;
1190
-        } elseif ($answer_is_obj) {
1191
-            // save this data to the answer object
1192
-            $this->_registration_answers[$answer_cache_id]->set_value($input_value);
1193
-            $result = $this->_registration_answers[$answer_cache_id]->save();
1194
-            return $result !== false ? true : false;
1195
-        } else {
1196
-            foreach ($this->_registration_answers as $answer) {
1197
-                if ($answer instanceof EE_Answer && $answer->question_ID() === $answer_cache_id) {
1198
-                    $answer->set_value($input_value);
1199
-                    $result = $answer->save();
1200
-                    return $result !== false ? true : false;
1201
-                }
1202
-            }
1203
-        }
1204
-        return false;
1205
-    }
1206
-
1207
-
1208
-    /**
1209
-     *    _verify_critical_attendee_details_are_set
1210
-     *
1211
-     * @param string $form_input
1212
-     * @param string $input_value
1213
-     * @return boolean
1214
-     */
1215
-    private function _verify_critical_attendee_details_are_set_and_validate_email(
1216
-        $form_input = '',
1217
-        $input_value = ''
1218
-    ) {
1219
-        if (empty($input_value)) {
1220
-            // if the form input isn't marked as being required, then just return
1221
-            if (! isset($this->_required_questions[$form_input]) || ! $this->_required_questions[$form_input]) {
1222
-                return true;
1223
-            }
1224
-            switch ($form_input) {
1225
-                case 'fname':
1226
-                    EE_Error::add_error(
1227
-                        esc_html__('First Name is a required value.', 'event_espresso'),
1228
-                        __FILE__,
1229
-                        __FUNCTION__,
1230
-                        __LINE__
1231
-                    );
1232
-                    return false;
1233
-                    break;
1234
-                case 'lname':
1235
-                    EE_Error::add_error(
1236
-                        esc_html__('Last Name is a required value.', 'event_espresso'),
1237
-                        __FILE__,
1238
-                        __FUNCTION__,
1239
-                        __LINE__
1240
-                    );
1241
-                    return false;
1242
-                    break;
1243
-                case 'email':
1244
-                    EE_Error::add_error(
1245
-                        esc_html__('Please enter a valid email address.', 'event_espresso'),
1246
-                        __FILE__,
1247
-                        __FUNCTION__,
1248
-                        __LINE__
1249
-                    );
1250
-                    return false;
1251
-                    break;
1252
-            }
1253
-        }
1254
-        return true;
1255
-    }
1256
-
1257
-
1258
-    /**
1259
-     *    _associate_attendee_with_registration
1260
-     *
1261
-     * @param EE_Registration $registration
1262
-     * @param EE_Attendee     $attendee
1263
-     * @return void
1264
-     * @throws EE_Error
1265
-     * @throws RuntimeException
1266
-     */
1267
-    private function _associate_attendee_with_registration(EE_Registration $registration, EE_Attendee $attendee)
1268
-    {
1269
-        // add relation to attendee
1270
-        $registration->_add_relation_to($attendee, 'Attendee');
1271
-        $registration->set_attendee_id($attendee->ID());
1272
-        $registration->update_cache_after_object_save('Attendee', $attendee);
1273
-    }
1274
-
1275
-
1276
-    /**
1277
-     *    _associate_registration_with_transaction
1278
-     *
1279
-     * @param EE_Registration $registration
1280
-     * @return void
1281
-     * @throws \EE_Error
1282
-     */
1283
-    private function _associate_registration_with_transaction(EE_Registration $registration)
1284
-    {
1285
-        // add relation to registration
1286
-        $this->checkout->transaction->_add_relation_to($registration, 'Registration');
1287
-        $this->checkout->transaction->update_cache_after_object_save('Registration', $registration);
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     *    _copy_critical_attendee_details_from_primary_registrant
1293
-     *    ensures that all attendees at least have data for first name, last name, and email address
1294
-     *
1295
-     * @param array $attendee_data
1296
-     * @return array
1297
-     * @throws \EE_Error
1298
-     */
1299
-    private function _copy_critical_attendee_details_from_primary_registrant($attendee_data = array())
1300
-    {
1301
-        // bare minimum critical details include first name, last name, email address
1302
-        $critical_attendee_details = array('ATT_fname', 'ATT_lname', 'ATT_email');
1303
-        // add address info to critical details?
1304
-        if (apply_filters(
1305
-            'FHEE__EE_SPCO_Reg_Step_Attendee_Information__merge_address_details_with_critical_attendee_details',
1306
-            false
1307
-        )) {
1308
-            $address_details           = array(
1309
-                'ATT_address',
1310
-                'ATT_address2',
1311
-                'ATT_city',
1312
-                'STA_ID',
1313
-                'CNT_ISO',
1314
-                'ATT_zip',
1315
-                'ATT_phone',
1316
-            );
1317
-            $critical_attendee_details = array_merge($critical_attendee_details, $address_details);
1318
-        }
1319
-        foreach ($critical_attendee_details as $critical_attendee_detail) {
1320
-            if (! isset($attendee_data[$critical_attendee_detail])
1321
-                || empty($attendee_data[$critical_attendee_detail])
1322
-            ) {
1323
-                $attendee_data[$critical_attendee_detail] = $this->checkout->primary_attendee_obj->get(
1324
-                    $critical_attendee_detail
1325
-                );
1326
-            }
1327
-        }
1328
-        return $attendee_data;
1329
-    }
1330
-
1331
-
1332
-    /**
1333
-     *    update_reg_step
1334
-     *    this is the final step after a user  revisits the site to edit their attendee information
1335
-     *    this gets called AFTER the process_reg_step() method above
1336
-     *
1337
-     * @return bool
1338
-     * @throws EE_Error
1339
-     * @throws InvalidArgumentException
1340
-     * @throws ReflectionException
1341
-     * @throws RuntimeException
1342
-     * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
1343
-     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
1344
-     */
1345
-    public function update_reg_step()
1346
-    {
1347
-        // save everything
1348
-        if ($this->process_reg_step()) {
1349
-            $this->checkout->redirect     = true;
1350
-            $this->checkout->redirect_url = add_query_arg(
1351
-                array(
1352
-                    'e_reg_url_link' => $this->checkout->reg_url_link,
1353
-                    'revisit'        => true,
1354
-                ),
1355
-                $this->checkout->thank_you_page_url
1356
-            );
1357
-            $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1358
-            return true;
1359
-        }
1360
-        return false;
1361
-    }
1151
+		$answer_cache_id = $this->checkout->reg_url_link
1152
+			? $form_input . '-' . $registration->reg_url_link()
1153
+			: $form_input;
1154
+		$answer_is_obj   = isset($this->_registration_answers[$answer_cache_id])
1155
+						   && $this->_registration_answers[$answer_cache_id] instanceof EE_Answer
1156
+			? true
1157
+			: false;
1158
+		//rename form_inputs if they are EE_Attendee properties
1159
+		switch ((string) $form_input) {
1160
+			case 'state':
1161
+			case 'STA_ID':
1162
+				$attendee_property = true;
1163
+				$form_input        = 'STA_ID';
1164
+				break;
1165
+
1166
+			case 'country':
1167
+			case 'CNT_ISO':
1168
+				$attendee_property = true;
1169
+				$form_input        = 'CNT_ISO';
1170
+				break;
1171
+
1172
+			default:
1173
+				$ATT_input = 'ATT_' . $form_input;
1174
+				//EEH_Debug_Tools::printr( $ATT_input, '$ATT_input', __FILE__, __LINE__ );
1175
+				$attendee_property = EEM_Attendee::instance()->has_field($ATT_input) ? true : false;
1176
+				$form_input        = $attendee_property ? 'ATT_' . $form_input : $form_input;
1177
+		}
1178
+		// EEH_Debug_Tools::printr( $answer_cache_id, '$answer_cache_id', __FILE__, __LINE__ );
1179
+		// EEH_Debug_Tools::printr( $attendee_property, '$attendee_property', __FILE__, __LINE__ );
1180
+		// EEH_Debug_Tools::printr( $answer_is_obj, '$answer_is_obj', __FILE__, __LINE__ );
1181
+		// if this form input has a corresponding attendee property
1182
+		if ($attendee_property) {
1183
+			$this->_attendee_data[$registration->reg_url_link()][$form_input] = $input_value;
1184
+			if ($answer_is_obj) {
1185
+				// and delete the corresponding answer since we won't be storing this data in that object
1186
+				$registration->_remove_relation_to($this->_registration_answers[$answer_cache_id], 'Answer');
1187
+				$this->_registration_answers[$answer_cache_id]->delete_permanently();
1188
+			}
1189
+			return true;
1190
+		} elseif ($answer_is_obj) {
1191
+			// save this data to the answer object
1192
+			$this->_registration_answers[$answer_cache_id]->set_value($input_value);
1193
+			$result = $this->_registration_answers[$answer_cache_id]->save();
1194
+			return $result !== false ? true : false;
1195
+		} else {
1196
+			foreach ($this->_registration_answers as $answer) {
1197
+				if ($answer instanceof EE_Answer && $answer->question_ID() === $answer_cache_id) {
1198
+					$answer->set_value($input_value);
1199
+					$result = $answer->save();
1200
+					return $result !== false ? true : false;
1201
+				}
1202
+			}
1203
+		}
1204
+		return false;
1205
+	}
1206
+
1207
+
1208
+	/**
1209
+	 *    _verify_critical_attendee_details_are_set
1210
+	 *
1211
+	 * @param string $form_input
1212
+	 * @param string $input_value
1213
+	 * @return boolean
1214
+	 */
1215
+	private function _verify_critical_attendee_details_are_set_and_validate_email(
1216
+		$form_input = '',
1217
+		$input_value = ''
1218
+	) {
1219
+		if (empty($input_value)) {
1220
+			// if the form input isn't marked as being required, then just return
1221
+			if (! isset($this->_required_questions[$form_input]) || ! $this->_required_questions[$form_input]) {
1222
+				return true;
1223
+			}
1224
+			switch ($form_input) {
1225
+				case 'fname':
1226
+					EE_Error::add_error(
1227
+						esc_html__('First Name is a required value.', 'event_espresso'),
1228
+						__FILE__,
1229
+						__FUNCTION__,
1230
+						__LINE__
1231
+					);
1232
+					return false;
1233
+					break;
1234
+				case 'lname':
1235
+					EE_Error::add_error(
1236
+						esc_html__('Last Name is a required value.', 'event_espresso'),
1237
+						__FILE__,
1238
+						__FUNCTION__,
1239
+						__LINE__
1240
+					);
1241
+					return false;
1242
+					break;
1243
+				case 'email':
1244
+					EE_Error::add_error(
1245
+						esc_html__('Please enter a valid email address.', 'event_espresso'),
1246
+						__FILE__,
1247
+						__FUNCTION__,
1248
+						__LINE__
1249
+					);
1250
+					return false;
1251
+					break;
1252
+			}
1253
+		}
1254
+		return true;
1255
+	}
1256
+
1257
+
1258
+	/**
1259
+	 *    _associate_attendee_with_registration
1260
+	 *
1261
+	 * @param EE_Registration $registration
1262
+	 * @param EE_Attendee     $attendee
1263
+	 * @return void
1264
+	 * @throws EE_Error
1265
+	 * @throws RuntimeException
1266
+	 */
1267
+	private function _associate_attendee_with_registration(EE_Registration $registration, EE_Attendee $attendee)
1268
+	{
1269
+		// add relation to attendee
1270
+		$registration->_add_relation_to($attendee, 'Attendee');
1271
+		$registration->set_attendee_id($attendee->ID());
1272
+		$registration->update_cache_after_object_save('Attendee', $attendee);
1273
+	}
1274
+
1275
+
1276
+	/**
1277
+	 *    _associate_registration_with_transaction
1278
+	 *
1279
+	 * @param EE_Registration $registration
1280
+	 * @return void
1281
+	 * @throws \EE_Error
1282
+	 */
1283
+	private function _associate_registration_with_transaction(EE_Registration $registration)
1284
+	{
1285
+		// add relation to registration
1286
+		$this->checkout->transaction->_add_relation_to($registration, 'Registration');
1287
+		$this->checkout->transaction->update_cache_after_object_save('Registration', $registration);
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 *    _copy_critical_attendee_details_from_primary_registrant
1293
+	 *    ensures that all attendees at least have data for first name, last name, and email address
1294
+	 *
1295
+	 * @param array $attendee_data
1296
+	 * @return array
1297
+	 * @throws \EE_Error
1298
+	 */
1299
+	private function _copy_critical_attendee_details_from_primary_registrant($attendee_data = array())
1300
+	{
1301
+		// bare minimum critical details include first name, last name, email address
1302
+		$critical_attendee_details = array('ATT_fname', 'ATT_lname', 'ATT_email');
1303
+		// add address info to critical details?
1304
+		if (apply_filters(
1305
+			'FHEE__EE_SPCO_Reg_Step_Attendee_Information__merge_address_details_with_critical_attendee_details',
1306
+			false
1307
+		)) {
1308
+			$address_details           = array(
1309
+				'ATT_address',
1310
+				'ATT_address2',
1311
+				'ATT_city',
1312
+				'STA_ID',
1313
+				'CNT_ISO',
1314
+				'ATT_zip',
1315
+				'ATT_phone',
1316
+			);
1317
+			$critical_attendee_details = array_merge($critical_attendee_details, $address_details);
1318
+		}
1319
+		foreach ($critical_attendee_details as $critical_attendee_detail) {
1320
+			if (! isset($attendee_data[$critical_attendee_detail])
1321
+				|| empty($attendee_data[$critical_attendee_detail])
1322
+			) {
1323
+				$attendee_data[$critical_attendee_detail] = $this->checkout->primary_attendee_obj->get(
1324
+					$critical_attendee_detail
1325
+				);
1326
+			}
1327
+		}
1328
+		return $attendee_data;
1329
+	}
1330
+
1331
+
1332
+	/**
1333
+	 *    update_reg_step
1334
+	 *    this is the final step after a user  revisits the site to edit their attendee information
1335
+	 *    this gets called AFTER the process_reg_step() method above
1336
+	 *
1337
+	 * @return bool
1338
+	 * @throws EE_Error
1339
+	 * @throws InvalidArgumentException
1340
+	 * @throws ReflectionException
1341
+	 * @throws RuntimeException
1342
+	 * @throws \EventEspresso\core\exceptions\InvalidDataTypeException
1343
+	 * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
1344
+	 */
1345
+	public function update_reg_step()
1346
+	{
1347
+		// save everything
1348
+		if ($this->process_reg_step()) {
1349
+			$this->checkout->redirect     = true;
1350
+			$this->checkout->redirect_url = add_query_arg(
1351
+				array(
1352
+					'e_reg_url_link' => $this->checkout->reg_url_link,
1353
+					'revisit'        => true,
1354
+				),
1355
+				$this->checkout->thank_you_page_url
1356
+			);
1357
+			$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1358
+			return true;
1359
+		}
1360
+		return false;
1361
+	}
1362 1362
 }
Please login to merge, or discard this patch.
Spacing   +53 added lines, -54 removed lines patch added patch discarded remove patch
@@ -48,7 +48,7 @@  discard block
 block discarded – undo
48 48
     {
49 49
         $this->_slug     = 'attendee_information';
50 50
         $this->_name     = esc_html__('Attendee Information', 'event_espresso');
51
-        $this->_template = SPCO_REG_STEPS_PATH . $this->_slug . DS . 'attendee_info_main.template.php';
51
+        $this->_template = SPCO_REG_STEPS_PATH.$this->_slug.DS.'attendee_info_main.template.php';
52 52
         $this->checkout  = $checkout;
53 53
         $this->_reset_success_message();
54 54
         $this->set_instructions(
@@ -59,11 +59,11 @@  discard block
 block discarded – undo
59 59
 
60 60
     public function translate_js_strings()
61 61
     {
62
-        EE_Registry::$i18n_js_strings['required_field']            = esc_html__(
62
+        EE_Registry::$i18n_js_strings['required_field'] = esc_html__(
63 63
             ' is a required question.',
64 64
             'event_espresso'
65 65
         );
66
-        EE_Registry::$i18n_js_strings['required_multi_field']      = esc_html__(
66
+        EE_Registry::$i18n_js_strings['required_multi_field'] = esc_html__(
67 67
             ' is a required question. Please enter a value for at least one of the options.',
68 68
             'event_espresso'
69 69
         );
@@ -71,18 +71,18 @@  discard block
 block discarded – undo
71 71
             'Please answer all required questions correctly before proceeding.',
72 72
             'event_espresso'
73 73
         );
74
-        EE_Registry::$i18n_js_strings['attendee_info_copied']      = sprintf(
74
+        EE_Registry::$i18n_js_strings['attendee_info_copied'] = sprintf(
75 75
             esc_html__(
76 76
                 'The attendee information was successfully copied.%sPlease ensure the rest of the registration form is completed before proceeding.',
77 77
                 'event_espresso'
78 78
             ),
79 79
             '<br/>'
80 80
         );
81
-        EE_Registry::$i18n_js_strings['attendee_info_copy_error']  = esc_html__(
81
+        EE_Registry::$i18n_js_strings['attendee_info_copy_error'] = esc_html__(
82 82
             'An unknown error occurred on the server while attempting to copy the attendee information. Please refresh the page and try again.',
83 83
             'event_espresso'
84 84
         );
85
-        EE_Registry::$i18n_js_strings['enter_valid_email']         = esc_html__(
85
+        EE_Registry::$i18n_js_strings['enter_valid_email'] = esc_html__(
86 86
             'You must enter a valid email address.',
87 87
             'event_espresso'
88 88
         );
@@ -145,11 +145,11 @@  discard block
 block discarded – undo
145 145
                     && $this->checkout->visit_allows_processing_of_this_registration($registration)
146 146
                 ) {
147 147
                     $subsection = $this->_registrations_reg_form($registration);
148
-                    if(!$subsection instanceof EE_Form_Section_Proper) {
148
+                    if ( ! $subsection instanceof EE_Form_Section_Proper) {
149 149
                         continue;
150 150
                     }
151
-                    $subsections[ $registration->reg_url_link() ] = $subsection;
152
-                    if (! $this->checkout->admin_request) {
151
+                    $subsections[$registration->reg_url_link()] = $subsection;
152
+                    if ( ! $this->checkout->admin_request) {
153 153
                         $template_args['registrations'][$registration->reg_url_link()]    = $registration;
154 154
                         $template_args['ticket_count'][$registration->ticket()->ID()]     = isset(
155 155
                             $template_args['ticket_count'][$registration->ticket()->ID()]
@@ -197,8 +197,7 @@  discard block
 block discarded – undo
197 197
                 'html_id'         => $this->reg_form_name(),
198 198
                 'subsections'     => $subsections,
199 199
                 'layout_strategy' => $this->checkout->admin_request ?
200
-                    new EE_Div_Per_Section_Layout() :
201
-                    new EE_Template_Layout(
200
+                    new EE_Div_Per_Section_Layout() : new EE_Template_Layout(
202 201
                         array(
203 202
                             'layout_template_file' => $this->_template, // layout_template
204 203
                             'template_args'        => $template_args,
@@ -241,7 +240,7 @@  discard block
 block discarded – undo
241 240
             if ($question_groups) {
242 241
                 // array of params to pass to parent constructor
243 242
                 $form_args = array(
244
-                    'html_id'         => 'ee-registration-' . $registration->reg_url_link(),
243
+                    'html_id'         => 'ee-registration-'.$registration->reg_url_link(),
245 244
                     'html_class'      => 'ee-reg-form-attendee-dv',
246 245
                     'html_style'      => $this->checkout->admin_request
247 246
                         ? 'padding:0em 2em 1em; margin:3em 0 0; border:1px solid #ddd;'
@@ -297,7 +296,7 @@  discard block
 block discarded – undo
297 296
         // generate hidden input
298 297
         return new EE_Hidden_Input(
299 298
             array(
300
-                'html_id' => 'additional-attendee-reg-info-' . $registration->reg_url_link(),
299
+                'html_id' => 'additional-attendee-reg-info-'.$registration->reg_url_link(),
301 300
                 'default' => $additional_attendee_reg_info,
302 301
             )
303 302
         );
@@ -317,11 +316,11 @@  discard block
 block discarded – undo
317 316
     {
318 317
         // array of params to pass to parent constructor
319 318
         $form_args = array(
320
-            'html_id'         => 'ee-reg-form-qstn-grp-' . $question_group->identifier() . '-' . $registration->ID(),
319
+            'html_id'         => 'ee-reg-form-qstn-grp-'.$question_group->identifier().'-'.$registration->ID(),
321 320
             'html_class'      => $this->checkout->admin_request
322 321
                 ? 'form-table ee-reg-form-qstn-grp-dv'
323 322
                 : 'ee-reg-form-qstn-grp-dv',
324
-            'html_label_id'   => 'ee-reg-form-qstn-grp-' . $question_group->identifier() .  '-' . $registration->ID() . '-lbl',
323
+            'html_label_id'   => 'ee-reg-form-qstn-grp-'.$question_group->identifier().'-'.$registration->ID().'-lbl',
325 324
             'subsections'     => array(
326 325
                 'reg_form_qstn_grp_hdr' => $this->_question_group_header($question_group),
327 326
             ),
@@ -332,7 +331,7 @@  discard block
 block discarded – undo
332 331
         // where params
333 332
         $query_params = array('QST_deleted' => 0);
334 333
         // don't load admin only questions on the frontend
335
-        if (! $this->checkout->admin_request) {
334
+        if ( ! $this->checkout->admin_request) {
336 335
             $query_params['QST_admin_only'] = array('!=', true);
337 336
         }
338 337
         $questions = $question_group->get_many_related(
@@ -476,7 +475,7 @@  discard block
 block discarded – undo
476 475
     {
477 476
         return new EE_Form_Section_HTML(
478 477
             EEH_Template::locate_template(
479
-                SPCO_REG_STEPS_PATH . $this->_slug . DS . '_auto_copy_attendee_info.template.php',
478
+                SPCO_REG_STEPS_PATH.$this->_slug.DS.'_auto_copy_attendee_info.template.php',
480 479
                 apply_filters(
481 480
                     'FHEE__EE_SPCO_Reg_Step_Attendee_Information__auto_copy_attendee_info__template_args',
482 481
                     array()
@@ -508,16 +507,16 @@  discard block
 block discarded – undo
508 507
                 if ($registration->ticket()->ID() !== $prev_ticket) {
509 508
                     $item_name = $registration->ticket()->name();
510 509
                     $item_name .= $registration->ticket()->description() !== ''
511
-                        ? ' - ' . $registration->ticket()->description()
510
+                        ? ' - '.$registration->ticket()->description()
512 511
                         : '';
513
-                    $copy_attendee_info_inputs['spco_copy_attendee_chk[ticket-' . $registration->ticket()->ID() . ']'] =
512
+                    $copy_attendee_info_inputs['spco_copy_attendee_chk[ticket-'.$registration->ticket()->ID().']'] =
514 513
                         new EE_Form_Section_HTML(
515
-                            '<h6 class="spco-copy-attendee-event-hdr">' . $item_name . '</h6>'
514
+                            '<h6 class="spco-copy-attendee-event-hdr">'.$item_name.'</h6>'
516 515
                         );
517 516
                     $prev_ticket = $registration->ticket()->ID();
518 517
                 }
519 518
 
520
-                $copy_attendee_info_inputs['spco_copy_attendee_chk[' . $registration->ID() . ']'] =
519
+                $copy_attendee_info_inputs['spco_copy_attendee_chk['.$registration->ID().']'] =
521 520
                     new EE_Checkbox_Multi_Input(
522 521
                         array(
523 522
                             $registration->ID() => sprintf(
@@ -526,7 +525,7 @@  discard block
 block discarded – undo
526 525
                             ),
527 526
                         ),
528 527
                         array(
529
-                            'html_id'                 => 'spco-copy-attendee-chk-' . $registration->reg_url_link(),
528
+                            'html_id'                 => 'spco-copy-attendee-chk-'.$registration->reg_url_link(),
530 529
                             'html_class'              => 'spco-copy-attendee-chk ee-do-not-validate',
531 530
                             'display_html_label_text' => false,
532 531
                         )
@@ -575,7 +574,7 @@  discard block
 block discarded – undo
575 574
             $registration,
576 575
             $question->system_ID()
577 576
         );
578
-        $answer       = $answer_value === null
577
+        $answer = $answer_value === null
579 578
             ? EEM_Answer::instance()->get_one(
580 579
                 array(array('QST_ID' => $question->ID(), 'REG_ID' => $registration->ID()))
581 580
             )
@@ -592,14 +591,14 @@  discard block
 block discarded – undo
592 591
         }
593 592
         // verify instance
594 593
         if ($answer instanceof EE_Answer) {
595
-            if (! empty($answer_value)) {
594
+            if ( ! empty($answer_value)) {
596 595
                 $answer->set('ANS_value', $answer_value);
597 596
             }
598 597
             $answer->cache('Question', $question);
599 598
             //remember system ID had a bug where sometimes it could be null
600 599
             $answer_cache_id = $question->is_system_question()
601
-                ? $question->system_ID() . '-' . $registration->reg_url_link()
602
-                : $question->ID() . '-' . $registration->reg_url_link();
600
+                ? $question->system_ID().'-'.$registration->reg_url_link()
601
+                : $question->ID().'-'.$registration->reg_url_link();
603 602
             $registration->cache('Answer', $answer, $answer_cache_id);
604 603
         }
605 604
         return $this->_generate_question_input($registration, $question, $answer);
@@ -631,18 +630,18 @@  discard block
 block discarded – undo
631 630
             10,
632 631
             4
633 632
         );
634
-        $input_constructor_args                  = array(
635
-            'html_name'        => 'ee_reg_qstn[' . $registration->ID() . '][' . $identifier . ']',
636
-            'html_id'          => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
637
-            'html_class'       => 'ee-reg-qstn ee-reg-qstn-' . $identifier,
638
-            'html_label_id'    => 'ee_reg_qstn-' . $registration->ID() . '-' . $identifier,
633
+        $input_constructor_args = array(
634
+            'html_name'        => 'ee_reg_qstn['.$registration->ID().']['.$identifier.']',
635
+            'html_id'          => 'ee_reg_qstn-'.$registration->ID().'-'.$identifier,
636
+            'html_class'       => 'ee-reg-qstn ee-reg-qstn-'.$identifier,
637
+            'html_label_id'    => 'ee_reg_qstn-'.$registration->ID().'-'.$identifier,
639 638
             'html_label_class' => 'ee-reg-qstn',
640 639
         );
641 640
         $input_constructor_args['html_label_id'] .= '-lbl';
642 641
         if ($answer instanceof EE_Answer && $answer->ID()) {
643
-            $input_constructor_args['html_name']     .= '[' . $answer->ID() . ']';
644
-            $input_constructor_args['html_id']       .= '-' . $answer->ID();
645
-            $input_constructor_args['html_label_id'] .= '-' . $answer->ID();
642
+            $input_constructor_args['html_name']     .= '['.$answer->ID().']';
643
+            $input_constructor_args['html_id']       .= '-'.$answer->ID();
644
+            $input_constructor_args['html_label_id'] .= '-'.$answer->ID();
646 645
         }
647 646
         $form_input = $question->generate_form_input(
648 647
             $registration,
@@ -685,7 +684,7 @@  discard block
 block discarded – undo
685 684
         $countries = $this->checkout->action === 'process_reg_step'
686 685
             ? EEM_Country::instance()->get_all_countries()
687 686
             : EEM_Country::instance()->get_all_active_countries();
688
-        if (! empty($countries)) {
687
+        if ( ! empty($countries)) {
689 688
             foreach ($countries as $country) {
690 689
                 if ($country instanceof EE_Country) {
691 690
                     $country_options[$country->ID()] = $country->name();
@@ -735,7 +734,7 @@  discard block
 block discarded – undo
735 734
         $states        = $this->checkout->action === 'process_reg_step'
736 735
             ? EEM_State::instance()->get_all_states()
737 736
             : EEM_State::instance()->get_all_active_states();
738
-        if (! empty($states)) {
737
+        if ( ! empty($states)) {
739 738
             foreach ($states as $state) {
740 739
                 if ($state instanceof EE_State) {
741 740
                     $state_options[$state->country()->name()][$state->ID()] = $state->name();
@@ -787,7 +786,7 @@  discard block
 block discarded – undo
787 786
             );
788 787
             return false;
789 788
         }
790
-        if (! $this->checkout->transaction instanceof EE_Transaction || ! $this->checkout->continue_reg) {
789
+        if ( ! $this->checkout->transaction instanceof EE_Transaction || ! $this->checkout->continue_reg) {
791 790
             EE_Error::add_error(
792 791
                 esc_html__(
793 792
                     'A valid transaction could not be initiated for processing your registrations.',
@@ -804,10 +803,10 @@  discard block
 block discarded – undo
804 803
         // verify we got the goods
805 804
         if (empty($registrations)) {
806 805
             //combine the old translated string with a new one, in order to not break translations
807
-            $error_message = esc_html__( 'Your form data could not be applied to any valid registrations.', 'event_espresso' )
806
+            $error_message = esc_html__('Your form data could not be applied to any valid registrations.', 'event_espresso')
808 807
                              . sprintf(
809 808
                                  esc_html__('%3$sThis can sometimes happen if too much time has been taken to complete the registration process.%3$sPlease return to the %1$sEvent List%2$s and reselect your tickets. If the problem continues, please contact the site administrator.', 'event_espresso'),
810
-                                 '<a href="' . get_post_type_archive_link('espresso_events') . '" >',
809
+                                 '<a href="'.get_post_type_archive_link('espresso_events').'" >',
811 810
                                  '</a>',
812 811
                                  '<br />'
813 812
                              );
@@ -826,7 +825,7 @@  discard block
 block discarded – undo
826 825
         if ($registrations_processed === false) {
827 826
             // but return immediately if the previous step exited early due to errors
828 827
             return false;
829
-        } elseif (! $this->checkout->revisit && $registrations_processed !== $this->checkout->total_ticket_count) {
828
+        } elseif ( ! $this->checkout->revisit && $registrations_processed !== $this->checkout->total_ticket_count) {
830 829
             // generate a correctly translated string for all possible singular/plural combinations
831 830
             if ($this->checkout->total_ticket_count === 1 && $registrations_processed !== 1) {
832 831
                 $error_msg = sprintf(
@@ -907,7 +906,7 @@  discard block
 block discarded – undo
907 906
         // grab the saved registrations from the transaction
908 907
         foreach ($registrations as $registration) {
909 908
             // verify EE_Registration object
910
-            if (! $registration instanceof EE_Registration) {
909
+            if ( ! $registration instanceof EE_Registration) {
911 910
                 EE_Error::add_error(
912 911
                     esc_html__(
913 912
                         'An invalid Registration object was discovered when attempting to process your registration information.',
@@ -922,12 +921,12 @@  discard block
 block discarded – undo
922 921
             /** @var string $reg_url_link */
923 922
             $reg_url_link = $registration->reg_url_link();
924 923
             // reg_url_link exists ?
925
-            if (! empty($reg_url_link)) {
924
+            if ( ! empty($reg_url_link)) {
926 925
                 // should this registration be processed during this visit ?
927 926
                 if ($this->checkout->visit_allows_processing_of_this_registration($registration)) {
928 927
                     // if NOT revisiting, then let's save the registration now,
929 928
                     // so that we have a REG_ID to use when generating other objects
930
-                    if (! $this->checkout->revisit) {
929
+                    if ( ! $this->checkout->revisit) {
931 930
                         $registration->save();
932 931
                     }
933 932
                     /**
@@ -962,7 +961,7 @@  discard block
 block discarded – undo
962 961
                             ? true
963 962
                             : false;
964 963
                         // filter form input data for this registration
965
-                        $valid_data[$reg_url_link] = (array)apply_filters(
964
+                        $valid_data[$reg_url_link] = (array) apply_filters(
966 965
                             'FHEE__EE_Single_Page_Checkout__process_attendee_information__valid_data_line_item',
967 966
                             $valid_data[$reg_url_link]
968 967
                         );
@@ -974,11 +973,11 @@  discard block
 block discarded – undo
974 973
                         }
975 974
                         // now loop through our array of valid post data && process attendee reg forms
976 975
                         foreach ($valid_data[$reg_url_link] as $form_section => $form_inputs) {
977
-                            if (! in_array($form_section, $non_input_form_sections)) {
976
+                            if ( ! in_array($form_section, $non_input_form_sections)) {
978 977
                                 foreach ($form_inputs as $form_input => $input_value) {
979 978
                                     // \EEH_Debug_Tools::printr( $input_value, $form_input, __FILE__, __LINE__ );
980 979
                                     // check for critical inputs
981
-                                    if (! $this->_verify_critical_attendee_details_are_set_and_validate_email(
980
+                                    if ( ! $this->_verify_critical_attendee_details_are_set_and_validate_email(
982 981
                                         $form_input,
983 982
                                         $input_value
984 983
                                     )
@@ -998,7 +997,7 @@  discard block
 block discarded – undo
998 997
                                         $input_value = $primary_registrant[$form_input];
999 998
                                     }
1000 999
                                     // now attempt to save the input data
1001
-                                    if (! $this->_save_registration_form_input(
1000
+                                    if ( ! $this->_save_registration_form_input(
1002 1001
                                         $registration,
1003 1002
                                         $form_input,
1004 1003
                                         $input_value
@@ -1054,7 +1053,7 @@  discard block
 block discarded – undo
1054 1053
                     // add relation to registration, set attendee ID, and cache attendee
1055 1054
                     $this->_associate_attendee_with_registration($registration, $attendee);
1056 1055
                     // \EEH_Debug_Tools::printr( $registration, '$registration', __FILE__, __LINE__ );
1057
-                    if (! $registration->attendee() instanceof EE_Attendee) {
1056
+                    if ( ! $registration->attendee() instanceof EE_Attendee) {
1058 1057
                         EE_Error::add_error(
1059 1058
                             sprintf(
1060 1059
                                 esc_html__(
@@ -1149,7 +1148,7 @@  discard block
 block discarded – undo
1149 1148
          * @see https://events.codebasehq.com/projects/event-espresso/tickets/10477
1150 1149
          */
1151 1150
         $answer_cache_id = $this->checkout->reg_url_link
1152
-            ? $form_input . '-' . $registration->reg_url_link()
1151
+            ? $form_input.'-'.$registration->reg_url_link()
1153 1152
             : $form_input;
1154 1153
         $answer_is_obj   = isset($this->_registration_answers[$answer_cache_id])
1155 1154
                            && $this->_registration_answers[$answer_cache_id] instanceof EE_Answer
@@ -1170,10 +1169,10 @@  discard block
 block discarded – undo
1170 1169
                 break;
1171 1170
 
1172 1171
             default:
1173
-                $ATT_input = 'ATT_' . $form_input;
1172
+                $ATT_input = 'ATT_'.$form_input;
1174 1173
                 //EEH_Debug_Tools::printr( $ATT_input, '$ATT_input', __FILE__, __LINE__ );
1175 1174
                 $attendee_property = EEM_Attendee::instance()->has_field($ATT_input) ? true : false;
1176
-                $form_input        = $attendee_property ? 'ATT_' . $form_input : $form_input;
1175
+                $form_input        = $attendee_property ? 'ATT_'.$form_input : $form_input;
1177 1176
         }
1178 1177
         // EEH_Debug_Tools::printr( $answer_cache_id, '$answer_cache_id', __FILE__, __LINE__ );
1179 1178
         // EEH_Debug_Tools::printr( $attendee_property, '$attendee_property', __FILE__, __LINE__ );
@@ -1218,7 +1217,7 @@  discard block
 block discarded – undo
1218 1217
     ) {
1219 1218
         if (empty($input_value)) {
1220 1219
             // if the form input isn't marked as being required, then just return
1221
-            if (! isset($this->_required_questions[$form_input]) || ! $this->_required_questions[$form_input]) {
1220
+            if ( ! isset($this->_required_questions[$form_input]) || ! $this->_required_questions[$form_input]) {
1222 1221
                 return true;
1223 1222
             }
1224 1223
             switch ($form_input) {
@@ -1305,7 +1304,7 @@  discard block
 block discarded – undo
1305 1304
             'FHEE__EE_SPCO_Reg_Step_Attendee_Information__merge_address_details_with_critical_attendee_details',
1306 1305
             false
1307 1306
         )) {
1308
-            $address_details           = array(
1307
+            $address_details = array(
1309 1308
                 'ATT_address',
1310 1309
                 'ATT_address2',
1311 1310
                 'ATT_city',
@@ -1317,7 +1316,7 @@  discard block
 block discarded – undo
1317 1316
             $critical_attendee_details = array_merge($critical_attendee_details, $address_details);
1318 1317
         }
1319 1318
         foreach ($critical_attendee_details as $critical_attendee_detail) {
1320
-            if (! isset($attendee_data[$critical_attendee_detail])
1319
+            if ( ! isset($attendee_data[$critical_attendee_detail])
1321 1320
                 || empty($attendee_data[$critical_attendee_detail])
1322 1321
             ) {
1323 1322
                 $attendee_data[$critical_attendee_detail] = $this->checkout->primary_attendee_obj->get(
Please login to merge, or discard this patch.