Completed
Branch dev (97838e)
by
unknown
18:15 queued 10:24
created
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +2128 added lines, -2128 removed lines patch added patch discarded remove patch
@@ -21,2132 +21,2132 @@
 block discarded – undo
21 21
  */
22 22
 class EEH_Line_Item
23 23
 {
24
-    /**
25
-     * @var EE_Line_Item[]
26
-    */
27
-    private static $global_taxes;
28
-
29
-
30
-    /**
31
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
34
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
-     * to keep the registration final prices in-sync with the transaction's total.
36
-     *
37
-     * @param EE_Line_Item $parent_line_item
38
-     * @param string       $name
39
-     * @param float        $unit_price
40
-     * @param string       $description
41
-     * @param int          $quantity
42
-     * @param boolean      $taxable
43
-     * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
-     * @param bool         $return_item
45
-     * @param bool         $recalculate_totals
46
-     * @return boolean|EE_Line_Item success
47
-     * @throws EE_Error
48
-     * @throws ReflectionException
49
-     */
50
-    public static function add_unrelated_item(
51
-        EE_Line_Item $parent_line_item,
52
-        string $name,
53
-        float $unit_price,
54
-        string $description = '',
55
-        int $quantity = 1,
56
-        bool $taxable = false,
57
-        ?string $code = null,
58
-        bool $return_item = false,
59
-        bool $recalculate_totals = true
60
-    ) {
61
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
-        $line_item      = EE_Line_Item::new_instance(
63
-            [
64
-                'LIN_name'       => $name,
65
-                'LIN_desc'       => $description,
66
-                'LIN_unit_price' => $unit_price,
67
-                'LIN_quantity'   => $quantity,
68
-                'LIN_percent'    => null,
69
-                'LIN_is_taxable' => $taxable,
70
-                'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
-                    ? count($items_subtotal->children())
72
-                    : 0,
73
-                'LIN_total'      => (float) $unit_price * (int) $quantity,
74
-                'LIN_type'       => EEM_Line_Item::type_line_item,
75
-                'LIN_code'       => $code,
76
-            ]
77
-        );
78
-        $line_item      = apply_filters(
79
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
-            $line_item,
81
-            $parent_line_item
82
-        );
83
-        $added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
-        return $return_item ? $line_item : $added;
85
-    }
86
-
87
-
88
-    /**
89
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
90
-     * in the correct spot in the line item tree. Does not automatically
91
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
-     * registrations' final prices (which should probably change because of this).
93
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
94
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
96
-     *
97
-     * @param EE_Line_Item $parent_line_item
98
-     * @param string       $name
99
-     * @param float        $percentage_amount
100
-     * @param string       $description
101
-     * @param boolean      $taxable
102
-     * @param string|null  $code
103
-     * @param bool         $return_item
104
-     * @return boolean|EE_Line_Item success
105
-     * @throws EE_Error
106
-     * @throws ReflectionException
107
-     */
108
-    public static function add_percentage_based_item(
109
-        EE_Line_Item $parent_line_item,
110
-        string $name,
111
-        float $percentage_amount,
112
-        string $description = '',
113
-        bool $taxable = false,
114
-        ?string $code = null,
115
-        bool $return_item = false
116
-    ) {
117
-        $total = $percentage_amount * $parent_line_item->total() / 100;
118
-        $line_item = EE_Line_Item::new_instance(
119
-            [
120
-                'LIN_name'       => $name,
121
-                'LIN_desc'       => $description,
122
-                'LIN_unit_price' => 0,
123
-                'LIN_percent'    => $percentage_amount,
124
-                'LIN_quantity'   => 1,
125
-                'LIN_is_taxable' => $taxable,
126
-                'LIN_total'      => (float) $total,
127
-                'LIN_type'       => EEM_Line_Item::type_line_item,
128
-                'LIN_parent'     => $parent_line_item->ID(),
129
-                'LIN_code'       => $code,
130
-            ]
131
-        );
132
-        $line_item = apply_filters(
133
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
-            $line_item
135
-        );
136
-        $added     = $parent_line_item->add_child_line_item($line_item, false);
137
-        return $return_item ? $line_item : $added;
138
-    }
139
-
140
-
141
-    /**
142
-     * Returns the new line item created by adding a purchase of the ticket
143
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
144
-     * If this ticket has already been purchased, just increments its count.
145
-     * Automatically re-calculates the line item totals and updates the related transaction. But
146
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
-     * should probably change because of this).
148
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
150
-     *
151
-     * @param EE_Line_Item|null $total_line_item grand total line item of type EEM_Line_Item::type_total
152
-     * @param EE_Ticket         $ticket
153
-     * @param int               $qty
154
-     * @return EE_Line_Item
155
-     * @throws EE_Error
156
-     * @throws ReflectionException
157
-     */
158
-    public static function add_ticket_purchase(
159
-        ?EE_Line_Item $total_line_item,
160
-        EE_Ticket $ticket,
161
-        int $qty = 1
162
-    ): ?EE_Line_Item {
163
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
-            throw new EE_Error(
165
-                sprintf(
166
-                    esc_html__(
167
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
-                        'event_espresso'
169
-                    ),
170
-                    $ticket->ID(),
171
-                    $total_line_item->ID()
172
-                )
173
-            );
174
-        }
175
-        // either increment the qty for an existing ticket
176
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
-        // or add a new one
178
-        if (! $line_item instanceof EE_Line_Item) {
179
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
-        }
181
-        $total_line_item->recalculate_total_including_taxes();
182
-        return $line_item;
183
-    }
184
-
185
-
186
-    /**
187
-     * Returns the new line item created by adding a purchase of the ticket
188
-     *
189
-     * @param EE_Line_Item $total_line_item
190
-     * @param EE_Ticket    $ticket
191
-     * @param int          $qty
192
-     * @return EE_Line_Item
193
-     * @throws EE_Error
194
-     * @throws InvalidArgumentException
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws ReflectionException
198
-     */
199
-    public static function increment_ticket_qty_if_already_in_cart(
200
-        EE_Line_Item $total_line_item,
201
-        EE_Ticket $ticket,
202
-        $qty = 1
203
-    ) {
204
-        $line_item = null;
205
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
-            foreach ($ticket_line_items as $ticket_line_item) {
208
-                if (
209
-                    $ticket_line_item instanceof EE_Line_Item
210
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
-                ) {
212
-                    $line_item = $ticket_line_item;
213
-                    break;
214
-                }
215
-            }
216
-        }
217
-        if ($line_item instanceof EE_Line_Item) {
218
-            EEH_Line_Item::increment_quantity($line_item, $qty);
219
-            return $line_item;
220
-        }
221
-        return null;
222
-    }
223
-
224
-
225
-    /**
226
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
-     * Does NOT save or recalculate other line items totals
228
-     *
229
-     * @param EE_Line_Item $line_item
230
-     * @param int          $qty
231
-     * @return void
232
-     * @throws EE_Error
233
-     * @throws InvalidArgumentException
234
-     * @throws InvalidDataTypeException
235
-     * @throws InvalidInterfaceException
236
-     * @throws ReflectionException
237
-     */
238
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
-    {
240
-        if (! $line_item->is_percent()) {
241
-            $qty += $line_item->quantity();
242
-            $line_item->set_quantity($qty);
243
-            $line_item->set_total($line_item->unit_price() * $qty);
244
-            $line_item->save();
245
-        }
246
-        foreach ($line_item->children() as $child) {
247
-            if ($child->is_sub_line_item()) {
248
-                EEH_Line_Item::update_quantity($child, $qty);
249
-            }
250
-        }
251
-    }
252
-
253
-
254
-    /**
255
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
-     * Does NOT save or recalculate other line items totals
257
-     *
258
-     * @param EE_Line_Item $line_item
259
-     * @param int          $qty
260
-     * @return void
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
-    {
269
-        if (! $line_item->is_percent()) {
270
-            $qty = $line_item->quantity() - $qty;
271
-            $qty = max($qty, 0);
272
-            $line_item->set_quantity($qty);
273
-            $line_item->set_total($line_item->unit_price() * $qty);
274
-            $line_item->save();
275
-        }
276
-        foreach ($line_item->children() as $child) {
277
-            if ($child->is_sub_line_item()) {
278
-                EEH_Line_Item::update_quantity($child, $qty);
279
-            }
280
-        }
281
-    }
282
-
283
-
284
-    /**
285
-     * Updates the line item and its children's quantities to the specified number.
286
-     * Does NOT save them or recalculate totals.
287
-     *
288
-     * @param EE_Line_Item $line_item
289
-     * @param int          $new_quantity
290
-     * @throws EE_Error
291
-     * @throws InvalidArgumentException
292
-     * @throws InvalidDataTypeException
293
-     * @throws InvalidInterfaceException
294
-     * @throws ReflectionException
295
-     */
296
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
-    {
298
-        if (! $line_item->is_percent()) {
299
-            $line_item->set_quantity($new_quantity);
300
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
301
-            $line_item->save();
302
-        }
303
-        foreach ($line_item->children() as $child) {
304
-            if ($child->is_sub_line_item()) {
305
-                EEH_Line_Item::update_quantity($child, $new_quantity);
306
-            }
307
-        }
308
-    }
309
-
310
-
311
-    /**
312
-     * Returns the new line item created by adding a purchase of the ticket
313
-     *
314
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
-     * @param EE_Ticket    $ticket
316
-     * @param int          $qty
317
-     * @return EE_Line_Item
318
-     * @throws EE_Error
319
-     * @throws InvalidArgumentException
320
-     * @throws InvalidDataTypeException
321
-     * @throws InvalidInterfaceException
322
-     * @throws ReflectionException
323
-     */
324
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
-    {
326
-        $datetimes = $ticket->datetimes();
327
-        $first_datetime = reset($datetimes);
328
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
329
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
-            $first_datetime_name = $first_datetime->event()->name();
331
-        }
332
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
-        // get event subtotal line
334
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
-        $taxes = $ticket->tax_price_modifiers();
336
-        // add $ticket to cart
337
-        $line_item = EE_Line_Item::new_instance(array(
338
-            'LIN_name'       => $ticket->name(),
339
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
-            'LIN_unit_price' => $ticket->price(),
341
-            'LIN_quantity'   => $qty,
342
-            'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
-            'LIN_order'      => count($events_sub_total->children()),
344
-            'LIN_total'      => $ticket->price() * $qty,
345
-            'LIN_type'       => EEM_Line_Item::type_line_item,
346
-            'OBJ_ID'         => $ticket->ID(),
347
-            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
-        ));
349
-        $line_item = apply_filters(
350
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
-            $line_item
352
-        );
353
-        if (!$line_item instanceof EE_Line_Item) {
354
-            throw new DomainException(
355
-                esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
-            );
357
-        }
358
-        $events_sub_total->add_child_line_item($line_item);
359
-        // now add the sub-line items
360
-        $running_total = 0;
361
-        $running_pre_tax_total = 0;
362
-        foreach ($ticket->prices() as $price) {
363
-            $sign = $price->is_discount() ? -1 : 1;
364
-            $price_total = $price->is_percent()
365
-                ? $running_pre_tax_total * $price->amount() / 100
366
-                : $price->amount() * $qty;
367
-            if ($price->is_percent()) {
368
-                $percent = $sign * $price->amount();
369
-                $unit_price = 0;
370
-            } else {
371
-                $percent    = 0;
372
-                $unit_price = $sign * $price->amount();
373
-            }
374
-            $sub_line_item = EE_Line_Item::new_instance(array(
375
-                'LIN_name'       => $price->name(),
376
-                'LIN_desc'       => $price->desc(),
377
-                'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
-                'LIN_is_taxable' => false,
379
-                'LIN_order'      => $price->order(),
380
-                'LIN_total'      => $price_total,
381
-                'LIN_pretax'     => 0,
382
-                'LIN_unit_price' => $unit_price,
383
-                'LIN_percent'    => $percent,
384
-                'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
-                'OBJ_ID'         => $price->ID(),
386
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
-            ));
388
-            $sub_line_item = apply_filters(
389
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
-                $sub_line_item
391
-            );
392
-            $running_total += $sign * $price_total;
393
-            $running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
-            $line_item->add_child_line_item($sub_line_item);
395
-        }
396
-        $line_item->setPretaxTotal($running_pre_tax_total);
397
-        return $line_item;
398
-    }
399
-
400
-
401
-    /**
402
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
-     * re-calculates the line item totals and updates the related transaction. But
404
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
-     * should probably change because of this).
406
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
408
-     *
409
-     * @param EE_Line_Item $total_line_item
410
-     * @param EE_Line_Item $item to be added
411
-     * @return boolean
412
-     * @throws EE_Error
413
-     * @throws InvalidArgumentException
414
-     * @throws InvalidDataTypeException
415
-     * @throws InvalidInterfaceException
416
-     * @throws ReflectionException
417
-     */
418
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
-    {
420
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
-            $success = $pre_tax_subtotal->add_child_line_item($item);
423
-        } else {
424
-            return false;
425
-        }
426
-        if ($recalculate_totals) {
427
-            $total_line_item->recalculate_total_including_taxes();
428
-        }
429
-        return $success;
430
-    }
431
-
432
-
433
-    /**
434
-     * cancels an existing ticket line item,
435
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
-     *
438
-     * @param EE_Line_Item $ticket_line_item
439
-     * @param int          $qty
440
-     * @return bool success
441
-     * @throws EE_Error
442
-     * @throws InvalidArgumentException
443
-     * @throws InvalidDataTypeException
444
-     * @throws InvalidInterfaceException
445
-     * @throws ReflectionException
446
-     */
447
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
-    {
449
-        // validate incoming line_item
450
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
-            throw new EE_Error(
452
-                sprintf(
453
-                    esc_html__(
454
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
-                        'event_espresso'
456
-                    ),
457
-                    $ticket_line_item->type()
458
-                )
459
-            );
460
-        }
461
-        if ($ticket_line_item->quantity() < $qty) {
462
-            throw new EE_Error(
463
-                sprintf(
464
-                    esc_html__(
465
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
-                        'event_espresso'
467
-                    ),
468
-                    $qty,
469
-                    $ticket_line_item->quantity()
470
-                )
471
-            );
472
-        }
473
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
-        foreach ($ticket_line_item->children() as $child_line_item) {
476
-            if (
477
-                $child_line_item->is_sub_line_item()
478
-                && ! $child_line_item->is_percent()
479
-                && ! $child_line_item->is_cancellation()
480
-            ) {
481
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
-            }
483
-        }
484
-        // get cancellation sub line item
485
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
-            $ticket_line_item,
487
-            EEM_Line_Item::type_cancellation
488
-        );
489
-        $cancellation_line_item = reset($cancellation_line_item);
490
-        // verify that this ticket was indeed previously cancelled
491
-        if ($cancellation_line_item instanceof EE_Line_Item) {
492
-            // increment cancelled quantity
493
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
-        } else {
495
-            // create cancellation sub line item
496
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
497
-                'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
-                'LIN_desc'       => sprintf(
499
-                    esc_html_x(
500
-                        'Cancelled %1$s : %2$s',
501
-                        'Cancelled Ticket Name : 2015-01-01 11:11',
502
-                        'event_espresso'
503
-                    ),
504
-                    $ticket_line_item->name(),
505
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
-                ),
507
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
-                'LIN_quantity'   => $qty,
509
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
-                'LIN_order'      => count($ticket_line_item->children()),
511
-                'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
-                'LIN_type'       => EEM_Line_Item::type_cancellation,
513
-            ));
514
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
515
-        }
516
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
517
-            // decrement parent line item quantity
518
-            $event_line_item = $ticket_line_item->parent();
519
-            if (
520
-                $event_line_item instanceof EE_Line_Item
521
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
-            ) {
523
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
-                $event_line_item->save();
525
-            }
526
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
-            return true;
528
-        }
529
-        return false;
530
-    }
531
-
532
-
533
-    /**
534
-     * reinstates (un-cancels?) a previously canceled ticket line item,
535
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
-     *
538
-     * @param EE_Line_Item $ticket_line_item
539
-     * @param int          $qty
540
-     * @return bool success
541
-     * @throws EE_Error
542
-     * @throws InvalidArgumentException
543
-     * @throws InvalidDataTypeException
544
-     * @throws InvalidInterfaceException
545
-     * @throws ReflectionException
546
-     */
547
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
-    {
549
-        // validate incoming line_item
550
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
-            throw new EE_Error(
552
-                sprintf(
553
-                    esc_html__(
554
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
-                        'event_espresso'
556
-                    ),
557
-                    $ticket_line_item->type()
558
-                )
559
-            );
560
-        }
561
-        // get cancellation sub line item
562
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
-            $ticket_line_item,
564
-            EEM_Line_Item::type_cancellation
565
-        );
566
-        $cancellation_line_item = reset($cancellation_line_item);
567
-        // verify that this ticket was indeed previously cancelled
568
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
569
-            return false;
570
-        }
571
-        if ($cancellation_line_item->quantity() > $qty) {
572
-            // decrement cancelled quantity
573
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
-        } elseif ($cancellation_line_item->quantity() === $qty) {
575
-            // decrement cancelled quantity in case anyone still has the object kicking around
576
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
-            // delete because quantity will end up as 0
578
-            $cancellation_line_item->delete();
579
-            // and attempt to destroy the object,
580
-            // even though PHP won't actually destroy it until it needs the memory
581
-            unset($cancellation_line_item);
582
-        } else {
583
-            // what ?!?! negative quantity ?!?!
584
-            throw new EE_Error(
585
-                sprintf(
586
-                    esc_html__(
587
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
-                        'event_espresso'
589
-                    ),
590
-                    $qty,
591
-                    $cancellation_line_item->quantity()
592
-                )
593
-            );
594
-        }
595
-        // increment ticket quantity
596
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
598
-            // increment parent line item quantity
599
-            $event_line_item = $ticket_line_item->parent();
600
-            if (
601
-                $event_line_item instanceof EE_Line_Item
602
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
-            ) {
604
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
-            }
606
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
-            return true;
608
-        }
609
-        return false;
610
-    }
611
-
612
-
613
-    /**
614
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
-     *
617
-     * @param EE_Line_Item $line_item
618
-     * @return float
619
-     * @throws EE_Error
620
-     * @throws InvalidArgumentException
621
-     * @throws InvalidDataTypeException
622
-     * @throws InvalidInterfaceException
623
-     * @throws ReflectionException
624
-     */
625
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
-    {
627
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
-        return $grand_total_line_item->recalculate_total_including_taxes();
629
-    }
630
-
631
-
632
-    /**
633
-     * Gets the line item which contains the subtotal of all the items
634
-     *
635
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
-     * @return EE_Line_Item
637
-     * @throws EE_Error
638
-     * @throws InvalidArgumentException
639
-     * @throws InvalidDataTypeException
640
-     * @throws InvalidInterfaceException
641
-     * @throws ReflectionException
642
-     */
643
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
-    {
645
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
-        return $pre_tax_subtotal instanceof EE_Line_Item
647
-            ? $pre_tax_subtotal
648
-            : self::create_pre_tax_subtotal($total_line_item);
649
-    }
650
-
651
-
652
-    /**
653
-     * Gets the line item for the taxes subtotal
654
-     *
655
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
-     * @return EE_Line_Item
657
-     * @throws EE_Error
658
-     * @throws InvalidArgumentException
659
-     * @throws InvalidDataTypeException
660
-     * @throws InvalidInterfaceException
661
-     * @throws ReflectionException
662
-     */
663
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
-    {
665
-        $taxes = $total_line_item->get_child_line_item('taxes');
666
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
-    }
668
-
669
-
670
-    /**
671
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
-     *
673
-     * @param EE_Line_Item   $line_item
674
-     * @param EE_Transaction $transaction
675
-     * @return void
676
-     * @throws EE_Error
677
-     * @throws InvalidArgumentException
678
-     * @throws InvalidDataTypeException
679
-     * @throws InvalidInterfaceException
680
-     * @throws ReflectionException
681
-     */
682
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
-    {
684
-        if ($transaction) {
685
-            /** @type EEM_Transaction $EEM_Transaction */
686
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
-            $line_item->set_TXN_ID($TXN_ID);
689
-        }
690
-    }
691
-
692
-
693
-    /**
694
-     * Creates a new default total line item for the transaction,
695
-     * and its tickets subtotal and taxes subtotal line items (and adds the
696
-     * existing taxes as children of the taxes subtotal line item)
697
-     *
698
-     * @param EE_Transaction $transaction
699
-     * @return EE_Line_Item of type total
700
-     * @throws EE_Error
701
-     * @throws InvalidArgumentException
702
-     * @throws InvalidDataTypeException
703
-     * @throws InvalidInterfaceException
704
-     * @throws ReflectionException
705
-     */
706
-    public static function create_total_line_item($transaction = null)
707
-    {
708
-        $total_line_item = EE_Line_Item::new_instance(array(
709
-            'LIN_code' => 'total',
710
-            'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
-            'LIN_type' => EEM_Line_Item::type_total,
712
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
-        ));
714
-        $total_line_item = apply_filters(
715
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
-            $total_line_item
717
-        );
718
-        self::set_TXN_ID($total_line_item, $transaction);
719
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
720
-        self::create_taxes_subtotal($total_line_item, $transaction);
721
-        return $total_line_item;
722
-    }
723
-
724
-
725
-    /**
726
-     * Creates a default items subtotal line item
727
-     *
728
-     * @param EE_Line_Item   $total_line_item
729
-     * @param EE_Transaction $transaction
730
-     * @return EE_Line_Item
731
-     * @throws EE_Error
732
-     * @throws InvalidArgumentException
733
-     * @throws InvalidDataTypeException
734
-     * @throws InvalidInterfaceException
735
-     * @throws ReflectionException
736
-     */
737
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
-    {
739
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
740
-            'LIN_code' => 'pre-tax-subtotal',
741
-            'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
-            'LIN_type' => EEM_Line_Item::type_sub_total,
743
-        ));
744
-        $pre_tax_line_item = apply_filters(
745
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
-            $pre_tax_line_item
747
-        );
748
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
749
-        $total_line_item->add_child_line_item($pre_tax_line_item);
750
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
751
-        return $pre_tax_line_item;
752
-    }
753
-
754
-
755
-    /**
756
-     * Creates a line item for the taxes subtotal and finds all the tax prices
757
-     * and applies taxes to it
758
-     *
759
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
-     * @param EE_Transaction $transaction
761
-     * @return EE_Line_Item
762
-     * @throws EE_Error
763
-     * @throws InvalidArgumentException
764
-     * @throws InvalidDataTypeException
765
-     * @throws InvalidInterfaceException
766
-     * @throws ReflectionException
767
-     */
768
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
-    {
770
-        $tax_line_item = EE_Line_Item::new_instance(array(
771
-            'LIN_code'  => 'taxes',
772
-            'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
-            'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
-            'LIN_order' => 1000,// this should always come last
775
-        ));
776
-        $tax_line_item = apply_filters(
777
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
-            $tax_line_item
779
-        );
780
-        self::set_TXN_ID($tax_line_item, $transaction);
781
-        $total_line_item->add_child_line_item($tax_line_item);
782
-        // and lastly, add the actual taxes
783
-        self::apply_taxes($total_line_item);
784
-        return $tax_line_item;
785
-    }
786
-
787
-
788
-    /**
789
-     * Creates a default items subtotal line item
790
-     *
791
-     * @param EE_Line_Item   $pre_tax_line_item
792
-     * @param EE_Transaction $transaction
793
-     * @param EE_Event       $event
794
-     * @return EE_Line_Item
795
-     * @throws EE_Error
796
-     * @throws InvalidArgumentException
797
-     * @throws InvalidDataTypeException
798
-     * @throws InvalidInterfaceException
799
-     * @throws ReflectionException
800
-     */
801
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
-    {
803
-        $event_line_item = EE_Line_Item::new_instance(array(
804
-            'LIN_code' => self::get_event_code($event),
805
-            'LIN_name' => self::get_event_name($event),
806
-            'LIN_desc' => self::get_event_desc($event),
807
-            'LIN_type' => EEM_Line_Item::type_sub_total,
808
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
-            'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
-        ));
811
-        $event_line_item = apply_filters(
812
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
-            $event_line_item
814
-        );
815
-        self::set_TXN_ID($event_line_item, $transaction);
816
-        $pre_tax_line_item->add_child_line_item($event_line_item);
817
-        return $event_line_item;
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets what the event ticket's code SHOULD be
823
-     *
824
-     * @param EE_Event $event
825
-     * @return string
826
-     * @throws EE_Error
827
-     */
828
-    public static function get_event_code($event)
829
-    {
830
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets the event name
836
-     *
837
-     * @param EE_Event $event
838
-     * @return string
839
-     * @throws EE_Error
840
-     */
841
-    public static function get_event_name($event)
842
-    {
843
-        return $event instanceof EE_Event
844
-            ? mb_substr($event->name(), 0, 245)
845
-            : esc_html__('Event', 'event_espresso');
846
-    }
847
-
848
-
849
-    /**
850
-     * Gets the event excerpt
851
-     *
852
-     * @param EE_Event $event
853
-     * @return string
854
-     * @throws EE_Error
855
-     */
856
-    public static function get_event_desc($event)
857
-    {
858
-        return $event instanceof EE_Event ? $event->short_description() : '';
859
-    }
860
-
861
-
862
-    /**
863
-     * Given the grand total line item and a ticket, finds the event sub-total
864
-     * line item the ticket's purchase should be added onto
865
-     *
866
-     * @access public
867
-     * @param EE_Line_Item $grand_total the grand total line item
868
-     * @param EE_Ticket    $ticket
869
-     * @return EE_Line_Item
870
-     * @throws EE_Error
871
-     * @throws InvalidArgumentException
872
-     * @throws InvalidDataTypeException
873
-     * @throws InvalidInterfaceException
874
-     * @throws ReflectionException
875
-     */
876
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
-    {
878
-        $first_datetime = $ticket->first_datetime();
879
-        if (! $first_datetime instanceof EE_Datetime) {
880
-            throw new EE_Error(
881
-                sprintf(
882
-                    esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
-                    $ticket->ID()
884
-                )
885
-            );
886
-        }
887
-        $event = $first_datetime->event();
888
-        if (! $event instanceof EE_Event) {
889
-            throw new EE_Error(
890
-                sprintf(
891
-                    esc_html__(
892
-                        'The supplied ticket (ID %d) has no event data associated with it.',
893
-                        'event_espresso'
894
-                    ),
895
-                    $ticket->ID()
896
-                )
897
-            );
898
-        }
899
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
-        if (! $events_sub_total instanceof EE_Line_Item) {
901
-            throw new EE_Error(
902
-                sprintf(
903
-                    esc_html__(
904
-                        'There is no events sub-total for ticket %s on total line item %d',
905
-                        'event_espresso'
906
-                    ),
907
-                    $ticket->ID(),
908
-                    $grand_total->ID()
909
-                )
910
-            );
911
-        }
912
-        return $events_sub_total;
913
-    }
914
-
915
-
916
-    /**
917
-     * Gets the event line item
918
-     *
919
-     * @param EE_Line_Item $grand_total
920
-     * @param EE_Event     $event
921
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
-     * @throws EE_Error
923
-     * @throws InvalidArgumentException
924
-     * @throws InvalidDataTypeException
925
-     * @throws InvalidInterfaceException
926
-     * @throws ReflectionException
927
-     */
928
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
-    {
930
-        /** @type EE_Event $event */
931
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
932
-        $event_line_item = null;
933
-        $found = false;
934
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
-            // default event subtotal, we should only ever find this the first time this method is called
936
-            if (! $event_line_item->OBJ_ID()) {
937
-                // let's use this! but first... set the event details
938
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
-                $found = true;
940
-                break;
941
-            }
942
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
943
-                // found existing line item for this event in the cart, so break out of loop and use this one
944
-                $found = true;
945
-                break;
946
-            }
947
-        }
948
-        if (! $found) {
949
-            // there is no event sub-total yet, so add it
950
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
-            // create a new "event" subtotal below that
952
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
-            // and set the event details
954
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
-        }
956
-        return $event_line_item;
957
-    }
958
-
959
-
960
-    /**
961
-     * Creates a default items subtotal line item
962
-     *
963
-     * @param EE_Line_Item   $event_line_item
964
-     * @param EE_Event       $event
965
-     * @param EE_Transaction $transaction
966
-     * @return void
967
-     * @throws EE_Error
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidDataTypeException
970
-     * @throws InvalidInterfaceException
971
-     * @throws ReflectionException
972
-     */
973
-    public static function set_event_subtotal_details(
974
-        EE_Line_Item $event_line_item,
975
-        EE_Event $event,
976
-        $transaction = null
977
-    ) {
978
-        if ($event instanceof EE_Event) {
979
-            $event_line_item->set_code(self::get_event_code($event));
980
-            $event_line_item->set_name(self::get_event_name($event));
981
-            $event_line_item->set_desc(self::get_event_desc($event));
982
-            $event_line_item->set_OBJ_ID($event->ID());
983
-        }
984
-        self::set_TXN_ID($event_line_item, $transaction);
985
-    }
986
-
987
-
988
-    /**
989
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
-     * any old taxes are removed
992
-     *
993
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
-     * @param bool         $update_txn_status
995
-     * @return bool
996
-     * @throws EE_Error
997
-     * @throws InvalidArgumentException
998
-     * @throws InvalidDataTypeException
999
-     * @throws InvalidInterfaceException
1000
-     * @throws ReflectionException
1001
-     * @throws RuntimeException
1002
-     */
1003
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
-    {
1005
-        $total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
-        $existing_global_taxes = $taxes_line_item->tax_descendants();
1008
-        $updates = false;
1009
-        // loop thru taxes
1010
-        $global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
-        foreach ($global_taxes as $order => $taxes) {
1012
-            foreach ($taxes as $tax) {
1013
-                if ($tax instanceof EE_Price) {
1014
-                    $found = false;
1015
-                    // check if this is already an existing tax
1016
-                    foreach ($existing_global_taxes as $existing_global_tax) {
1017
-                        if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
-                            // maybe update the tax rate in case it has changed
1019
-                            if ($existing_global_tax->percent() !== $tax->amount()) {
1020
-                                $existing_global_tax->set_percent($tax->amount());
1021
-                                $existing_global_tax->save();
1022
-                                $updates = true;
1023
-                            }
1024
-                            $found = true;
1025
-                            break;
1026
-                        }
1027
-                    }
1028
-                    if (! $found) {
1029
-                        // add a new line item for this global tax
1030
-                        $tax_line_item = apply_filters(
1031
-                            'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
-                            EE_Line_Item::new_instance(
1033
-                                [
1034
-                                    'LIN_name'       => $tax->name(),
1035
-                                    'LIN_desc'       => $tax->desc(),
1036
-                                    'LIN_percent'    => $tax->amount(),
1037
-                                    'LIN_is_taxable' => false,
1038
-                                    'LIN_order'      => $order,
1039
-                                    'LIN_total'      => 0,
1040
-                                    'LIN_type'       => EEM_Line_Item::type_tax,
1041
-                                    'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
-                                    'OBJ_ID'         => $tax->ID(),
1043
-                                ]
1044
-                            )
1045
-                        );
1046
-                        $updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
-                    }
1048
-                }
1049
-            }
1050
-        }
1051
-        // only recalculate totals if something changed
1052
-        if ($updates) {
1053
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
-            return true;
1055
-        }
1056
-        return false;
1057
-    }
1058
-
1059
-
1060
-    /**
1061
-     * Ensures that taxes have been applied to the order, if not applies them.
1062
-     * Returns the total amount of tax
1063
-     *
1064
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
-     * @return float
1066
-     * @throws EE_Error
1067
-     * @throws InvalidArgumentException
1068
-     * @throws InvalidDataTypeException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws ReflectionException
1071
-     */
1072
-    public static function ensure_taxes_applied($total_line_item)
1073
-    {
1074
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
-        if (! $taxes_subtotal->children()) {
1076
-            self::apply_taxes($total_line_item);
1077
-        }
1078
-        return $taxes_subtotal->total();
1079
-    }
1080
-
1081
-
1082
-    /**
1083
-     * Deletes ALL children of the passed line item
1084
-     *
1085
-     * @param EE_Line_Item $parent_line_item
1086
-     * @return bool
1087
-     * @throws EE_Error
1088
-     * @throws InvalidArgumentException
1089
-     * @throws InvalidDataTypeException
1090
-     * @throws InvalidInterfaceException
1091
-     * @throws ReflectionException
1092
-     */
1093
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
-    {
1095
-        $deleted = 0;
1096
-        foreach ($parent_line_item->children() as $child_line_item) {
1097
-            if ($child_line_item instanceof EE_Line_Item) {
1098
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
-                if ($child_line_item->ID()) {
1100
-                    $child_line_item->delete();
1101
-                    unset($child_line_item);
1102
-                } else {
1103
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1104
-                }
1105
-                $deleted++;
1106
-            }
1107
-        }
1108
-        return $deleted;
1109
-    }
1110
-
1111
-
1112
-    /**
1113
-     * Deletes the line items as indicated by the line item code(s) provided,
1114
-     * regardless of where they're found in the line item tree. Automatically
1115
-     * re-calculates the line item totals and updates the related transaction. But
1116
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
-     * should probably change because of this).
1118
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
-     *
1121
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
-     * @param array|bool|string $line_item_codes
1123
-     * @return int number of items successfully removed
1124
-     * @throws EE_Error
1125
-     */
1126
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
-    {
1128
-
1129
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
-            EE_Error::doing_it_wrong(
1131
-                'EEH_Line_Item::delete_items',
1132
-                esc_html__(
1133
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
-                    'event_espresso'
1135
-                ),
1136
-                '4.6.18'
1137
-            );
1138
-        }
1139
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
-
1141
-        // check if only a single line_item_id was passed
1142
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
-            // place single line_item_id in an array to appear as multiple line_item_ids
1144
-            $line_item_codes = array($line_item_codes);
1145
-        }
1146
-        $removals = 0;
1147
-        // cycle thru line_item_ids
1148
-        foreach ($line_item_codes as $line_item_id) {
1149
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1150
-        }
1151
-
1152
-        if ($removals > 0) {
1153
-            $total_line_item->recalculate_taxes_and_tax_total();
1154
-            return $removals;
1155
-        } else {
1156
-            return false;
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
-     * tax and updates the total line item accordingly
1164
-     *
1165
-     * @param EE_Line_Item $total_line_item
1166
-     * @param float        $amount
1167
-     * @param string       $name
1168
-     * @param string       $description
1169
-     * @param string       $code
1170
-     * @param boolean      $add_to_existing_line_item
1171
-     *                          if true, and a duplicate line item with the same code is found,
1172
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
-     * @return EE_Line_Item the new tax line item created
1174
-     * @throws EE_Error
1175
-     * @throws InvalidArgumentException
1176
-     * @throws InvalidDataTypeException
1177
-     * @throws InvalidInterfaceException
1178
-     * @throws ReflectionException
1179
-     */
1180
-    public static function set_total_tax_to(
1181
-        EE_Line_Item $total_line_item,
1182
-        $amount,
1183
-        $name = null,
1184
-        $description = null,
1185
-        $code = null,
1186
-        $add_to_existing_line_item = false
1187
-    ) {
1188
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
-        $taxable_total = $total_line_item->taxable_total();
1190
-
1191
-        if ($add_to_existing_line_item) {
1192
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1193
-            EEM_Line_Item::instance()->delete(
1194
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
-            );
1196
-        } else {
1197
-            $new_tax = null;
1198
-            $tax_subtotal->delete_children_line_items();
1199
-        }
1200
-        if ($new_tax) {
1201
-            $new_tax->set_total($new_tax->total() + $amount);
1202
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
-        } else {
1204
-            // no existing tax item. Create it
1205
-            $new_tax = EE_Line_Item::new_instance(array(
1206
-                'TXN_ID'      => $total_line_item->TXN_ID(),
1207
-                'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
-                'LIN_desc'    => $description ?: '',
1209
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
-                'LIN_total'   => $amount,
1211
-                'LIN_parent'  => $tax_subtotal->ID(),
1212
-                'LIN_type'    => EEM_Line_Item::type_tax,
1213
-                'LIN_code'    => $code,
1214
-            ));
1215
-        }
1216
-
1217
-        $new_tax = apply_filters(
1218
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
-            $new_tax,
1220
-            $total_line_item
1221
-        );
1222
-        $new_tax->save();
1223
-        $tax_subtotal->set_total($new_tax->total());
1224
-        $tax_subtotal->save();
1225
-        $total_line_item->recalculate_total_including_taxes();
1226
-        return $new_tax;
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Makes all the line items which are children of $line_item taxable (or not).
1232
-     * Does NOT save the line items
1233
-     *
1234
-     * @param EE_Line_Item $line_item
1235
-     * @param boolean      $taxable
1236
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
-     * @throws EE_Error
1239
-     */
1240
-    public static function set_line_items_taxable(
1241
-        EE_Line_Item $line_item,
1242
-        $taxable = true,
1243
-        $code_substring_for_whitelist = null
1244
-    ) {
1245
-        $whitelisted = false;
1246
-        if ($code_substring_for_whitelist !== null) {
1247
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
-        }
1249
-        if (! $whitelisted && $line_item->is_line_item()) {
1250
-            $line_item->set_is_taxable($taxable);
1251
-        }
1252
-        foreach ($line_item->children() as $child_line_item) {
1253
-            EEH_Line_Item::set_line_items_taxable(
1254
-                $child_line_item,
1255
-                $taxable,
1256
-                $code_substring_for_whitelist
1257
-            );
1258
-        }
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Gets all descendants that are event subtotals
1264
-     *
1265
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
-     * @return EE_Line_Item[]
1268
-     * @throws EE_Error
1269
-     */
1270
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
-    {
1272
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
-    }
1274
-
1275
-
1276
-    /**
1277
-     * Gets all descendants subtotals that match the supplied object type
1278
-     *
1279
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
-     * @param string       $obj_type
1282
-     * @return EE_Line_Item[]
1283
-     * @throws EE_Error
1284
-     */
1285
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
-    {
1287
-        return self::_get_descendants_by_type_and_object_type(
1288
-            $parent_line_item,
1289
-            EEM_Line_Item::type_sub_total,
1290
-            $obj_type
1291
-        );
1292
-    }
1293
-
1294
-
1295
-    /**
1296
-     * Gets all descendants that are tickets
1297
-     *
1298
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
-     * @return EE_Line_Item[]
1301
-     * @throws EE_Error
1302
-     */
1303
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
-    {
1305
-        return self::get_line_items_of_object_type(
1306
-            $parent_line_item,
1307
-            EEM_Line_Item::OBJ_TYPE_TICKET
1308
-        );
1309
-    }
1310
-
1311
-
1312
-    /**
1313
-     * Gets all descendants subtotals that match the supplied object type
1314
-     *
1315
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
-     * @param string       $obj_type
1318
-     * @return EE_Line_Item[]
1319
-     * @throws EE_Error
1320
-     */
1321
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
-    {
1323
-        return self::_get_descendants_by_type_and_object_type(
1324
-            $parent_line_item,
1325
-            EEM_Line_Item::type_line_item,
1326
-            $obj_type
1327
-        );
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
-     *
1334
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1335
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
-     * @return EE_Line_Item[]
1337
-     * @throws EE_Error
1338
-     */
1339
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
-    {
1341
-        return EEH_Line_Item::get_descendants_of_type(
1342
-            $parent_line_item,
1343
-            EEM_Line_Item::type_tax
1344
-        );
1345
-    }
1346
-
1347
-
1348
-    /**
1349
-     * Gets all the real items purchased which are children of this item
1350
-     *
1351
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1352
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
-     * @return EE_Line_Item[]
1354
-     * @throws EE_Error
1355
-     */
1356
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
-    {
1358
-        return EEH_Line_Item::get_descendants_of_type(
1359
-            $parent_line_item,
1360
-            EEM_Line_Item::type_line_item
1361
-        );
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * Gets all descendants of supplied line item that match the supplied line item type
1367
-     *
1368
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
-     * @return EE_Line_Item[]
1372
-     * @throws EE_Error
1373
-     */
1374
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
-    {
1376
-        return self::_get_descendants_by_type_and_object_type(
1377
-            $parent_line_item,
1378
-            $line_item_type,
1379
-            null
1380
-        );
1381
-    }
1382
-
1383
-
1384
-    /**
1385
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
-     * as well
1387
-     *
1388
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
-     * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
-     *                                        searching
1392
-     * @return EE_Line_Item[]
1393
-     * @throws EE_Error
1394
-     */
1395
-    protected static function _get_descendants_by_type_and_object_type(
1396
-        EE_Line_Item $parent_line_item,
1397
-        $line_item_type,
1398
-        $obj_type = null
1399
-    ) {
1400
-        $objects = array();
1401
-        foreach ($parent_line_item->children() as $child_line_item) {
1402
-            if ($child_line_item instanceof EE_Line_Item) {
1403
-                if (
1404
-                    $child_line_item->type() === $line_item_type
1405
-                    && (
1406
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
-                    )
1408
-                ) {
1409
-                    $objects[] = $child_line_item;
1410
-                } else {
1411
-                    // go-through-all-its children looking for more matches
1412
-                    $objects = array_merge(
1413
-                        $objects,
1414
-                        self::_get_descendants_by_type_and_object_type(
1415
-                            $child_line_item,
1416
-                            $line_item_type,
1417
-                            $obj_type
1418
-                        )
1419
-                    );
1420
-                }
1421
-            }
1422
-        }
1423
-        return $objects;
1424
-    }
1425
-
1426
-
1427
-    /**
1428
-     * Gets all descendants subtotals that match the supplied object type
1429
-     *
1430
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1431
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
-     * @param string       $OBJ_type         object type (like Event)
1433
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1434
-     * @return EE_Line_Item[]
1435
-     * @throws EE_Error
1436
-     */
1437
-    public static function get_line_items_by_object_type_and_IDs(
1438
-        EE_Line_Item $parent_line_item,
1439
-        $OBJ_type = '',
1440
-        $OBJ_IDs = array()
1441
-    ) {
1442
-        return self::_get_descendants_by_object_type_and_object_ID(
1443
-            $parent_line_item,
1444
-            $OBJ_type,
1445
-            $OBJ_IDs
1446
-        );
1447
-    }
1448
-
1449
-
1450
-    /**
1451
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
-     * as well
1453
-     *
1454
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
-     * @param string       $OBJ_type         object type (like Event)
1456
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1457
-     * @return EE_Line_Item[]
1458
-     * @throws EE_Error
1459
-     */
1460
-    protected static function _get_descendants_by_object_type_and_object_ID(
1461
-        EE_Line_Item $parent_line_item,
1462
-        $OBJ_type,
1463
-        $OBJ_IDs
1464
-    ) {
1465
-        $objects = array();
1466
-        foreach ($parent_line_item->children() as $child_line_item) {
1467
-            if ($child_line_item instanceof EE_Line_Item) {
1468
-                if (
1469
-                    $child_line_item->OBJ_type() === $OBJ_type
1470
-                    && is_array($OBJ_IDs)
1471
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
-                ) {
1473
-                    $objects[] = $child_line_item;
1474
-                } else {
1475
-                    // go-through-all-its children looking for more matches
1476
-                    $objects = array_merge(
1477
-                        $objects,
1478
-                        self::_get_descendants_by_object_type_and_object_ID(
1479
-                            $child_line_item,
1480
-                            $OBJ_type,
1481
-                            $OBJ_IDs
1482
-                        )
1483
-                    );
1484
-                }
1485
-            }
1486
-        }
1487
-        return $objects;
1488
-    }
1489
-
1490
-
1491
-    /**
1492
-     * Uses a breadth-first-search in order to find the nearest descendant of
1493
-     * the specified type and returns it, else NULL
1494
-     *
1495
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1498
-     * @return EE_Line_Item
1499
-     * @throws EE_Error
1500
-     * @throws InvalidArgumentException
1501
-     * @throws InvalidDataTypeException
1502
-     * @throws InvalidInterfaceException
1503
-     * @throws ReflectionException
1504
-     */
1505
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
-    {
1507
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Uses a breadth-first-search in order to find the nearest descendant
1513
-     * having the specified LIN_code and returns it, else NULL
1514
-     *
1515
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
-     * @param string       $code             any value used for LIN_code
1518
-     * @return EE_Line_Item
1519
-     * @throws EE_Error
1520
-     * @throws InvalidArgumentException
1521
-     * @throws InvalidDataTypeException
1522
-     * @throws InvalidInterfaceException
1523
-     * @throws ReflectionException
1524
-     */
1525
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
-    {
1527
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
-    }
1529
-
1530
-
1531
-    /**
1532
-     * Uses a breadth-first-search in order to find the nearest descendant
1533
-     * having the specified LIN_code and returns it, else NULL
1534
-     *
1535
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
-     * @param string       $search_field     name of EE_Line_Item property
1537
-     * @param string       $value            any value stored in $search_field
1538
-     * @return EE_Line_Item
1539
-     * @throws EE_Error
1540
-     * @throws InvalidArgumentException
1541
-     * @throws InvalidDataTypeException
1542
-     * @throws InvalidInterfaceException
1543
-     * @throws ReflectionException
1544
-     */
1545
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
-    {
1547
-        foreach ($parent_line_item->children() as $child) {
1548
-            if ($child->get($search_field) == $value) {
1549
-                return $child;
1550
-            }
1551
-        }
1552
-        foreach ($parent_line_item->children() as $child) {
1553
-            $descendant_found = self::_get_nearest_descendant(
1554
-                $child,
1555
-                $search_field,
1556
-                $value
1557
-            );
1558
-            if ($descendant_found) {
1559
-                return $descendant_found;
1560
-            }
1561
-        }
1562
-        return null;
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
-     * else recursively walks up the line item tree until a parent of type total is found,
1569
-     *
1570
-     * @param EE_Line_Item $line_item
1571
-     * @return EE_Line_Item
1572
-     * @throws EE_Error
1573
-     * @throws ReflectionException
1574
-     */
1575
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
-    {
1577
-        if ($line_item->is_total()) {
1578
-            return $line_item;
1579
-        }
1580
-        if ($line_item->TXN_ID()) {
1581
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1582
-            if ($total_line_item instanceof EE_Line_Item) {
1583
-                return $total_line_item;
1584
-            }
1585
-        } else {
1586
-            $line_item_parent = $line_item->parent();
1587
-            if ($line_item_parent instanceof EE_Line_Item) {
1588
-                if ($line_item_parent->is_total()) {
1589
-                    return $line_item_parent;
1590
-                }
1591
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
-            }
1593
-        }
1594
-        throw new EE_Error(
1595
-            sprintf(
1596
-                esc_html__(
1597
-                    'A valid grand total for line item %1$d was not found.',
1598
-                    'event_espresso'
1599
-                ),
1600
-                $line_item->ID()
1601
-            )
1602
-        );
1603
-    }
1604
-
1605
-
1606
-    /**
1607
-     * Prints out a representation of the line item tree
1608
-     *
1609
-     * @param EE_Line_Item $line_item
1610
-     * @param int          $indentation
1611
-     * @return void
1612
-     * @throws EE_Error
1613
-     */
1614
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
-    {
1616
-        $new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
-        echo $new_line;
1618
-        if (! $indentation) {
1619
-            echo $new_line;
1620
-        }
1621
-        echo str_repeat('. ', $indentation);
1622
-        $breakdown = '';
1623
-        if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
-            if ($line_item->is_percent()) {
1625
-                $breakdown = "{$line_item->percent()}%";
1626
-            } else {
1627
-                $breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
-            }
1629
-        }
1630
-        echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1631
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
-        echo "\${$line_item->total()}";
1633
-        if ($breakdown) {
1634
-            echo " ( {$breakdown} )";
1635
-        }
1636
-        if ($line_item->is_taxable()) {
1637
-            echo '  * taxable';
1638
-        }
1639
-        if ($line_item->children()) {
1640
-            foreach ($line_item->children() as $child) {
1641
-                self::visualize($child, $indentation + 1);
1642
-            }
1643
-        }
1644
-        if (! $indentation) {
1645
-            echo $new_line . $new_line;
1646
-        }
1647
-    }
1648
-
1649
-
1650
-    /**
1651
-     * Calculates the registration's final price, taking into account that they
1652
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
-     * and receive a portion of any transaction-wide discounts.
1654
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
-     * and brent's final price should be $5.50.
1658
-     * In order to do this, we basically need to traverse the line item tree calculating
1659
-     * the running totals (just as if we were recalculating the total), but when we identify
1660
-     * regular line items, we need to keep track of their share of the grand total.
1661
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
-     * when there are non-taxable items; otherwise they would be the same)
1664
-     *
1665
-     * @param EE_Line_Item $line_item
1666
-     * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
-     *                                                  can be included in price calculations at this moment
1668
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
-     *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
-     *                                                  the ticket IDs.
1671
-     *                                                  Eg array(
1672
-     *                                                      12 => 4.3
1673
-     *                                                      23 => 8.0
1674
-     *                                                      'total' => 16.6,
1675
-     *                                                      'taxable' => array(
1676
-     *                                                          12 => 10,
1677
-     *                                                          23 => 4
1678
-     *                                                      ).
1679
-     *                                                  So to find which registrations have which final price, we need
1680
-     *                                                  to find which line item is theirs, which can be done with
1681
-     *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
-     *                                                  $registration );`
1683
-     * @throws EE_Error
1684
-     * @throws InvalidArgumentException
1685
-     * @throws InvalidDataTypeException
1686
-     * @throws InvalidInterfaceException
1687
-     * @throws ReflectionException
1688
-     */
1689
-    public static function calculate_reg_final_prices_per_line_item(
1690
-        EE_Line_Item $line_item,
1691
-        $billable_ticket_quantities = array()
1692
-    ) {
1693
-        $running_totals = [
1694
-            'total'   => 0,
1695
-            'taxable' => ['total' => 0]
1696
-        ];
1697
-        foreach ($line_item->children() as $child_line_item) {
1698
-            switch ($child_line_item->type()) {
1699
-                case EEM_Line_Item::type_sub_total:
1700
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
-                        $child_line_item,
1702
-                        $billable_ticket_quantities
1703
-                    );
1704
-                    // combine arrays but preserve numeric keys
1705
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1707
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
-                    break;
1709
-
1710
-                case EEM_Line_Item::type_tax_sub_total:
1711
-                    // find how much the taxes percentage is
1712
-                    if ($child_line_item->percent() !== 0) {
1713
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1714
-                    } else {
1715
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
-                    }
1717
-                    // and apply to all the taxable totals, and add to the pretax totals
1718
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1719
-                        // "total" and "taxable" array key is an exception
1720
-                        if ($line_item_id === 'taxable') {
1721
-                            continue;
1722
-                        }
1723
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
-                    }
1726
-                    break;
1727
-
1728
-                case EEM_Line_Item::type_line_item:
1729
-                    // ticket line items or ????
1730
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
-                        // kk it's a ticket
1732
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1733
-                            // huh? that shouldn't happen.
1734
-                            $running_totals['total'] += $child_line_item->total();
1735
-                        } else {
1736
-                            // its not in our running totals yet. great.
1737
-                            if ($child_line_item->is_taxable()) {
1738
-                                $taxable_amount = $child_line_item->unit_price();
1739
-                            } else {
1740
-                                $taxable_amount = 0;
1741
-                            }
1742
-                            // are we only calculating totals for some tickets?
1743
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1746
-                                    ? $child_line_item->unit_price()
1747
-                                    : 0;
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
-                                    ? $taxable_amount
1750
-                                    : 0;
1751
-                            } else {
1752
-                                $quantity = $child_line_item->quantity();
1753
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
-                            }
1756
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
-                        }
1759
-                    } else {
1760
-                        // it's some other type of item added to the cart
1761
-                        // it should affect the running totals
1762
-                        // basically we want to convert it into a PERCENT modifier. Because
1763
-                        // more clearly affect all registration's final price equally
1764
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1765
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1766
-                            : 1;
1767
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1768
-                            // the "taxable" array key is an exception
1769
-                            if ($line_item_id === 'taxable') {
1770
-                                continue;
1771
-                            }
1772
-                            // update the running totals
1773
-                            // yes this actually even works for the running grand total!
1774
-                            $running_totals[ $line_item_id ] =
1775
-                                $line_items_percent_of_running_total * $this_running_total;
1776
-
1777
-                            if ($child_line_item->is_taxable()) {
1778
-                                $running_totals['taxable'][ $line_item_id ] =
1779
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
-                            }
1781
-                        }
1782
-                    }
1783
-                    break;
1784
-            }
1785
-        }
1786
-        return $running_totals;
1787
-    }
1788
-
1789
-
1790
-    /**
1791
-     * @param EE_Line_Item $total_line_item
1792
-     * @param EE_Line_Item $ticket_line_item
1793
-     * @return float | null
1794
-     * @throws EE_Error
1795
-     * @throws InvalidArgumentException
1796
-     * @throws InvalidDataTypeException
1797
-     * @throws InvalidInterfaceException
1798
-     * @throws OutOfRangeException
1799
-     * @throws ReflectionException
1800
-     */
1801
-    public static function calculate_final_price_for_ticket_line_item(
1802
-        EE_Line_Item $total_line_item,
1803
-        EE_Line_Item $ticket_line_item
1804
-    ) {
1805
-        static $final_prices_per_ticket_line_item = array();
1806
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
-                $total_line_item
1809
-            );
1810
-        }
1811
-        // ok now find this new registration's final price
1812
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
-        }
1815
-        $message = sprintf(
1816
-            esc_html__(
1817
-                'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
-                'event_espresso'
1819
-            ),
1820
-            $ticket_line_item->ID(),
1821
-            $total_line_item->ID()
1822
-        );
1823
-        if (WP_DEBUG) {
1824
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
-            throw new OutOfRangeException($message);
1826
-        }
1827
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
-        return null;
1829
-    }
1830
-
1831
-
1832
-    /**
1833
-     * Creates a duplicate of the line item tree, except only includes billable items
1834
-     * and the portion of line items attributed to billable things
1835
-     *
1836
-     * @param EE_Line_Item      $line_item
1837
-     * @param EE_Registration[] $registrations
1838
-     * @return EE_Line_Item
1839
-     * @throws EE_Error
1840
-     * @throws InvalidArgumentException
1841
-     * @throws InvalidDataTypeException
1842
-     * @throws InvalidInterfaceException
1843
-     * @throws ReflectionException
1844
-     */
1845
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
-    {
1847
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
-        foreach ($line_item->children() as $child_li) {
1849
-            $copy_li->add_child_line_item(
1850
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
-            );
1852
-        }
1853
-        // if this is the grand total line item, make sure the totals all add up
1854
-        // (we could have duplicated this logic AS we copied the line items, but
1855
-        // it seems DRYer this way)
1856
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
-            $copy_li->recalculate_total_including_taxes();
1858
-        }
1859
-        return $copy_li;
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * Creates a new, unsaved line item from $line_item that factors in the
1865
-     * number of billable registrations on $registrations.
1866
-     *
1867
-     * @param EE_Line_Item      $line_item
1868
-     * @param EE_Registration[] $registrations
1869
-     * @return EE_Line_Item
1870
-     * @throws EE_Error
1871
-     * @throws InvalidArgumentException
1872
-     * @throws InvalidDataTypeException
1873
-     * @throws InvalidInterfaceException
1874
-     * @throws ReflectionException
1875
-     */
1876
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
-    {
1878
-        $new_li_fields = $line_item->model_field_array();
1879
-        if (
1880
-            $line_item->type() === EEM_Line_Item::type_line_item &&
1881
-            $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
-        ) {
1883
-            $count = 0;
1884
-            foreach ($registrations as $registration) {
1885
-                if (
1886
-                    $line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
-                    in_array(
1888
-                        $registration->status_ID(),
1889
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1890
-                        true
1891
-                    )
1892
-                ) {
1893
-                    $count++;
1894
-                }
1895
-            }
1896
-            $new_li_fields['LIN_quantity'] = $count;
1897
-        }
1898
-        // don't set the total. We'll leave that up to the code that calculates it
1899
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
-        return EE_Line_Item::new_instance($new_li_fields);
1901
-    }
1902
-
1903
-
1904
-    /**
1905
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1906
-     * are removed, and line items with a quantity of 0
1907
-     *
1908
-     * @param EE_Line_Item $line_item |null
1909
-     * @return EE_Line_Item|null
1910
-     * @throws EE_Error
1911
-     * @throws InvalidArgumentException
1912
-     * @throws InvalidDataTypeException
1913
-     * @throws InvalidInterfaceException
1914
-     * @throws ReflectionException
1915
-     */
1916
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1917
-    {
1918
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
-        if ($copied_li === null) {
1920
-            return null;
1921
-        }
1922
-        // if this is an event subtotal, we want to only include it if it
1923
-        // has a non-zero total and at least one ticket line item child
1924
-        $ticket_children = 0;
1925
-        foreach ($line_item->children() as $child_li) {
1926
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
-            if ($child_li_copy !== null) {
1928
-                $copied_li->add_child_line_item($child_li_copy);
1929
-                if (
1930
-                    $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
-                    $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
-                ) {
1933
-                    $ticket_children++;
1934
-                }
1935
-            }
1936
-        }
1937
-        // if this is an event subtotal with NO ticket children
1938
-        // we basically want to ignore it
1939
-        if (
1940
-            $ticket_children === 0
1941
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1942
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
-            && $line_item->total() === 0
1944
-        ) {
1945
-            return null;
1946
-        }
1947
-        return $copied_li;
1948
-    }
1949
-
1950
-
1951
-    /**
1952
-     * Creates a new, unsaved line item, but if it's a ticket line item
1953
-     * with a total of 0, or a subtotal of 0, returns null instead
1954
-     *
1955
-     * @param EE_Line_Item $line_item
1956
-     * @return EE_Line_Item
1957
-     * @throws EE_Error
1958
-     * @throws InvalidArgumentException
1959
-     * @throws InvalidDataTypeException
1960
-     * @throws InvalidInterfaceException
1961
-     * @throws ReflectionException
1962
-     */
1963
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1964
-    {
1965
-        if (
1966
-            $line_item->type() === EEM_Line_Item::type_line_item
1967
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
-            && $line_item->quantity() === 0
1969
-        ) {
1970
-            return null;
1971
-        }
1972
-        $new_li_fields = $line_item->model_field_array();
1973
-        // don't set the total. We'll leave that up to the code that calculates it
1974
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
-        return EE_Line_Item::new_instance($new_li_fields);
1976
-    }
1977
-
1978
-
1979
-    /**
1980
-     * Cycles through all of the ticket line items for the supplied total line item
1981
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
-     *
1983
-     * @param EE_Line_Item $total_line_item
1984
-     * @since 4.9.79.p
1985
-     * @throws EE_Error
1986
-     * @throws InvalidArgumentException
1987
-     * @throws InvalidDataTypeException
1988
-     * @throws InvalidInterfaceException
1989
-     * @throws ReflectionException
1990
-     */
1991
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
-    {
1993
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
-        foreach ($ticket_line_items as $ticket_line_item) {
1995
-            if (
1996
-                $ticket_line_item instanceof EE_Line_Item
1997
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
-            ) {
1999
-                $ticket = $ticket_line_item->ticket();
2000
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
2002
-                    $ticket_line_item->save();
2003
-                }
2004
-            }
2005
-        }
2006
-    }
2007
-
2008
-
2009
-    /**
2010
-     * @return EE_Line_Item[]
2011
-     * @throws EE_Error
2012
-     * @throws ReflectionException
2013
-     * @since   $VID:$
2014
-     */
2015
-    private static function getGlobalTaxes(): array
2016
-    {
2017
-        if (EEH_Line_Item::$global_taxes === null) {
2018
-
2019
-            /** @type EEM_Price $EEM_Price */
2020
-            $EEM_Price = EE_Registry::instance()->load_model('Price');
2021
-            // get array of taxes via Price Model
2022
-            EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
-            ksort(EEH_Line_Item::$global_taxes);
2024
-        }
2025
-        return EEH_Line_Item::$global_taxes;
2026
-    }
2027
-
2028
-
2029
-
2030
-    /**************************************** @DEPRECATED METHODS *************************************** */
2031
-    /**
2032
-     * @deprecated
2033
-     * @param EE_Line_Item $total_line_item
2034
-     * @return EE_Line_Item
2035
-     * @throws EE_Error
2036
-     * @throws InvalidArgumentException
2037
-     * @throws InvalidDataTypeException
2038
-     * @throws InvalidInterfaceException
2039
-     * @throws ReflectionException
2040
-     */
2041
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
-    {
2043
-        EE_Error::doing_it_wrong(
2044
-            'EEH_Line_Item::get_items_subtotal()',
2045
-            sprintf(
2046
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
-                'EEH_Line_Item::get_pre_tax_subtotal()'
2048
-            ),
2049
-            '4.6.0'
2050
-        );
2051
-        return self::get_pre_tax_subtotal($total_line_item);
2052
-    }
2053
-
2054
-
2055
-    /**
2056
-     * @deprecated
2057
-     * @param EE_Transaction $transaction
2058
-     * @return EE_Line_Item
2059
-     * @throws EE_Error
2060
-     * @throws InvalidArgumentException
2061
-     * @throws InvalidDataTypeException
2062
-     * @throws InvalidInterfaceException
2063
-     * @throws ReflectionException
2064
-     */
2065
-    public static function create_default_total_line_item($transaction = null)
2066
-    {
2067
-        EE_Error::doing_it_wrong(
2068
-            'EEH_Line_Item::create_default_total_line_item()',
2069
-            sprintf(
2070
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
-                'EEH_Line_Item::create_total_line_item()'
2072
-            ),
2073
-            '4.6.0'
2074
-        );
2075
-        return self::create_total_line_item($transaction);
2076
-    }
2077
-
2078
-
2079
-    /**
2080
-     * @deprecated
2081
-     * @param EE_Line_Item   $total_line_item
2082
-     * @param EE_Transaction $transaction
2083
-     * @return EE_Line_Item
2084
-     * @throws EE_Error
2085
-     * @throws InvalidArgumentException
2086
-     * @throws InvalidDataTypeException
2087
-     * @throws InvalidInterfaceException
2088
-     * @throws ReflectionException
2089
-     */
2090
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
-    {
2092
-        EE_Error::doing_it_wrong(
2093
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2094
-            sprintf(
2095
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2097
-            ),
2098
-            '4.6.0'
2099
-        );
2100
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
-    }
2102
-
2103
-
2104
-    /**
2105
-     * @deprecated
2106
-     * @param EE_Line_Item   $total_line_item
2107
-     * @param EE_Transaction $transaction
2108
-     * @return EE_Line_Item
2109
-     * @throws EE_Error
2110
-     * @throws InvalidArgumentException
2111
-     * @throws InvalidDataTypeException
2112
-     * @throws InvalidInterfaceException
2113
-     * @throws ReflectionException
2114
-     */
2115
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
-    {
2117
-        EE_Error::doing_it_wrong(
2118
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2119
-            sprintf(
2120
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
-                'EEH_Line_Item::create_taxes_subtotal()'
2122
-            ),
2123
-            '4.6.0'
2124
-        );
2125
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2126
-    }
2127
-
2128
-
2129
-    /**
2130
-     * @deprecated
2131
-     * @param EE_Line_Item   $total_line_item
2132
-     * @param EE_Transaction $transaction
2133
-     * @return EE_Line_Item
2134
-     * @throws EE_Error
2135
-     * @throws InvalidArgumentException
2136
-     * @throws InvalidDataTypeException
2137
-     * @throws InvalidInterfaceException
2138
-     * @throws ReflectionException
2139
-     */
2140
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
-    {
2142
-        EE_Error::doing_it_wrong(
2143
-            'EEH_Line_Item::create_default_event_subtotal()',
2144
-            sprintf(
2145
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
-                'EEH_Line_Item::create_event_subtotal()'
2147
-            ),
2148
-            '4.6.0'
2149
-        );
2150
-        return self::create_event_subtotal($total_line_item, $transaction);
2151
-    }
24
+	/**
25
+	 * @var EE_Line_Item[]
26
+	 */
27
+	private static $global_taxes;
28
+
29
+
30
+	/**
31
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
34
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
+	 * to keep the registration final prices in-sync with the transaction's total.
36
+	 *
37
+	 * @param EE_Line_Item $parent_line_item
38
+	 * @param string       $name
39
+	 * @param float        $unit_price
40
+	 * @param string       $description
41
+	 * @param int          $quantity
42
+	 * @param boolean      $taxable
43
+	 * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
+	 * @param bool         $return_item
45
+	 * @param bool         $recalculate_totals
46
+	 * @return boolean|EE_Line_Item success
47
+	 * @throws EE_Error
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function add_unrelated_item(
51
+		EE_Line_Item $parent_line_item,
52
+		string $name,
53
+		float $unit_price,
54
+		string $description = '',
55
+		int $quantity = 1,
56
+		bool $taxable = false,
57
+		?string $code = null,
58
+		bool $return_item = false,
59
+		bool $recalculate_totals = true
60
+	) {
61
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
+		$line_item      = EE_Line_Item::new_instance(
63
+			[
64
+				'LIN_name'       => $name,
65
+				'LIN_desc'       => $description,
66
+				'LIN_unit_price' => $unit_price,
67
+				'LIN_quantity'   => $quantity,
68
+				'LIN_percent'    => null,
69
+				'LIN_is_taxable' => $taxable,
70
+				'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
+					? count($items_subtotal->children())
72
+					: 0,
73
+				'LIN_total'      => (float) $unit_price * (int) $quantity,
74
+				'LIN_type'       => EEM_Line_Item::type_line_item,
75
+				'LIN_code'       => $code,
76
+			]
77
+		);
78
+		$line_item      = apply_filters(
79
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
+			$line_item,
81
+			$parent_line_item
82
+		);
83
+		$added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
+		return $return_item ? $line_item : $added;
85
+	}
86
+
87
+
88
+	/**
89
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
90
+	 * in the correct spot in the line item tree. Does not automatically
91
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
+	 * registrations' final prices (which should probably change because of this).
93
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
94
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
96
+	 *
97
+	 * @param EE_Line_Item $parent_line_item
98
+	 * @param string       $name
99
+	 * @param float        $percentage_amount
100
+	 * @param string       $description
101
+	 * @param boolean      $taxable
102
+	 * @param string|null  $code
103
+	 * @param bool         $return_item
104
+	 * @return boolean|EE_Line_Item success
105
+	 * @throws EE_Error
106
+	 * @throws ReflectionException
107
+	 */
108
+	public static function add_percentage_based_item(
109
+		EE_Line_Item $parent_line_item,
110
+		string $name,
111
+		float $percentage_amount,
112
+		string $description = '',
113
+		bool $taxable = false,
114
+		?string $code = null,
115
+		bool $return_item = false
116
+	) {
117
+		$total = $percentage_amount * $parent_line_item->total() / 100;
118
+		$line_item = EE_Line_Item::new_instance(
119
+			[
120
+				'LIN_name'       => $name,
121
+				'LIN_desc'       => $description,
122
+				'LIN_unit_price' => 0,
123
+				'LIN_percent'    => $percentage_amount,
124
+				'LIN_quantity'   => 1,
125
+				'LIN_is_taxable' => $taxable,
126
+				'LIN_total'      => (float) $total,
127
+				'LIN_type'       => EEM_Line_Item::type_line_item,
128
+				'LIN_parent'     => $parent_line_item->ID(),
129
+				'LIN_code'       => $code,
130
+			]
131
+		);
132
+		$line_item = apply_filters(
133
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
+			$line_item
135
+		);
136
+		$added     = $parent_line_item->add_child_line_item($line_item, false);
137
+		return $return_item ? $line_item : $added;
138
+	}
139
+
140
+
141
+	/**
142
+	 * Returns the new line item created by adding a purchase of the ticket
143
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
144
+	 * If this ticket has already been purchased, just increments its count.
145
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
146
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
+	 * should probably change because of this).
148
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
150
+	 *
151
+	 * @param EE_Line_Item|null $total_line_item grand total line item of type EEM_Line_Item::type_total
152
+	 * @param EE_Ticket         $ticket
153
+	 * @param int               $qty
154
+	 * @return EE_Line_Item
155
+	 * @throws EE_Error
156
+	 * @throws ReflectionException
157
+	 */
158
+	public static function add_ticket_purchase(
159
+		?EE_Line_Item $total_line_item,
160
+		EE_Ticket $ticket,
161
+		int $qty = 1
162
+	): ?EE_Line_Item {
163
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
+			throw new EE_Error(
165
+				sprintf(
166
+					esc_html__(
167
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
+						'event_espresso'
169
+					),
170
+					$ticket->ID(),
171
+					$total_line_item->ID()
172
+				)
173
+			);
174
+		}
175
+		// either increment the qty for an existing ticket
176
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
+		// or add a new one
178
+		if (! $line_item instanceof EE_Line_Item) {
179
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
+		}
181
+		$total_line_item->recalculate_total_including_taxes();
182
+		return $line_item;
183
+	}
184
+
185
+
186
+	/**
187
+	 * Returns the new line item created by adding a purchase of the ticket
188
+	 *
189
+	 * @param EE_Line_Item $total_line_item
190
+	 * @param EE_Ticket    $ticket
191
+	 * @param int          $qty
192
+	 * @return EE_Line_Item
193
+	 * @throws EE_Error
194
+	 * @throws InvalidArgumentException
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws ReflectionException
198
+	 */
199
+	public static function increment_ticket_qty_if_already_in_cart(
200
+		EE_Line_Item $total_line_item,
201
+		EE_Ticket $ticket,
202
+		$qty = 1
203
+	) {
204
+		$line_item = null;
205
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
+			foreach ($ticket_line_items as $ticket_line_item) {
208
+				if (
209
+					$ticket_line_item instanceof EE_Line_Item
210
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
+				) {
212
+					$line_item = $ticket_line_item;
213
+					break;
214
+				}
215
+			}
216
+		}
217
+		if ($line_item instanceof EE_Line_Item) {
218
+			EEH_Line_Item::increment_quantity($line_item, $qty);
219
+			return $line_item;
220
+		}
221
+		return null;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
+	 * Does NOT save or recalculate other line items totals
228
+	 *
229
+	 * @param EE_Line_Item $line_item
230
+	 * @param int          $qty
231
+	 * @return void
232
+	 * @throws EE_Error
233
+	 * @throws InvalidArgumentException
234
+	 * @throws InvalidDataTypeException
235
+	 * @throws InvalidInterfaceException
236
+	 * @throws ReflectionException
237
+	 */
238
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
+	{
240
+		if (! $line_item->is_percent()) {
241
+			$qty += $line_item->quantity();
242
+			$line_item->set_quantity($qty);
243
+			$line_item->set_total($line_item->unit_price() * $qty);
244
+			$line_item->save();
245
+		}
246
+		foreach ($line_item->children() as $child) {
247
+			if ($child->is_sub_line_item()) {
248
+				EEH_Line_Item::update_quantity($child, $qty);
249
+			}
250
+		}
251
+	}
252
+
253
+
254
+	/**
255
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
+	 * Does NOT save or recalculate other line items totals
257
+	 *
258
+	 * @param EE_Line_Item $line_item
259
+	 * @param int          $qty
260
+	 * @return void
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
+	{
269
+		if (! $line_item->is_percent()) {
270
+			$qty = $line_item->quantity() - $qty;
271
+			$qty = max($qty, 0);
272
+			$line_item->set_quantity($qty);
273
+			$line_item->set_total($line_item->unit_price() * $qty);
274
+			$line_item->save();
275
+		}
276
+		foreach ($line_item->children() as $child) {
277
+			if ($child->is_sub_line_item()) {
278
+				EEH_Line_Item::update_quantity($child, $qty);
279
+			}
280
+		}
281
+	}
282
+
283
+
284
+	/**
285
+	 * Updates the line item and its children's quantities to the specified number.
286
+	 * Does NOT save them or recalculate totals.
287
+	 *
288
+	 * @param EE_Line_Item $line_item
289
+	 * @param int          $new_quantity
290
+	 * @throws EE_Error
291
+	 * @throws InvalidArgumentException
292
+	 * @throws InvalidDataTypeException
293
+	 * @throws InvalidInterfaceException
294
+	 * @throws ReflectionException
295
+	 */
296
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
+	{
298
+		if (! $line_item->is_percent()) {
299
+			$line_item->set_quantity($new_quantity);
300
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
301
+			$line_item->save();
302
+		}
303
+		foreach ($line_item->children() as $child) {
304
+			if ($child->is_sub_line_item()) {
305
+				EEH_Line_Item::update_quantity($child, $new_quantity);
306
+			}
307
+		}
308
+	}
309
+
310
+
311
+	/**
312
+	 * Returns the new line item created by adding a purchase of the ticket
313
+	 *
314
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
+	 * @param EE_Ticket    $ticket
316
+	 * @param int          $qty
317
+	 * @return EE_Line_Item
318
+	 * @throws EE_Error
319
+	 * @throws InvalidArgumentException
320
+	 * @throws InvalidDataTypeException
321
+	 * @throws InvalidInterfaceException
322
+	 * @throws ReflectionException
323
+	 */
324
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
+	{
326
+		$datetimes = $ticket->datetimes();
327
+		$first_datetime = reset($datetimes);
328
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
329
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
+			$first_datetime_name = $first_datetime->event()->name();
331
+		}
332
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
+		// get event subtotal line
334
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
+		$taxes = $ticket->tax_price_modifiers();
336
+		// add $ticket to cart
337
+		$line_item = EE_Line_Item::new_instance(array(
338
+			'LIN_name'       => $ticket->name(),
339
+			'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
+			'LIN_unit_price' => $ticket->price(),
341
+			'LIN_quantity'   => $qty,
342
+			'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
+			'LIN_order'      => count($events_sub_total->children()),
344
+			'LIN_total'      => $ticket->price() * $qty,
345
+			'LIN_type'       => EEM_Line_Item::type_line_item,
346
+			'OBJ_ID'         => $ticket->ID(),
347
+			'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
+		));
349
+		$line_item = apply_filters(
350
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
+			$line_item
352
+		);
353
+		if (!$line_item instanceof EE_Line_Item) {
354
+			throw new DomainException(
355
+				esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
+			);
357
+		}
358
+		$events_sub_total->add_child_line_item($line_item);
359
+		// now add the sub-line items
360
+		$running_total = 0;
361
+		$running_pre_tax_total = 0;
362
+		foreach ($ticket->prices() as $price) {
363
+			$sign = $price->is_discount() ? -1 : 1;
364
+			$price_total = $price->is_percent()
365
+				? $running_pre_tax_total * $price->amount() / 100
366
+				: $price->amount() * $qty;
367
+			if ($price->is_percent()) {
368
+				$percent = $sign * $price->amount();
369
+				$unit_price = 0;
370
+			} else {
371
+				$percent    = 0;
372
+				$unit_price = $sign * $price->amount();
373
+			}
374
+			$sub_line_item = EE_Line_Item::new_instance(array(
375
+				'LIN_name'       => $price->name(),
376
+				'LIN_desc'       => $price->desc(),
377
+				'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
+				'LIN_is_taxable' => false,
379
+				'LIN_order'      => $price->order(),
380
+				'LIN_total'      => $price_total,
381
+				'LIN_pretax'     => 0,
382
+				'LIN_unit_price' => $unit_price,
383
+				'LIN_percent'    => $percent,
384
+				'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
+				'OBJ_ID'         => $price->ID(),
386
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
+			));
388
+			$sub_line_item = apply_filters(
389
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
+				$sub_line_item
391
+			);
392
+			$running_total += $sign * $price_total;
393
+			$running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
+			$line_item->add_child_line_item($sub_line_item);
395
+		}
396
+		$line_item->setPretaxTotal($running_pre_tax_total);
397
+		return $line_item;
398
+	}
399
+
400
+
401
+	/**
402
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
+	 * re-calculates the line item totals and updates the related transaction. But
404
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
+	 * should probably change because of this).
406
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
408
+	 *
409
+	 * @param EE_Line_Item $total_line_item
410
+	 * @param EE_Line_Item $item to be added
411
+	 * @return boolean
412
+	 * @throws EE_Error
413
+	 * @throws InvalidArgumentException
414
+	 * @throws InvalidDataTypeException
415
+	 * @throws InvalidInterfaceException
416
+	 * @throws ReflectionException
417
+	 */
418
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
+	{
420
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
+			$success = $pre_tax_subtotal->add_child_line_item($item);
423
+		} else {
424
+			return false;
425
+		}
426
+		if ($recalculate_totals) {
427
+			$total_line_item->recalculate_total_including_taxes();
428
+		}
429
+		return $success;
430
+	}
431
+
432
+
433
+	/**
434
+	 * cancels an existing ticket line item,
435
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
+	 *
438
+	 * @param EE_Line_Item $ticket_line_item
439
+	 * @param int          $qty
440
+	 * @return bool success
441
+	 * @throws EE_Error
442
+	 * @throws InvalidArgumentException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws InvalidInterfaceException
445
+	 * @throws ReflectionException
446
+	 */
447
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
+	{
449
+		// validate incoming line_item
450
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
+			throw new EE_Error(
452
+				sprintf(
453
+					esc_html__(
454
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
+						'event_espresso'
456
+					),
457
+					$ticket_line_item->type()
458
+				)
459
+			);
460
+		}
461
+		if ($ticket_line_item->quantity() < $qty) {
462
+			throw new EE_Error(
463
+				sprintf(
464
+					esc_html__(
465
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
+						'event_espresso'
467
+					),
468
+					$qty,
469
+					$ticket_line_item->quantity()
470
+				)
471
+			);
472
+		}
473
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
+		foreach ($ticket_line_item->children() as $child_line_item) {
476
+			if (
477
+				$child_line_item->is_sub_line_item()
478
+				&& ! $child_line_item->is_percent()
479
+				&& ! $child_line_item->is_cancellation()
480
+			) {
481
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
+			}
483
+		}
484
+		// get cancellation sub line item
485
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
+			$ticket_line_item,
487
+			EEM_Line_Item::type_cancellation
488
+		);
489
+		$cancellation_line_item = reset($cancellation_line_item);
490
+		// verify that this ticket was indeed previously cancelled
491
+		if ($cancellation_line_item instanceof EE_Line_Item) {
492
+			// increment cancelled quantity
493
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
+		} else {
495
+			// create cancellation sub line item
496
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
497
+				'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
+				'LIN_desc'       => sprintf(
499
+					esc_html_x(
500
+						'Cancelled %1$s : %2$s',
501
+						'Cancelled Ticket Name : 2015-01-01 11:11',
502
+						'event_espresso'
503
+					),
504
+					$ticket_line_item->name(),
505
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
+				),
507
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
+				'LIN_quantity'   => $qty,
509
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
+				'LIN_order'      => count($ticket_line_item->children()),
511
+				'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
+				'LIN_type'       => EEM_Line_Item::type_cancellation,
513
+			));
514
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
515
+		}
516
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
517
+			// decrement parent line item quantity
518
+			$event_line_item = $ticket_line_item->parent();
519
+			if (
520
+				$event_line_item instanceof EE_Line_Item
521
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
+			) {
523
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
+				$event_line_item->save();
525
+			}
526
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
+			return true;
528
+		}
529
+		return false;
530
+	}
531
+
532
+
533
+	/**
534
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
535
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
+	 *
538
+	 * @param EE_Line_Item $ticket_line_item
539
+	 * @param int          $qty
540
+	 * @return bool success
541
+	 * @throws EE_Error
542
+	 * @throws InvalidArgumentException
543
+	 * @throws InvalidDataTypeException
544
+	 * @throws InvalidInterfaceException
545
+	 * @throws ReflectionException
546
+	 */
547
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
+	{
549
+		// validate incoming line_item
550
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
+			throw new EE_Error(
552
+				sprintf(
553
+					esc_html__(
554
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
+						'event_espresso'
556
+					),
557
+					$ticket_line_item->type()
558
+				)
559
+			);
560
+		}
561
+		// get cancellation sub line item
562
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
+			$ticket_line_item,
564
+			EEM_Line_Item::type_cancellation
565
+		);
566
+		$cancellation_line_item = reset($cancellation_line_item);
567
+		// verify that this ticket was indeed previously cancelled
568
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
569
+			return false;
570
+		}
571
+		if ($cancellation_line_item->quantity() > $qty) {
572
+			// decrement cancelled quantity
573
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
+		} elseif ($cancellation_line_item->quantity() === $qty) {
575
+			// decrement cancelled quantity in case anyone still has the object kicking around
576
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
+			// delete because quantity will end up as 0
578
+			$cancellation_line_item->delete();
579
+			// and attempt to destroy the object,
580
+			// even though PHP won't actually destroy it until it needs the memory
581
+			unset($cancellation_line_item);
582
+		} else {
583
+			// what ?!?! negative quantity ?!?!
584
+			throw new EE_Error(
585
+				sprintf(
586
+					esc_html__(
587
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
+						'event_espresso'
589
+					),
590
+					$qty,
591
+					$cancellation_line_item->quantity()
592
+				)
593
+			);
594
+		}
595
+		// increment ticket quantity
596
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
598
+			// increment parent line item quantity
599
+			$event_line_item = $ticket_line_item->parent();
600
+			if (
601
+				$event_line_item instanceof EE_Line_Item
602
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
+			) {
604
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
+			}
606
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
+			return true;
608
+		}
609
+		return false;
610
+	}
611
+
612
+
613
+	/**
614
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
+	 *
617
+	 * @param EE_Line_Item $line_item
618
+	 * @return float
619
+	 * @throws EE_Error
620
+	 * @throws InvalidArgumentException
621
+	 * @throws InvalidDataTypeException
622
+	 * @throws InvalidInterfaceException
623
+	 * @throws ReflectionException
624
+	 */
625
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
+	{
627
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
+		return $grand_total_line_item->recalculate_total_including_taxes();
629
+	}
630
+
631
+
632
+	/**
633
+	 * Gets the line item which contains the subtotal of all the items
634
+	 *
635
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
+	 * @return EE_Line_Item
637
+	 * @throws EE_Error
638
+	 * @throws InvalidArgumentException
639
+	 * @throws InvalidDataTypeException
640
+	 * @throws InvalidInterfaceException
641
+	 * @throws ReflectionException
642
+	 */
643
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
+	{
645
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
+		return $pre_tax_subtotal instanceof EE_Line_Item
647
+			? $pre_tax_subtotal
648
+			: self::create_pre_tax_subtotal($total_line_item);
649
+	}
650
+
651
+
652
+	/**
653
+	 * Gets the line item for the taxes subtotal
654
+	 *
655
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
+	 * @return EE_Line_Item
657
+	 * @throws EE_Error
658
+	 * @throws InvalidArgumentException
659
+	 * @throws InvalidDataTypeException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws ReflectionException
662
+	 */
663
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
+	{
665
+		$taxes = $total_line_item->get_child_line_item('taxes');
666
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
+	}
668
+
669
+
670
+	/**
671
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
+	 *
673
+	 * @param EE_Line_Item   $line_item
674
+	 * @param EE_Transaction $transaction
675
+	 * @return void
676
+	 * @throws EE_Error
677
+	 * @throws InvalidArgumentException
678
+	 * @throws InvalidDataTypeException
679
+	 * @throws InvalidInterfaceException
680
+	 * @throws ReflectionException
681
+	 */
682
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
+	{
684
+		if ($transaction) {
685
+			/** @type EEM_Transaction $EEM_Transaction */
686
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
+			$line_item->set_TXN_ID($TXN_ID);
689
+		}
690
+	}
691
+
692
+
693
+	/**
694
+	 * Creates a new default total line item for the transaction,
695
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
696
+	 * existing taxes as children of the taxes subtotal line item)
697
+	 *
698
+	 * @param EE_Transaction $transaction
699
+	 * @return EE_Line_Item of type total
700
+	 * @throws EE_Error
701
+	 * @throws InvalidArgumentException
702
+	 * @throws InvalidDataTypeException
703
+	 * @throws InvalidInterfaceException
704
+	 * @throws ReflectionException
705
+	 */
706
+	public static function create_total_line_item($transaction = null)
707
+	{
708
+		$total_line_item = EE_Line_Item::new_instance(array(
709
+			'LIN_code' => 'total',
710
+			'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
+			'LIN_type' => EEM_Line_Item::type_total,
712
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
+		));
714
+		$total_line_item = apply_filters(
715
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
+			$total_line_item
717
+		);
718
+		self::set_TXN_ID($total_line_item, $transaction);
719
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
720
+		self::create_taxes_subtotal($total_line_item, $transaction);
721
+		return $total_line_item;
722
+	}
723
+
724
+
725
+	/**
726
+	 * Creates a default items subtotal line item
727
+	 *
728
+	 * @param EE_Line_Item   $total_line_item
729
+	 * @param EE_Transaction $transaction
730
+	 * @return EE_Line_Item
731
+	 * @throws EE_Error
732
+	 * @throws InvalidArgumentException
733
+	 * @throws InvalidDataTypeException
734
+	 * @throws InvalidInterfaceException
735
+	 * @throws ReflectionException
736
+	 */
737
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
+	{
739
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
740
+			'LIN_code' => 'pre-tax-subtotal',
741
+			'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
+			'LIN_type' => EEM_Line_Item::type_sub_total,
743
+		));
744
+		$pre_tax_line_item = apply_filters(
745
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
+			$pre_tax_line_item
747
+		);
748
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
749
+		$total_line_item->add_child_line_item($pre_tax_line_item);
750
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
751
+		return $pre_tax_line_item;
752
+	}
753
+
754
+
755
+	/**
756
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
757
+	 * and applies taxes to it
758
+	 *
759
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
+	 * @param EE_Transaction $transaction
761
+	 * @return EE_Line_Item
762
+	 * @throws EE_Error
763
+	 * @throws InvalidArgumentException
764
+	 * @throws InvalidDataTypeException
765
+	 * @throws InvalidInterfaceException
766
+	 * @throws ReflectionException
767
+	 */
768
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
+	{
770
+		$tax_line_item = EE_Line_Item::new_instance(array(
771
+			'LIN_code'  => 'taxes',
772
+			'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
+			'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
+			'LIN_order' => 1000,// this should always come last
775
+		));
776
+		$tax_line_item = apply_filters(
777
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
+			$tax_line_item
779
+		);
780
+		self::set_TXN_ID($tax_line_item, $transaction);
781
+		$total_line_item->add_child_line_item($tax_line_item);
782
+		// and lastly, add the actual taxes
783
+		self::apply_taxes($total_line_item);
784
+		return $tax_line_item;
785
+	}
786
+
787
+
788
+	/**
789
+	 * Creates a default items subtotal line item
790
+	 *
791
+	 * @param EE_Line_Item   $pre_tax_line_item
792
+	 * @param EE_Transaction $transaction
793
+	 * @param EE_Event       $event
794
+	 * @return EE_Line_Item
795
+	 * @throws EE_Error
796
+	 * @throws InvalidArgumentException
797
+	 * @throws InvalidDataTypeException
798
+	 * @throws InvalidInterfaceException
799
+	 * @throws ReflectionException
800
+	 */
801
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
+	{
803
+		$event_line_item = EE_Line_Item::new_instance(array(
804
+			'LIN_code' => self::get_event_code($event),
805
+			'LIN_name' => self::get_event_name($event),
806
+			'LIN_desc' => self::get_event_desc($event),
807
+			'LIN_type' => EEM_Line_Item::type_sub_total,
808
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
+			'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
+		));
811
+		$event_line_item = apply_filters(
812
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
+			$event_line_item
814
+		);
815
+		self::set_TXN_ID($event_line_item, $transaction);
816
+		$pre_tax_line_item->add_child_line_item($event_line_item);
817
+		return $event_line_item;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets what the event ticket's code SHOULD be
823
+	 *
824
+	 * @param EE_Event $event
825
+	 * @return string
826
+	 * @throws EE_Error
827
+	 */
828
+	public static function get_event_code($event)
829
+	{
830
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets the event name
836
+	 *
837
+	 * @param EE_Event $event
838
+	 * @return string
839
+	 * @throws EE_Error
840
+	 */
841
+	public static function get_event_name($event)
842
+	{
843
+		return $event instanceof EE_Event
844
+			? mb_substr($event->name(), 0, 245)
845
+			: esc_html__('Event', 'event_espresso');
846
+	}
847
+
848
+
849
+	/**
850
+	 * Gets the event excerpt
851
+	 *
852
+	 * @param EE_Event $event
853
+	 * @return string
854
+	 * @throws EE_Error
855
+	 */
856
+	public static function get_event_desc($event)
857
+	{
858
+		return $event instanceof EE_Event ? $event->short_description() : '';
859
+	}
860
+
861
+
862
+	/**
863
+	 * Given the grand total line item and a ticket, finds the event sub-total
864
+	 * line item the ticket's purchase should be added onto
865
+	 *
866
+	 * @access public
867
+	 * @param EE_Line_Item $grand_total the grand total line item
868
+	 * @param EE_Ticket    $ticket
869
+	 * @return EE_Line_Item
870
+	 * @throws EE_Error
871
+	 * @throws InvalidArgumentException
872
+	 * @throws InvalidDataTypeException
873
+	 * @throws InvalidInterfaceException
874
+	 * @throws ReflectionException
875
+	 */
876
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
+	{
878
+		$first_datetime = $ticket->first_datetime();
879
+		if (! $first_datetime instanceof EE_Datetime) {
880
+			throw new EE_Error(
881
+				sprintf(
882
+					esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
+					$ticket->ID()
884
+				)
885
+			);
886
+		}
887
+		$event = $first_datetime->event();
888
+		if (! $event instanceof EE_Event) {
889
+			throw new EE_Error(
890
+				sprintf(
891
+					esc_html__(
892
+						'The supplied ticket (ID %d) has no event data associated with it.',
893
+						'event_espresso'
894
+					),
895
+					$ticket->ID()
896
+				)
897
+			);
898
+		}
899
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
+		if (! $events_sub_total instanceof EE_Line_Item) {
901
+			throw new EE_Error(
902
+				sprintf(
903
+					esc_html__(
904
+						'There is no events sub-total for ticket %s on total line item %d',
905
+						'event_espresso'
906
+					),
907
+					$ticket->ID(),
908
+					$grand_total->ID()
909
+				)
910
+			);
911
+		}
912
+		return $events_sub_total;
913
+	}
914
+
915
+
916
+	/**
917
+	 * Gets the event line item
918
+	 *
919
+	 * @param EE_Line_Item $grand_total
920
+	 * @param EE_Event     $event
921
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
+	 * @throws EE_Error
923
+	 * @throws InvalidArgumentException
924
+	 * @throws InvalidDataTypeException
925
+	 * @throws InvalidInterfaceException
926
+	 * @throws ReflectionException
927
+	 */
928
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
+	{
930
+		/** @type EE_Event $event */
931
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
932
+		$event_line_item = null;
933
+		$found = false;
934
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
+			// default event subtotal, we should only ever find this the first time this method is called
936
+			if (! $event_line_item->OBJ_ID()) {
937
+				// let's use this! but first... set the event details
938
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
+				$found = true;
940
+				break;
941
+			}
942
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
943
+				// found existing line item for this event in the cart, so break out of loop and use this one
944
+				$found = true;
945
+				break;
946
+			}
947
+		}
948
+		if (! $found) {
949
+			// there is no event sub-total yet, so add it
950
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
+			// create a new "event" subtotal below that
952
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
+			// and set the event details
954
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
+		}
956
+		return $event_line_item;
957
+	}
958
+
959
+
960
+	/**
961
+	 * Creates a default items subtotal line item
962
+	 *
963
+	 * @param EE_Line_Item   $event_line_item
964
+	 * @param EE_Event       $event
965
+	 * @param EE_Transaction $transaction
966
+	 * @return void
967
+	 * @throws EE_Error
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidDataTypeException
970
+	 * @throws InvalidInterfaceException
971
+	 * @throws ReflectionException
972
+	 */
973
+	public static function set_event_subtotal_details(
974
+		EE_Line_Item $event_line_item,
975
+		EE_Event $event,
976
+		$transaction = null
977
+	) {
978
+		if ($event instanceof EE_Event) {
979
+			$event_line_item->set_code(self::get_event_code($event));
980
+			$event_line_item->set_name(self::get_event_name($event));
981
+			$event_line_item->set_desc(self::get_event_desc($event));
982
+			$event_line_item->set_OBJ_ID($event->ID());
983
+		}
984
+		self::set_TXN_ID($event_line_item, $transaction);
985
+	}
986
+
987
+
988
+	/**
989
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
+	 * any old taxes are removed
992
+	 *
993
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
+	 * @param bool         $update_txn_status
995
+	 * @return bool
996
+	 * @throws EE_Error
997
+	 * @throws InvalidArgumentException
998
+	 * @throws InvalidDataTypeException
999
+	 * @throws InvalidInterfaceException
1000
+	 * @throws ReflectionException
1001
+	 * @throws RuntimeException
1002
+	 */
1003
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
+	{
1005
+		$total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
+		$existing_global_taxes = $taxes_line_item->tax_descendants();
1008
+		$updates = false;
1009
+		// loop thru taxes
1010
+		$global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
+		foreach ($global_taxes as $order => $taxes) {
1012
+			foreach ($taxes as $tax) {
1013
+				if ($tax instanceof EE_Price) {
1014
+					$found = false;
1015
+					// check if this is already an existing tax
1016
+					foreach ($existing_global_taxes as $existing_global_tax) {
1017
+						if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
+							// maybe update the tax rate in case it has changed
1019
+							if ($existing_global_tax->percent() !== $tax->amount()) {
1020
+								$existing_global_tax->set_percent($tax->amount());
1021
+								$existing_global_tax->save();
1022
+								$updates = true;
1023
+							}
1024
+							$found = true;
1025
+							break;
1026
+						}
1027
+					}
1028
+					if (! $found) {
1029
+						// add a new line item for this global tax
1030
+						$tax_line_item = apply_filters(
1031
+							'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
+							EE_Line_Item::new_instance(
1033
+								[
1034
+									'LIN_name'       => $tax->name(),
1035
+									'LIN_desc'       => $tax->desc(),
1036
+									'LIN_percent'    => $tax->amount(),
1037
+									'LIN_is_taxable' => false,
1038
+									'LIN_order'      => $order,
1039
+									'LIN_total'      => 0,
1040
+									'LIN_type'       => EEM_Line_Item::type_tax,
1041
+									'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
+									'OBJ_ID'         => $tax->ID(),
1043
+								]
1044
+							)
1045
+						);
1046
+						$updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
+					}
1048
+				}
1049
+			}
1050
+		}
1051
+		// only recalculate totals if something changed
1052
+		if ($updates) {
1053
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
+			return true;
1055
+		}
1056
+		return false;
1057
+	}
1058
+
1059
+
1060
+	/**
1061
+	 * Ensures that taxes have been applied to the order, if not applies them.
1062
+	 * Returns the total amount of tax
1063
+	 *
1064
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
+	 * @return float
1066
+	 * @throws EE_Error
1067
+	 * @throws InvalidArgumentException
1068
+	 * @throws InvalidDataTypeException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws ReflectionException
1071
+	 */
1072
+	public static function ensure_taxes_applied($total_line_item)
1073
+	{
1074
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
+		if (! $taxes_subtotal->children()) {
1076
+			self::apply_taxes($total_line_item);
1077
+		}
1078
+		return $taxes_subtotal->total();
1079
+	}
1080
+
1081
+
1082
+	/**
1083
+	 * Deletes ALL children of the passed line item
1084
+	 *
1085
+	 * @param EE_Line_Item $parent_line_item
1086
+	 * @return bool
1087
+	 * @throws EE_Error
1088
+	 * @throws InvalidArgumentException
1089
+	 * @throws InvalidDataTypeException
1090
+	 * @throws InvalidInterfaceException
1091
+	 * @throws ReflectionException
1092
+	 */
1093
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
+	{
1095
+		$deleted = 0;
1096
+		foreach ($parent_line_item->children() as $child_line_item) {
1097
+			if ($child_line_item instanceof EE_Line_Item) {
1098
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
+				if ($child_line_item->ID()) {
1100
+					$child_line_item->delete();
1101
+					unset($child_line_item);
1102
+				} else {
1103
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1104
+				}
1105
+				$deleted++;
1106
+			}
1107
+		}
1108
+		return $deleted;
1109
+	}
1110
+
1111
+
1112
+	/**
1113
+	 * Deletes the line items as indicated by the line item code(s) provided,
1114
+	 * regardless of where they're found in the line item tree. Automatically
1115
+	 * re-calculates the line item totals and updates the related transaction. But
1116
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
+	 * should probably change because of this).
1118
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
+	 *
1121
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
+	 * @param array|bool|string $line_item_codes
1123
+	 * @return int number of items successfully removed
1124
+	 * @throws EE_Error
1125
+	 */
1126
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
+	{
1128
+
1129
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
+			EE_Error::doing_it_wrong(
1131
+				'EEH_Line_Item::delete_items',
1132
+				esc_html__(
1133
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
+					'event_espresso'
1135
+				),
1136
+				'4.6.18'
1137
+			);
1138
+		}
1139
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
+
1141
+		// check if only a single line_item_id was passed
1142
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
+			// place single line_item_id in an array to appear as multiple line_item_ids
1144
+			$line_item_codes = array($line_item_codes);
1145
+		}
1146
+		$removals = 0;
1147
+		// cycle thru line_item_ids
1148
+		foreach ($line_item_codes as $line_item_id) {
1149
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1150
+		}
1151
+
1152
+		if ($removals > 0) {
1153
+			$total_line_item->recalculate_taxes_and_tax_total();
1154
+			return $removals;
1155
+		} else {
1156
+			return false;
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
+	 * tax and updates the total line item accordingly
1164
+	 *
1165
+	 * @param EE_Line_Item $total_line_item
1166
+	 * @param float        $amount
1167
+	 * @param string       $name
1168
+	 * @param string       $description
1169
+	 * @param string       $code
1170
+	 * @param boolean      $add_to_existing_line_item
1171
+	 *                          if true, and a duplicate line item with the same code is found,
1172
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
+	 * @return EE_Line_Item the new tax line item created
1174
+	 * @throws EE_Error
1175
+	 * @throws InvalidArgumentException
1176
+	 * @throws InvalidDataTypeException
1177
+	 * @throws InvalidInterfaceException
1178
+	 * @throws ReflectionException
1179
+	 */
1180
+	public static function set_total_tax_to(
1181
+		EE_Line_Item $total_line_item,
1182
+		$amount,
1183
+		$name = null,
1184
+		$description = null,
1185
+		$code = null,
1186
+		$add_to_existing_line_item = false
1187
+	) {
1188
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
+		$taxable_total = $total_line_item->taxable_total();
1190
+
1191
+		if ($add_to_existing_line_item) {
1192
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1193
+			EEM_Line_Item::instance()->delete(
1194
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
+			);
1196
+		} else {
1197
+			$new_tax = null;
1198
+			$tax_subtotal->delete_children_line_items();
1199
+		}
1200
+		if ($new_tax) {
1201
+			$new_tax->set_total($new_tax->total() + $amount);
1202
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
+		} else {
1204
+			// no existing tax item. Create it
1205
+			$new_tax = EE_Line_Item::new_instance(array(
1206
+				'TXN_ID'      => $total_line_item->TXN_ID(),
1207
+				'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
+				'LIN_desc'    => $description ?: '',
1209
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
+				'LIN_total'   => $amount,
1211
+				'LIN_parent'  => $tax_subtotal->ID(),
1212
+				'LIN_type'    => EEM_Line_Item::type_tax,
1213
+				'LIN_code'    => $code,
1214
+			));
1215
+		}
1216
+
1217
+		$new_tax = apply_filters(
1218
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
+			$new_tax,
1220
+			$total_line_item
1221
+		);
1222
+		$new_tax->save();
1223
+		$tax_subtotal->set_total($new_tax->total());
1224
+		$tax_subtotal->save();
1225
+		$total_line_item->recalculate_total_including_taxes();
1226
+		return $new_tax;
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Makes all the line items which are children of $line_item taxable (or not).
1232
+	 * Does NOT save the line items
1233
+	 *
1234
+	 * @param EE_Line_Item $line_item
1235
+	 * @param boolean      $taxable
1236
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
+	 * @throws EE_Error
1239
+	 */
1240
+	public static function set_line_items_taxable(
1241
+		EE_Line_Item $line_item,
1242
+		$taxable = true,
1243
+		$code_substring_for_whitelist = null
1244
+	) {
1245
+		$whitelisted = false;
1246
+		if ($code_substring_for_whitelist !== null) {
1247
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
+		}
1249
+		if (! $whitelisted && $line_item->is_line_item()) {
1250
+			$line_item->set_is_taxable($taxable);
1251
+		}
1252
+		foreach ($line_item->children() as $child_line_item) {
1253
+			EEH_Line_Item::set_line_items_taxable(
1254
+				$child_line_item,
1255
+				$taxable,
1256
+				$code_substring_for_whitelist
1257
+			);
1258
+		}
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Gets all descendants that are event subtotals
1264
+	 *
1265
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
+	 * @return EE_Line_Item[]
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
+	{
1272
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
+	}
1274
+
1275
+
1276
+	/**
1277
+	 * Gets all descendants subtotals that match the supplied object type
1278
+	 *
1279
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
+	 * @param string       $obj_type
1282
+	 * @return EE_Line_Item[]
1283
+	 * @throws EE_Error
1284
+	 */
1285
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
+	{
1287
+		return self::_get_descendants_by_type_and_object_type(
1288
+			$parent_line_item,
1289
+			EEM_Line_Item::type_sub_total,
1290
+			$obj_type
1291
+		);
1292
+	}
1293
+
1294
+
1295
+	/**
1296
+	 * Gets all descendants that are tickets
1297
+	 *
1298
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
+	 * @return EE_Line_Item[]
1301
+	 * @throws EE_Error
1302
+	 */
1303
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
+	{
1305
+		return self::get_line_items_of_object_type(
1306
+			$parent_line_item,
1307
+			EEM_Line_Item::OBJ_TYPE_TICKET
1308
+		);
1309
+	}
1310
+
1311
+
1312
+	/**
1313
+	 * Gets all descendants subtotals that match the supplied object type
1314
+	 *
1315
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
+	 * @param string       $obj_type
1318
+	 * @return EE_Line_Item[]
1319
+	 * @throws EE_Error
1320
+	 */
1321
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
+	{
1323
+		return self::_get_descendants_by_type_and_object_type(
1324
+			$parent_line_item,
1325
+			EEM_Line_Item::type_line_item,
1326
+			$obj_type
1327
+		);
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
+	 *
1334
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1335
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
+	 * @return EE_Line_Item[]
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
+	{
1341
+		return EEH_Line_Item::get_descendants_of_type(
1342
+			$parent_line_item,
1343
+			EEM_Line_Item::type_tax
1344
+		);
1345
+	}
1346
+
1347
+
1348
+	/**
1349
+	 * Gets all the real items purchased which are children of this item
1350
+	 *
1351
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1352
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
+	 * @return EE_Line_Item[]
1354
+	 * @throws EE_Error
1355
+	 */
1356
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
+	{
1358
+		return EEH_Line_Item::get_descendants_of_type(
1359
+			$parent_line_item,
1360
+			EEM_Line_Item::type_line_item
1361
+		);
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * Gets all descendants of supplied line item that match the supplied line item type
1367
+	 *
1368
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
+	 * @return EE_Line_Item[]
1372
+	 * @throws EE_Error
1373
+	 */
1374
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
+	{
1376
+		return self::_get_descendants_by_type_and_object_type(
1377
+			$parent_line_item,
1378
+			$line_item_type,
1379
+			null
1380
+		);
1381
+	}
1382
+
1383
+
1384
+	/**
1385
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
+	 * as well
1387
+	 *
1388
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
+	 * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
+	 *                                        searching
1392
+	 * @return EE_Line_Item[]
1393
+	 * @throws EE_Error
1394
+	 */
1395
+	protected static function _get_descendants_by_type_and_object_type(
1396
+		EE_Line_Item $parent_line_item,
1397
+		$line_item_type,
1398
+		$obj_type = null
1399
+	) {
1400
+		$objects = array();
1401
+		foreach ($parent_line_item->children() as $child_line_item) {
1402
+			if ($child_line_item instanceof EE_Line_Item) {
1403
+				if (
1404
+					$child_line_item->type() === $line_item_type
1405
+					&& (
1406
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
+					)
1408
+				) {
1409
+					$objects[] = $child_line_item;
1410
+				} else {
1411
+					// go-through-all-its children looking for more matches
1412
+					$objects = array_merge(
1413
+						$objects,
1414
+						self::_get_descendants_by_type_and_object_type(
1415
+							$child_line_item,
1416
+							$line_item_type,
1417
+							$obj_type
1418
+						)
1419
+					);
1420
+				}
1421
+			}
1422
+		}
1423
+		return $objects;
1424
+	}
1425
+
1426
+
1427
+	/**
1428
+	 * Gets all descendants subtotals that match the supplied object type
1429
+	 *
1430
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1431
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
+	 * @param string       $OBJ_type         object type (like Event)
1433
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1434
+	 * @return EE_Line_Item[]
1435
+	 * @throws EE_Error
1436
+	 */
1437
+	public static function get_line_items_by_object_type_and_IDs(
1438
+		EE_Line_Item $parent_line_item,
1439
+		$OBJ_type = '',
1440
+		$OBJ_IDs = array()
1441
+	) {
1442
+		return self::_get_descendants_by_object_type_and_object_ID(
1443
+			$parent_line_item,
1444
+			$OBJ_type,
1445
+			$OBJ_IDs
1446
+		);
1447
+	}
1448
+
1449
+
1450
+	/**
1451
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
+	 * as well
1453
+	 *
1454
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
+	 * @param string       $OBJ_type         object type (like Event)
1456
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1457
+	 * @return EE_Line_Item[]
1458
+	 * @throws EE_Error
1459
+	 */
1460
+	protected static function _get_descendants_by_object_type_and_object_ID(
1461
+		EE_Line_Item $parent_line_item,
1462
+		$OBJ_type,
1463
+		$OBJ_IDs
1464
+	) {
1465
+		$objects = array();
1466
+		foreach ($parent_line_item->children() as $child_line_item) {
1467
+			if ($child_line_item instanceof EE_Line_Item) {
1468
+				if (
1469
+					$child_line_item->OBJ_type() === $OBJ_type
1470
+					&& is_array($OBJ_IDs)
1471
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
+				) {
1473
+					$objects[] = $child_line_item;
1474
+				} else {
1475
+					// go-through-all-its children looking for more matches
1476
+					$objects = array_merge(
1477
+						$objects,
1478
+						self::_get_descendants_by_object_type_and_object_ID(
1479
+							$child_line_item,
1480
+							$OBJ_type,
1481
+							$OBJ_IDs
1482
+						)
1483
+					);
1484
+				}
1485
+			}
1486
+		}
1487
+		return $objects;
1488
+	}
1489
+
1490
+
1491
+	/**
1492
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1493
+	 * the specified type and returns it, else NULL
1494
+	 *
1495
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1498
+	 * @return EE_Line_Item
1499
+	 * @throws EE_Error
1500
+	 * @throws InvalidArgumentException
1501
+	 * @throws InvalidDataTypeException
1502
+	 * @throws InvalidInterfaceException
1503
+	 * @throws ReflectionException
1504
+	 */
1505
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
+	{
1507
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Uses a breadth-first-search in order to find the nearest descendant
1513
+	 * having the specified LIN_code and returns it, else NULL
1514
+	 *
1515
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
+	 * @param string       $code             any value used for LIN_code
1518
+	 * @return EE_Line_Item
1519
+	 * @throws EE_Error
1520
+	 * @throws InvalidArgumentException
1521
+	 * @throws InvalidDataTypeException
1522
+	 * @throws InvalidInterfaceException
1523
+	 * @throws ReflectionException
1524
+	 */
1525
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
+	{
1527
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
+	}
1529
+
1530
+
1531
+	/**
1532
+	 * Uses a breadth-first-search in order to find the nearest descendant
1533
+	 * having the specified LIN_code and returns it, else NULL
1534
+	 *
1535
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
+	 * @param string       $search_field     name of EE_Line_Item property
1537
+	 * @param string       $value            any value stored in $search_field
1538
+	 * @return EE_Line_Item
1539
+	 * @throws EE_Error
1540
+	 * @throws InvalidArgumentException
1541
+	 * @throws InvalidDataTypeException
1542
+	 * @throws InvalidInterfaceException
1543
+	 * @throws ReflectionException
1544
+	 */
1545
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
+	{
1547
+		foreach ($parent_line_item->children() as $child) {
1548
+			if ($child->get($search_field) == $value) {
1549
+				return $child;
1550
+			}
1551
+		}
1552
+		foreach ($parent_line_item->children() as $child) {
1553
+			$descendant_found = self::_get_nearest_descendant(
1554
+				$child,
1555
+				$search_field,
1556
+				$value
1557
+			);
1558
+			if ($descendant_found) {
1559
+				return $descendant_found;
1560
+			}
1561
+		}
1562
+		return null;
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
+	 * else recursively walks up the line item tree until a parent of type total is found,
1569
+	 *
1570
+	 * @param EE_Line_Item $line_item
1571
+	 * @return EE_Line_Item
1572
+	 * @throws EE_Error
1573
+	 * @throws ReflectionException
1574
+	 */
1575
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
+	{
1577
+		if ($line_item->is_total()) {
1578
+			return $line_item;
1579
+		}
1580
+		if ($line_item->TXN_ID()) {
1581
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1582
+			if ($total_line_item instanceof EE_Line_Item) {
1583
+				return $total_line_item;
1584
+			}
1585
+		} else {
1586
+			$line_item_parent = $line_item->parent();
1587
+			if ($line_item_parent instanceof EE_Line_Item) {
1588
+				if ($line_item_parent->is_total()) {
1589
+					return $line_item_parent;
1590
+				}
1591
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
+			}
1593
+		}
1594
+		throw new EE_Error(
1595
+			sprintf(
1596
+				esc_html__(
1597
+					'A valid grand total for line item %1$d was not found.',
1598
+					'event_espresso'
1599
+				),
1600
+				$line_item->ID()
1601
+			)
1602
+		);
1603
+	}
1604
+
1605
+
1606
+	/**
1607
+	 * Prints out a representation of the line item tree
1608
+	 *
1609
+	 * @param EE_Line_Item $line_item
1610
+	 * @param int          $indentation
1611
+	 * @return void
1612
+	 * @throws EE_Error
1613
+	 */
1614
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
+	{
1616
+		$new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
+		echo $new_line;
1618
+		if (! $indentation) {
1619
+			echo $new_line;
1620
+		}
1621
+		echo str_repeat('. ', $indentation);
1622
+		$breakdown = '';
1623
+		if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
+			if ($line_item->is_percent()) {
1625
+				$breakdown = "{$line_item->percent()}%";
1626
+			} else {
1627
+				$breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
+			}
1629
+		}
1630
+		echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1631
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
+		echo "\${$line_item->total()}";
1633
+		if ($breakdown) {
1634
+			echo " ( {$breakdown} )";
1635
+		}
1636
+		if ($line_item->is_taxable()) {
1637
+			echo '  * taxable';
1638
+		}
1639
+		if ($line_item->children()) {
1640
+			foreach ($line_item->children() as $child) {
1641
+				self::visualize($child, $indentation + 1);
1642
+			}
1643
+		}
1644
+		if (! $indentation) {
1645
+			echo $new_line . $new_line;
1646
+		}
1647
+	}
1648
+
1649
+
1650
+	/**
1651
+	 * Calculates the registration's final price, taking into account that they
1652
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
+	 * and receive a portion of any transaction-wide discounts.
1654
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
+	 * and brent's final price should be $5.50.
1658
+	 * In order to do this, we basically need to traverse the line item tree calculating
1659
+	 * the running totals (just as if we were recalculating the total), but when we identify
1660
+	 * regular line items, we need to keep track of their share of the grand total.
1661
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
+	 * when there are non-taxable items; otherwise they would be the same)
1664
+	 *
1665
+	 * @param EE_Line_Item $line_item
1666
+	 * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
+	 *                                                  can be included in price calculations at this moment
1668
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
+	 *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
+	 *                                                  the ticket IDs.
1671
+	 *                                                  Eg array(
1672
+	 *                                                      12 => 4.3
1673
+	 *                                                      23 => 8.0
1674
+	 *                                                      'total' => 16.6,
1675
+	 *                                                      'taxable' => array(
1676
+	 *                                                          12 => 10,
1677
+	 *                                                          23 => 4
1678
+	 *                                                      ).
1679
+	 *                                                  So to find which registrations have which final price, we need
1680
+	 *                                                  to find which line item is theirs, which can be done with
1681
+	 *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
+	 *                                                  $registration );`
1683
+	 * @throws EE_Error
1684
+	 * @throws InvalidArgumentException
1685
+	 * @throws InvalidDataTypeException
1686
+	 * @throws InvalidInterfaceException
1687
+	 * @throws ReflectionException
1688
+	 */
1689
+	public static function calculate_reg_final_prices_per_line_item(
1690
+		EE_Line_Item $line_item,
1691
+		$billable_ticket_quantities = array()
1692
+	) {
1693
+		$running_totals = [
1694
+			'total'   => 0,
1695
+			'taxable' => ['total' => 0]
1696
+		];
1697
+		foreach ($line_item->children() as $child_line_item) {
1698
+			switch ($child_line_item->type()) {
1699
+				case EEM_Line_Item::type_sub_total:
1700
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
+						$child_line_item,
1702
+						$billable_ticket_quantities
1703
+					);
1704
+					// combine arrays but preserve numeric keys
1705
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1707
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
+					break;
1709
+
1710
+				case EEM_Line_Item::type_tax_sub_total:
1711
+					// find how much the taxes percentage is
1712
+					if ($child_line_item->percent() !== 0) {
1713
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1714
+					} else {
1715
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
+					}
1717
+					// and apply to all the taxable totals, and add to the pretax totals
1718
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1719
+						// "total" and "taxable" array key is an exception
1720
+						if ($line_item_id === 'taxable') {
1721
+							continue;
1722
+						}
1723
+						$taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
+						$running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
+					}
1726
+					break;
1727
+
1728
+				case EEM_Line_Item::type_line_item:
1729
+					// ticket line items or ????
1730
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
+						// kk it's a ticket
1732
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1733
+							// huh? that shouldn't happen.
1734
+							$running_totals['total'] += $child_line_item->total();
1735
+						} else {
1736
+							// its not in our running totals yet. great.
1737
+							if ($child_line_item->is_taxable()) {
1738
+								$taxable_amount = $child_line_item->unit_price();
1739
+							} else {
1740
+								$taxable_amount = 0;
1741
+							}
1742
+							// are we only calculating totals for some tickets?
1743
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
+								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
+								$running_totals[ $child_line_item->ID() ] = $quantity
1746
+									? $child_line_item->unit_price()
1747
+									: 0;
1748
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
+									? $taxable_amount
1750
+									: 0;
1751
+							} else {
1752
+								$quantity = $child_line_item->quantity();
1753
+								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
+							}
1756
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
+						}
1759
+					} else {
1760
+						// it's some other type of item added to the cart
1761
+						// it should affect the running totals
1762
+						// basically we want to convert it into a PERCENT modifier. Because
1763
+						// more clearly affect all registration's final price equally
1764
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1765
+							? ($child_line_item->total() / $running_totals['total']) + 1
1766
+							: 1;
1767
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1768
+							// the "taxable" array key is an exception
1769
+							if ($line_item_id === 'taxable') {
1770
+								continue;
1771
+							}
1772
+							// update the running totals
1773
+							// yes this actually even works for the running grand total!
1774
+							$running_totals[ $line_item_id ] =
1775
+								$line_items_percent_of_running_total * $this_running_total;
1776
+
1777
+							if ($child_line_item->is_taxable()) {
1778
+								$running_totals['taxable'][ $line_item_id ] =
1779
+									$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
+							}
1781
+						}
1782
+					}
1783
+					break;
1784
+			}
1785
+		}
1786
+		return $running_totals;
1787
+	}
1788
+
1789
+
1790
+	/**
1791
+	 * @param EE_Line_Item $total_line_item
1792
+	 * @param EE_Line_Item $ticket_line_item
1793
+	 * @return float | null
1794
+	 * @throws EE_Error
1795
+	 * @throws InvalidArgumentException
1796
+	 * @throws InvalidDataTypeException
1797
+	 * @throws InvalidInterfaceException
1798
+	 * @throws OutOfRangeException
1799
+	 * @throws ReflectionException
1800
+	 */
1801
+	public static function calculate_final_price_for_ticket_line_item(
1802
+		EE_Line_Item $total_line_item,
1803
+		EE_Line_Item $ticket_line_item
1804
+	) {
1805
+		static $final_prices_per_ticket_line_item = array();
1806
+		if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
+				$total_line_item
1809
+			);
1810
+		}
1811
+		// ok now find this new registration's final price
1812
+		if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
+			return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
+		}
1815
+		$message = sprintf(
1816
+			esc_html__(
1817
+				'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
+				'event_espresso'
1819
+			),
1820
+			$ticket_line_item->ID(),
1821
+			$total_line_item->ID()
1822
+		);
1823
+		if (WP_DEBUG) {
1824
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
+			throw new OutOfRangeException($message);
1826
+		}
1827
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
+		return null;
1829
+	}
1830
+
1831
+
1832
+	/**
1833
+	 * Creates a duplicate of the line item tree, except only includes billable items
1834
+	 * and the portion of line items attributed to billable things
1835
+	 *
1836
+	 * @param EE_Line_Item      $line_item
1837
+	 * @param EE_Registration[] $registrations
1838
+	 * @return EE_Line_Item
1839
+	 * @throws EE_Error
1840
+	 * @throws InvalidArgumentException
1841
+	 * @throws InvalidDataTypeException
1842
+	 * @throws InvalidInterfaceException
1843
+	 * @throws ReflectionException
1844
+	 */
1845
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
+	{
1847
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
+		foreach ($line_item->children() as $child_li) {
1849
+			$copy_li->add_child_line_item(
1850
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
+			);
1852
+		}
1853
+		// if this is the grand total line item, make sure the totals all add up
1854
+		// (we could have duplicated this logic AS we copied the line items, but
1855
+		// it seems DRYer this way)
1856
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
+			$copy_li->recalculate_total_including_taxes();
1858
+		}
1859
+		return $copy_li;
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * Creates a new, unsaved line item from $line_item that factors in the
1865
+	 * number of billable registrations on $registrations.
1866
+	 *
1867
+	 * @param EE_Line_Item      $line_item
1868
+	 * @param EE_Registration[] $registrations
1869
+	 * @return EE_Line_Item
1870
+	 * @throws EE_Error
1871
+	 * @throws InvalidArgumentException
1872
+	 * @throws InvalidDataTypeException
1873
+	 * @throws InvalidInterfaceException
1874
+	 * @throws ReflectionException
1875
+	 */
1876
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
+	{
1878
+		$new_li_fields = $line_item->model_field_array();
1879
+		if (
1880
+			$line_item->type() === EEM_Line_Item::type_line_item &&
1881
+			$line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
+		) {
1883
+			$count = 0;
1884
+			foreach ($registrations as $registration) {
1885
+				if (
1886
+					$line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
+					in_array(
1888
+						$registration->status_ID(),
1889
+						EEM_Registration::reg_statuses_that_allow_payment(),
1890
+						true
1891
+					)
1892
+				) {
1893
+					$count++;
1894
+				}
1895
+			}
1896
+			$new_li_fields['LIN_quantity'] = $count;
1897
+		}
1898
+		// don't set the total. We'll leave that up to the code that calculates it
1899
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
+		return EE_Line_Item::new_instance($new_li_fields);
1901
+	}
1902
+
1903
+
1904
+	/**
1905
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1906
+	 * are removed, and line items with a quantity of 0
1907
+	 *
1908
+	 * @param EE_Line_Item $line_item |null
1909
+	 * @return EE_Line_Item|null
1910
+	 * @throws EE_Error
1911
+	 * @throws InvalidArgumentException
1912
+	 * @throws InvalidDataTypeException
1913
+	 * @throws InvalidInterfaceException
1914
+	 * @throws ReflectionException
1915
+	 */
1916
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1917
+	{
1918
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
+		if ($copied_li === null) {
1920
+			return null;
1921
+		}
1922
+		// if this is an event subtotal, we want to only include it if it
1923
+		// has a non-zero total and at least one ticket line item child
1924
+		$ticket_children = 0;
1925
+		foreach ($line_item->children() as $child_li) {
1926
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
+			if ($child_li_copy !== null) {
1928
+				$copied_li->add_child_line_item($child_li_copy);
1929
+				if (
1930
+					$child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
+					$child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
+				) {
1933
+					$ticket_children++;
1934
+				}
1935
+			}
1936
+		}
1937
+		// if this is an event subtotal with NO ticket children
1938
+		// we basically want to ignore it
1939
+		if (
1940
+			$ticket_children === 0
1941
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1942
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
+			&& $line_item->total() === 0
1944
+		) {
1945
+			return null;
1946
+		}
1947
+		return $copied_li;
1948
+	}
1949
+
1950
+
1951
+	/**
1952
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1953
+	 * with a total of 0, or a subtotal of 0, returns null instead
1954
+	 *
1955
+	 * @param EE_Line_Item $line_item
1956
+	 * @return EE_Line_Item
1957
+	 * @throws EE_Error
1958
+	 * @throws InvalidArgumentException
1959
+	 * @throws InvalidDataTypeException
1960
+	 * @throws InvalidInterfaceException
1961
+	 * @throws ReflectionException
1962
+	 */
1963
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1964
+	{
1965
+		if (
1966
+			$line_item->type() === EEM_Line_Item::type_line_item
1967
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
+			&& $line_item->quantity() === 0
1969
+		) {
1970
+			return null;
1971
+		}
1972
+		$new_li_fields = $line_item->model_field_array();
1973
+		// don't set the total. We'll leave that up to the code that calculates it
1974
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
+		return EE_Line_Item::new_instance($new_li_fields);
1976
+	}
1977
+
1978
+
1979
+	/**
1980
+	 * Cycles through all of the ticket line items for the supplied total line item
1981
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
+	 *
1983
+	 * @param EE_Line_Item $total_line_item
1984
+	 * @since 4.9.79.p
1985
+	 * @throws EE_Error
1986
+	 * @throws InvalidArgumentException
1987
+	 * @throws InvalidDataTypeException
1988
+	 * @throws InvalidInterfaceException
1989
+	 * @throws ReflectionException
1990
+	 */
1991
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
+	{
1993
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
+		foreach ($ticket_line_items as $ticket_line_item) {
1995
+			if (
1996
+				$ticket_line_item instanceof EE_Line_Item
1997
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
+			) {
1999
+				$ticket = $ticket_line_item->ticket();
2000
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
+					$ticket_line_item->set_is_taxable($ticket->taxable());
2002
+					$ticket_line_item->save();
2003
+				}
2004
+			}
2005
+		}
2006
+	}
2007
+
2008
+
2009
+	/**
2010
+	 * @return EE_Line_Item[]
2011
+	 * @throws EE_Error
2012
+	 * @throws ReflectionException
2013
+	 * @since   $VID:$
2014
+	 */
2015
+	private static function getGlobalTaxes(): array
2016
+	{
2017
+		if (EEH_Line_Item::$global_taxes === null) {
2018
+
2019
+			/** @type EEM_Price $EEM_Price */
2020
+			$EEM_Price = EE_Registry::instance()->load_model('Price');
2021
+			// get array of taxes via Price Model
2022
+			EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
+			ksort(EEH_Line_Item::$global_taxes);
2024
+		}
2025
+		return EEH_Line_Item::$global_taxes;
2026
+	}
2027
+
2028
+
2029
+
2030
+	/**************************************** @DEPRECATED METHODS *************************************** */
2031
+	/**
2032
+	 * @deprecated
2033
+	 * @param EE_Line_Item $total_line_item
2034
+	 * @return EE_Line_Item
2035
+	 * @throws EE_Error
2036
+	 * @throws InvalidArgumentException
2037
+	 * @throws InvalidDataTypeException
2038
+	 * @throws InvalidInterfaceException
2039
+	 * @throws ReflectionException
2040
+	 */
2041
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
+	{
2043
+		EE_Error::doing_it_wrong(
2044
+			'EEH_Line_Item::get_items_subtotal()',
2045
+			sprintf(
2046
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
+				'EEH_Line_Item::get_pre_tax_subtotal()'
2048
+			),
2049
+			'4.6.0'
2050
+		);
2051
+		return self::get_pre_tax_subtotal($total_line_item);
2052
+	}
2053
+
2054
+
2055
+	/**
2056
+	 * @deprecated
2057
+	 * @param EE_Transaction $transaction
2058
+	 * @return EE_Line_Item
2059
+	 * @throws EE_Error
2060
+	 * @throws InvalidArgumentException
2061
+	 * @throws InvalidDataTypeException
2062
+	 * @throws InvalidInterfaceException
2063
+	 * @throws ReflectionException
2064
+	 */
2065
+	public static function create_default_total_line_item($transaction = null)
2066
+	{
2067
+		EE_Error::doing_it_wrong(
2068
+			'EEH_Line_Item::create_default_total_line_item()',
2069
+			sprintf(
2070
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
+				'EEH_Line_Item::create_total_line_item()'
2072
+			),
2073
+			'4.6.0'
2074
+		);
2075
+		return self::create_total_line_item($transaction);
2076
+	}
2077
+
2078
+
2079
+	/**
2080
+	 * @deprecated
2081
+	 * @param EE_Line_Item   $total_line_item
2082
+	 * @param EE_Transaction $transaction
2083
+	 * @return EE_Line_Item
2084
+	 * @throws EE_Error
2085
+	 * @throws InvalidArgumentException
2086
+	 * @throws InvalidDataTypeException
2087
+	 * @throws InvalidInterfaceException
2088
+	 * @throws ReflectionException
2089
+	 */
2090
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
+	{
2092
+		EE_Error::doing_it_wrong(
2093
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2094
+			sprintf(
2095
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2097
+			),
2098
+			'4.6.0'
2099
+		);
2100
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
+	}
2102
+
2103
+
2104
+	/**
2105
+	 * @deprecated
2106
+	 * @param EE_Line_Item   $total_line_item
2107
+	 * @param EE_Transaction $transaction
2108
+	 * @return EE_Line_Item
2109
+	 * @throws EE_Error
2110
+	 * @throws InvalidArgumentException
2111
+	 * @throws InvalidDataTypeException
2112
+	 * @throws InvalidInterfaceException
2113
+	 * @throws ReflectionException
2114
+	 */
2115
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
+	{
2117
+		EE_Error::doing_it_wrong(
2118
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2119
+			sprintf(
2120
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
+				'EEH_Line_Item::create_taxes_subtotal()'
2122
+			),
2123
+			'4.6.0'
2124
+		);
2125
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2126
+	}
2127
+
2128
+
2129
+	/**
2130
+	 * @deprecated
2131
+	 * @param EE_Line_Item   $total_line_item
2132
+	 * @param EE_Transaction $transaction
2133
+	 * @return EE_Line_Item
2134
+	 * @throws EE_Error
2135
+	 * @throws InvalidArgumentException
2136
+	 * @throws InvalidDataTypeException
2137
+	 * @throws InvalidInterfaceException
2138
+	 * @throws ReflectionException
2139
+	 */
2140
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
+	{
2142
+		EE_Error::doing_it_wrong(
2143
+			'EEH_Line_Item::create_default_event_subtotal()',
2144
+			sprintf(
2145
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
+				'EEH_Line_Item::create_event_subtotal()'
2147
+			),
2148
+			'4.6.0'
2149
+		);
2150
+		return self::create_event_subtotal($total_line_item, $transaction);
2151
+	}
2152 2152
 }
Please login to merge, or discard this patch.
Spacing   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -75,12 +75,12 @@  discard block
 block discarded – undo
75 75
                 'LIN_code'       => $code,
76 76
             ]
77 77
         );
78
-        $line_item      = apply_filters(
78
+        $line_item = apply_filters(
79 79
             'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80 80
             $line_item,
81 81
             $parent_line_item
82 82
         );
83
-        $added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
83
+        $added = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84 84
         return $return_item ? $line_item : $added;
85 85
     }
86 86
 
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
             'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134 134
             $line_item
135 135
         );
136
-        $added     = $parent_line_item->add_child_line_item($line_item, false);
136
+        $added = $parent_line_item->add_child_line_item($line_item, false);
137 137
         return $return_item ? $line_item : $added;
138 138
     }
139 139
 
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
         EE_Ticket $ticket,
161 161
         int $qty = 1
162 162
     ): ?EE_Line_Item {
163
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
163
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164 164
             throw new EE_Error(
165 165
                 sprintf(
166 166
                     esc_html__(
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
         // either increment the qty for an existing ticket
176 176
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177 177
         // or add a new one
178
-        if (! $line_item instanceof EE_Line_Item) {
178
+        if ( ! $line_item instanceof EE_Line_Item) {
179 179
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180 180
         }
181 181
         $total_line_item->recalculate_total_including_taxes();
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
      */
238 238
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239 239
     {
240
-        if (! $line_item->is_percent()) {
240
+        if ( ! $line_item->is_percent()) {
241 241
             $qty += $line_item->quantity();
242 242
             $line_item->set_quantity($qty);
243 243
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
      */
267 267
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268 268
     {
269
-        if (! $line_item->is_percent()) {
269
+        if ( ! $line_item->is_percent()) {
270 270
             $qty = $line_item->quantity() - $qty;
271 271
             $qty = max($qty, 0);
272 272
             $line_item->set_quantity($qty);
@@ -295,7 +295,7 @@  discard block
 block discarded – undo
295 295
      */
296 296
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297 297
     {
298
-        if (! $line_item->is_percent()) {
298
+        if ( ! $line_item->is_percent()) {
299 299
             $line_item->set_quantity($new_quantity);
300 300
             $line_item->set_total($line_item->unit_price() * $new_quantity);
301 301
             $line_item->save();
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
         // add $ticket to cart
337 337
         $line_item = EE_Line_Item::new_instance(array(
338 338
             'LIN_name'       => $ticket->name(),
339
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
339
+            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
340 340
             'LIN_unit_price' => $ticket->price(),
341 341
             'LIN_quantity'   => $qty,
342 342
             'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
             'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351 351
             $line_item
352 352
         );
353
-        if (!$line_item instanceof EE_Line_Item) {
353
+        if ( ! $line_item instanceof EE_Line_Item) {
354 354
             throw new DomainException(
355 355
                 esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356 356
             );
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
                         'event_espresso'
503 503
                     ),
504 504
                     $ticket_line_item->name(),
505
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
505
+                    current_time(get_option('date_format').' '.get_option('time_format'))
506 506
                 ),
507 507
                 'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508 508
                 'LIN_quantity'   => $qty,
@@ -565,7 +565,7 @@  discard block
 block discarded – undo
565 565
         );
566 566
         $cancellation_line_item = reset($cancellation_line_item);
567 567
         // verify that this ticket was indeed previously cancelled
568
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
568
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
569 569
             return false;
570 570
         }
571 571
         if ($cancellation_line_item->quantity() > $qty) {
@@ -771,7 +771,7 @@  discard block
 block discarded – undo
771 771
             'LIN_code'  => 'taxes',
772 772
             'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773 773
             'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
-            'LIN_order' => 1000,// this should always come last
774
+            'LIN_order' => 1000, // this should always come last
775 775
         ));
776 776
         $tax_line_item = apply_filters(
777 777
             'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
@@ -827,7 +827,7 @@  discard block
 block discarded – undo
827 827
      */
828 828
     public static function get_event_code($event)
829 829
     {
830
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
830
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
831 831
     }
832 832
 
833 833
 
@@ -876,7 +876,7 @@  discard block
 block discarded – undo
876 876
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877 877
     {
878 878
         $first_datetime = $ticket->first_datetime();
879
-        if (! $first_datetime instanceof EE_Datetime) {
879
+        if ( ! $first_datetime instanceof EE_Datetime) {
880 880
             throw new EE_Error(
881 881
                 sprintf(
882 882
                     esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
@@ -885,7 +885,7 @@  discard block
 block discarded – undo
885 885
             );
886 886
         }
887 887
         $event = $first_datetime->event();
888
-        if (! $event instanceof EE_Event) {
888
+        if ( ! $event instanceof EE_Event) {
889 889
             throw new EE_Error(
890 890
                 sprintf(
891 891
                     esc_html__(
@@ -897,7 +897,7 @@  discard block
 block discarded – undo
897 897
             );
898 898
         }
899 899
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
-        if (! $events_sub_total instanceof EE_Line_Item) {
900
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
901 901
             throw new EE_Error(
902 902
                 sprintf(
903 903
                     esc_html__(
@@ -933,7 +933,7 @@  discard block
 block discarded – undo
933 933
         $found = false;
934 934
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935 935
             // default event subtotal, we should only ever find this the first time this method is called
936
-            if (! $event_line_item->OBJ_ID()) {
936
+            if ( ! $event_line_item->OBJ_ID()) {
937 937
                 // let's use this! but first... set the event details
938 938
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939 939
                 $found = true;
@@ -945,7 +945,7 @@  discard block
 block discarded – undo
945 945
                 break;
946 946
             }
947 947
         }
948
-        if (! $found) {
948
+        if ( ! $found) {
949 949
             // there is no event sub-total yet, so add it
950 950
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951 951
             // create a new "event" subtotal below that
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
                             break;
1026 1026
                         }
1027 1027
                     }
1028
-                    if (! $found) {
1028
+                    if ( ! $found) {
1029 1029
                         // add a new line item for this global tax
1030 1030
                         $tax_line_item = apply_filters(
1031 1031
                             'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
@@ -1072,7 +1072,7 @@  discard block
 block discarded – undo
1072 1072
     public static function ensure_taxes_applied($total_line_item)
1073 1073
     {
1074 1074
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
-        if (! $taxes_subtotal->children()) {
1075
+        if ( ! $taxes_subtotal->children()) {
1076 1076
             self::apply_taxes($total_line_item);
1077 1077
         }
1078 1078
         return $taxes_subtotal->total();
@@ -1139,7 +1139,7 @@  discard block
 block discarded – undo
1139 1139
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140 1140
 
1141 1141
         // check if only a single line_item_id was passed
1142
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1142
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143 1143
             // place single line_item_id in an array to appear as multiple line_item_ids
1144 1144
             $line_item_codes = array($line_item_codes);
1145 1145
         }
@@ -1246,7 +1246,7 @@  discard block
 block discarded – undo
1246 1246
         if ($code_substring_for_whitelist !== null) {
1247 1247
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248 1248
         }
1249
-        if (! $whitelisted && $line_item->is_line_item()) {
1249
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1250 1250
             $line_item->set_is_taxable($taxable);
1251 1251
         }
1252 1252
         foreach ($line_item->children() as $child_line_item) {
@@ -1615,7 +1615,7 @@  discard block
 block discarded – undo
1615 1615
     {
1616 1616
         $new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617 1617
         echo $new_line;
1618
-        if (! $indentation) {
1618
+        if ( ! $indentation) {
1619 1619
             echo $new_line;
1620 1620
         }
1621 1621
         echo str_repeat('. ', $indentation);
@@ -1641,8 +1641,8 @@  discard block
 block discarded – undo
1641 1641
                 self::visualize($child, $indentation + 1);
1642 1642
             }
1643 1643
         }
1644
-        if (! $indentation) {
1645
-            echo $new_line . $new_line;
1644
+        if ( ! $indentation) {
1645
+            echo $new_line.$new_line;
1646 1646
         }
1647 1647
     }
1648 1648
 
@@ -1720,8 +1720,8 @@  discard block
 block discarded – undo
1720 1720
                         if ($line_item_id === 'taxable') {
1721 1721
                             continue;
1722 1722
                         }
1723
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1723
+                        $taxable_total = $running_totals['taxable'][$line_item_id];
1724
+                        $running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1725 1725
                     }
1726 1726
                     break;
1727 1727
 
@@ -1729,7 +1729,7 @@  discard block
 block discarded – undo
1729 1729
                     // ticket line items or ????
1730 1730
                     if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731 1731
                         // kk it's a ticket
1732
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1732
+                        if (isset($running_totals[$child_line_item->ID()])) {
1733 1733
                             // huh? that shouldn't happen.
1734 1734
                             $running_totals['total'] += $child_line_item->total();
1735 1735
                         } else {
@@ -1740,18 +1740,18 @@  discard block
 block discarded – undo
1740 1740
                                 $taxable_amount = 0;
1741 1741
                             }
1742 1742
                             // are we only calculating totals for some tickets?
1743
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1743
+                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1744
+                                $quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1745
+                                $running_totals[$child_line_item->ID()] = $quantity
1746 1746
                                     ? $child_line_item->unit_price()
1747 1747
                                     : 0;
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1748
+                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1749 1749
                                     ? $taxable_amount
1750 1750
                                     : 0;
1751 1751
                             } else {
1752 1752
                                 $quantity = $child_line_item->quantity();
1753
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1753
+                                $running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1754
+                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1755 1755
                             }
1756 1756
                             $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757 1757
                             $running_totals['total'] += $child_line_item->unit_price() * $quantity;
@@ -1771,12 +1771,12 @@  discard block
 block discarded – undo
1771 1771
                             }
1772 1772
                             // update the running totals
1773 1773
                             // yes this actually even works for the running grand total!
1774
-                            $running_totals[ $line_item_id ] =
1774
+                            $running_totals[$line_item_id] =
1775 1775
                                 $line_items_percent_of_running_total * $this_running_total;
1776 1776
 
1777 1777
                             if ($child_line_item->is_taxable()) {
1778
-                                $running_totals['taxable'][ $line_item_id ] =
1779
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1778
+                                $running_totals['taxable'][$line_item_id] =
1779
+                                    $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1780 1780
                             }
1781 1781
                         }
1782 1782
                     }
@@ -1803,14 +1803,14 @@  discard block
 block discarded – undo
1803 1803
         EE_Line_Item $ticket_line_item
1804 1804
     ) {
1805 1805
         static $final_prices_per_ticket_line_item = array();
1806
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1806
+        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[$total_line_item->ID()])) {
1807
+            $final_prices_per_ticket_line_item[$total_line_item->ID()] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808 1808
                 $total_line_item
1809 1809
             );
1810 1810
         }
1811 1811
         // ok now find this new registration's final price
1812
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1812
+        if (isset($final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()])) {
1813
+            return $final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()];
1814 1814
         }
1815 1815
         $message = sprintf(
1816 1816
             esc_html__(
@@ -1821,7 +1821,7 @@  discard block
 block discarded – undo
1821 1821
             $total_line_item->ID()
1822 1822
         );
1823 1823
         if (WP_DEBUG) {
1824
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1824
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1825 1825
             throw new OutOfRangeException($message);
1826 1826
         }
1827 1827
         EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/EE_Capabilities.core.php 2 patches
Indentation   +1396 added lines, -1396 removed lines patch added patch discarded remove patch
@@ -13,991 +13,991 @@  discard block
 block discarded – undo
13 13
  */
14 14
 final class EE_Capabilities extends EE_Base
15 15
 {
16
-    const ROLE_ADMINISTRATOR        = 'administrator';
17
-
18
-    const ROLE_EVENTS_ADMINISTRATOR = 'ee_events_administrator';
19
-
20
-
21
-    /**
22
-     * the name of the wp option used to store caps previously initialized
23
-     */
24
-    const option_name = 'ee_caps_initialized';
25
-
26
-    /**
27
-     * instance of EE_Capabilities object
28
-     *
29
-     * @var EE_Capabilities
30
-     */
31
-    private static $_instance;
32
-
33
-
34
-    /**
35
-     * This is a map of caps that correspond to a default WP_Role.
36
-     * Array is indexed by Role and values are ee capabilities.
37
-     *
38
-     * @since 4.5.0
39
-     *
40
-     * @var array
41
-     */
42
-    private $capabilities_map = [];
43
-
44
-    /**
45
-     * This used to hold an array of EE_Meta_Capability_Map objects
46
-     * that define the granular capabilities mapped to for a user depending on context.
47
-     *
48
-     * @var EE_Meta_Capability_Map[]
49
-     */
50
-    private $_meta_caps = [];
51
-
52
-    /**
53
-     * The internal $capabilities_map needs to be initialized before it can be used.
54
-     * This flag tracks whether that has happened or not.
55
-     * But for this to work, we need three states to indicate:
56
-     *      initialization has not occurred at all
57
-     *      initialization has started but is not complete
58
-     *      initialization is complete
59
-     * The reason this is needed is because the addCaps() method
60
-     * normally requires the $capabilities_map to be initialized,
61
-     * but is also used during the initialization process.
62
-     * So:
63
-     *      If initialized === null, init_caps() will be called before any other methods will run.
64
-     *      If initialized === false, then init_caps() is in the process of running it's logic.
65
-     *      If initialized === true, then init_caps() has completed the initialization process.
66
-     *
67
-     * @var boolean|null $initialized
68
-     */
69
-    private $initialized;
70
-
71
-    /**
72
-     * @var boolean $reset
73
-     */
74
-    private $reset = false;
75
-
76
-
77
-    /**
78
-     * singleton method used to instantiate class object
79
-     *
80
-     * @return EE_Capabilities
81
-     * @since 4.5.0
82
-     *
83
-     */
84
-    public static function instance(): EE_Capabilities
85
-    {
86
-        // check if instantiated, and if not do so.
87
-        if (! self::$_instance instanceof EE_Capabilities) {
88
-            self::$_instance = new self();
89
-        }
90
-        return self::$_instance;
91
-    }
92
-
93
-
94
-    /**
95
-     * private constructor
96
-     *
97
-     * @since 4.5.0
98
-     */
99
-    private function __construct()
100
-    {
101
-    }
102
-
103
-
104
-    /**
105
-     * This delays the initialization of the capabilities class until EE_System core is loaded and ready.
106
-     *
107
-     * @param bool $reset allows for resetting the default capabilities saved on roles.  Note that this doesn't
108
-     *                    actually REMOVE any capabilities from existing roles, it just resaves defaults roles and
109
-     *                    ensures that they are up to date.
110
-     *
111
-     * @since 4.5.0
112
-     * @return bool
113
-     * @throws EE_Error
114
-     */
115
-    public function init_caps(?bool $reset = false): bool
116
-    {
117
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
118
-            return false;
119
-        }
120
-        $this->reset = filter_var($reset, FILTER_VALIDATE_BOOLEAN);
121
-        // if reset, then completely delete the cache option and clear the $capabilities_map property.
122
-        if ($this->reset) {
123
-            $this->initialized      = null;
124
-            $this->capabilities_map = [];
125
-            delete_option(self::option_name);
126
-        }
127
-        if ($this->initialized === null) {
128
-            $this->initialized = false;
129
-            do_action(
130
-                'AHEE__EE_Capabilities__init_caps__before_initialization',
131
-                $this->reset
132
-            );
133
-            $this->addCaps($this->_init_caps_map());
134
-            $this->_set_meta_caps();
135
-            do_action(
136
-                'AHEE__EE_Capabilities__init_caps__after_initialization',
137
-                $this->capabilities_map
138
-            );
139
-            $this->initialized = true;
140
-        }
141
-        // reset $this->reset so that it's not stuck on true if init_caps() gets called again
142
-        $this->reset = false;
143
-        return true;
144
-    }
145
-
146
-
147
-    /**
148
-     * This sets the meta caps property.
149
-     *
150
-     * @return void
151
-     * @throws EE_Error
152
-     * @since 4.5.0
153
-     */
154
-    private function _set_meta_caps()
155
-    {
156
-        // get default meta caps and filter the returned array
157
-        $this->_meta_caps = apply_filters(
158
-            'FHEE__EE_Capabilities___set_meta_caps__meta_caps',
159
-            $this->_get_default_meta_caps_array()
160
-        );
161
-        // add filter for map_meta_caps but only if models can query.
162
-        if (! has_filter('map_meta_cap', [$this, 'map_meta_caps'])) {
163
-            add_filter('map_meta_cap', [$this, 'map_meta_caps'], 10, 4);
164
-        }
165
-    }
166
-
167
-
168
-    /**
169
-     * This builds and returns the default meta_caps array only once.
170
-     *
171
-     * @return array
172
-     * @throws EE_Error
173
-     * @since  4.8.28.rc.012
174
-     */
175
-    private function _get_default_meta_caps_array(): array
176
-    {
177
-        static $default_meta_caps = [];
178
-        // make sure we're only ever initializing the default _meta_caps array once if it's empty.
179
-        if (empty($default_meta_caps)) {
180
-            $default_meta_caps = [
181
-                // edits
182
-                new EE_Meta_Capability_Map_Edit(
183
-                    'ee_edit_event',
184
-                    ['Event', 'ee_edit_published_events', 'ee_edit_others_events', 'ee_edit_private_events']
185
-                ),
186
-                new EE_Meta_Capability_Map_Edit(
187
-                    'ee_edit_venue',
188
-                    ['Venue', 'ee_edit_published_venues', 'ee_edit_others_venues', 'ee_edit_private_venues']
189
-                ),
190
-                new EE_Meta_Capability_Map_Edit(
191
-                    'ee_edit_registration',
192
-                    ['Registration', '', 'ee_edit_others_registrations', '']
193
-                ),
194
-                new EE_Meta_Capability_Map_Edit(
195
-                    'ee_edit_checkin',
196
-                    ['Registration', '', 'ee_edit_others_checkins', '']
197
-                ),
198
-                new EE_Meta_Capability_Map_Messages_Cap(
199
-                    'ee_edit_message',
200
-                    ['Message_Template_Group', '', 'ee_edit_others_messages', 'ee_edit_global_messages']
201
-                ),
202
-                new EE_Meta_Capability_Map_Edit(
203
-                    'ee_edit_default_ticket',
204
-                    ['Ticket', '', 'ee_edit_others_default_tickets', '']
205
-                ),
206
-                new EE_Meta_Capability_Map_Registration_Form_Cap(
207
-                    'ee_edit_question',
208
-                    ['Question', '', '', 'ee_edit_system_questions']
209
-                ),
210
-                new EE_Meta_Capability_Map_Registration_Form_Cap(
211
-                    'ee_edit_question_group',
212
-                    ['Question_Group', '', '', 'ee_edit_system_question_groups']
213
-                ),
214
-                new EE_Meta_Capability_Map_Edit(
215
-                    'ee_edit_payment_method',
216
-                    ['Payment_Method', '', 'ee_edit_others_payment_methods', '']
217
-                ),
218
-                // reads
219
-                new EE_Meta_Capability_Map_Read(
220
-                    'ee_read_event',
221
-                    ['Event', '', 'ee_read_others_events', 'ee_read_private_events']
222
-                ),
223
-                new EE_Meta_Capability_Map_Read(
224
-                    'ee_read_venue',
225
-                    ['Venue', '', 'ee_read_others_venues', 'ee_read_private_venues']
226
-                ),
227
-                new EE_Meta_Capability_Map_Read(
228
-                    'ee_read_registration',
229
-                    ['Registration', '', 'ee_read_others_registrations', '']
230
-                ),
231
-                new EE_Meta_Capability_Map_Read(
232
-                    'ee_read_checkin',
233
-                    ['Registration', '', 'ee_read_others_checkins', '']
234
-                ),
235
-                new EE_Meta_Capability_Map_Messages_Cap(
236
-                    'ee_read_message',
237
-                    ['Message_Template_Group', '', 'ee_read_others_messages', 'ee_read_global_messages']
238
-                ),
239
-                new EE_Meta_Capability_Map_Read(
240
-                    'ee_read_default_ticket',
241
-                    ['Ticket', '', 'ee_read_others_default_tickets', '']
242
-                ),
243
-                new EE_Meta_Capability_Map_Read(
244
-                    'ee_read_payment_method',
245
-                    ['Payment_Method', '', 'ee_read_others_payment_methods', '']
246
-                ),
247
-                // deletes
248
-                new EE_Meta_Capability_Map_Delete(
249
-                    'ee_delete_event',
250
-                    [
251
-                        'Event',
252
-                        'ee_delete_published_events',
253
-                        'ee_delete_others_events',
254
-                        'ee_delete_private_events',
255
-                    ]
256
-                ),
257
-                new EE_Meta_Capability_Map_Delete(
258
-                    'ee_delete_venue',
259
-                    [
260
-                        'Venue',
261
-                        'ee_delete_published_venues',
262
-                        'ee_delete_others_venues',
263
-                        'ee_delete_private_venues',
264
-                    ]
265
-                ),
266
-                new EE_Meta_Capability_Map_Delete(
267
-                    'ee_delete_registration',
268
-                    ['Registration', '', 'ee_delete_others_registrations', '']
269
-                ),
270
-                new EE_Meta_Capability_Map_Delete(
271
-                    'ee_delete_checkin',
272
-                    ['Registration', '', 'ee_delete_others_checkins', '']
273
-                ),
274
-                new EE_Meta_Capability_Map_Messages_Cap(
275
-                    'ee_delete_message',
276
-                    ['Message_Template_Group', '', 'ee_delete_others_messages', 'ee_delete_global_messages']
277
-                ),
278
-                new EE_Meta_Capability_Map_Delete(
279
-                    'ee_delete_default_ticket',
280
-                    ['Ticket', '', 'ee_delete_others_default_tickets', '']
281
-                ),
282
-                new EE_Meta_Capability_Map_Registration_Form_Cap(
283
-                    'ee_delete_question',
284
-                    ['Question', '', '', 'delete_system_questions']
285
-                ),
286
-                new EE_Meta_Capability_Map_Registration_Form_Cap(
287
-                    'ee_delete_question_group',
288
-                    ['Question_Group', '', '', 'delete_system_question_groups']
289
-                ),
290
-                new EE_Meta_Capability_Map_Delete(
291
-                    'ee_delete_payment_method',
292
-                    ['Payment_Method', '', 'ee_delete_others_payment_methods', '']
293
-                ),
294
-            ];
295
-        }
296
-        return $default_meta_caps;
297
-    }
298
-
299
-
300
-    /**
301
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
302
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
303
-     *
304
-     * The actual logic is carried out by implementer classes in their definition of _map_meta_caps.
305
-     *
306
-     * @param array  $caps    actual users capabilities
307
-     * @param string $cap     initial capability name that is being checked (the "map" key)
308
-     * @param int    $user_id The user id
309
-     * @param array  $args    Adds context to the cap. Typically the object ID.
310
-     * @return array actual users capabilities
311
-     * @throws EE_Error
312
-     * @throws ReflectionException
313
-     * @since 4.5.0
314
-     * @see   wp-includes/capabilities.php
315
-     *
316
-     */
317
-    public function map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
318
-    {
319
-        if (did_action('AHEE__EE_System__load_espresso_addons__complete')) {
320
-            // loop through our _meta_caps array
321
-            foreach ($this->_meta_caps as $meta_map) {
322
-                if (! $meta_map instanceof EE_Meta_Capability_Map) {
323
-                    continue;
324
-                }
325
-                // don't load models if there is no object ID in the args
326
-                if (! empty($args[0])) {
327
-                    $meta_map->ensure_is_model();
328
-                }
329
-                $caps = $meta_map->map_meta_caps($caps, $cap, $user_id, $args);
330
-            }
331
-        }
332
-        return $caps;
333
-    }
334
-
335
-
336
-    /**
337
-     * This sets up and returns the initial capabilities map for Event Espresso
338
-     * Note this array is filtered.
339
-     * It is assumed that all available EE capabilities are assigned to the administrator role.
340
-     *
341
-     * @return array
342
-     * @since 4.5.0
343
-     *
344
-     */
345
-    private function _init_caps_map(): array
346
-    {
347
-        return apply_filters(
348
-            'FHEE__EE_Capabilities__init_caps_map__caps',
349
-            [
350
-                EE_Capabilities::ROLE_ADMINISTRATOR        => [
351
-                    // basic access
352
-                    'ee_read_ee',
353
-                    // gateways
354
-                    /**
355
-                     * note that with payment method capabilities, although we've implemented
356
-                     * capability mapping which will be used for accessing payment methods owned by
357
-                     * other users.  This is not fully implemented yet in the payment method ui.
358
-                     * Currently only the "plural" caps are in active use.
359
-                     * (Specific payment method caps are in use as well).
360
-                     **/
361
-                    'ee_manage_gateways',
362
-                    'ee_read_payment_methods',
363
-                    'ee_read_others_payment_methods',
364
-                    'ee_edit_payment_methods',
365
-                    'ee_edit_others_payment_methods',
366
-                    'ee_delete_payment_methods',
367
-                    // events
368
-                    'ee_publish_events',
369
-                    'ee_read_private_events',
370
-                    'ee_read_others_events',
371
-                    'ee_read_events',
372
-                    'ee_edit_events',
373
-                    'ee_edit_published_events',
374
-                    'ee_edit_others_events',
375
-                    'ee_edit_private_events',
376
-                    'ee_delete_published_events',
377
-                    'ee_delete_private_events',
378
-                    'ee_delete_events',
379
-                    'ee_delete_others_events',
380
-                    // event categories
381
-                    'ee_manage_event_categories',
382
-                    'ee_edit_event_category',
383
-                    'ee_delete_event_category',
384
-                    'ee_assign_event_category',
385
-                    // venues
386
-                    'ee_publish_venues',
387
-                    'ee_read_venues',
388
-                    'ee_read_others_venues',
389
-                    'ee_read_private_venues',
390
-                    'ee_edit_venues',
391
-                    'ee_edit_others_venues',
392
-                    'ee_edit_published_venues',
393
-                    'ee_edit_private_venues',
394
-                    'ee_delete_venues',
395
-                    'ee_delete_others_venues',
396
-                    'ee_delete_private_venues',
397
-                    'ee_delete_published_venues',
398
-                    // venue categories
399
-                    'ee_manage_venue_categories',
400
-                    'ee_edit_venue_category',
401
-                    'ee_delete_venue_category',
402
-                    'ee_assign_venue_category',
403
-                    // contacts
404
-                    'ee_read_contacts',
405
-                    'ee_edit_contacts',
406
-                    'ee_delete_contacts',
407
-                    // registrations
408
-                    'ee_read_registrations',
409
-                    'ee_read_others_registrations',
410
-                    'ee_edit_registrations',
411
-                    'ee_edit_others_registrations',
412
-                    'ee_delete_registrations',
413
-                    'ee_delete_others_registrations',
414
-                    // checkins
415
-                    'ee_read_others_checkins',
416
-                    'ee_read_checkins',
417
-                    'ee_edit_checkins',
418
-                    'ee_edit_others_checkins',
419
-                    'ee_delete_checkins',
420
-                    'ee_delete_others_checkins',
421
-                    // transactions && payments
422
-                    'ee_read_transaction',
423
-                    'ee_read_transactions',
424
-                    'ee_edit_payments',
425
-                    'ee_delete_payments',
426
-                    // messages
427
-                    'ee_read_messages',
428
-                    'ee_read_others_messages',
429
-                    'ee_read_global_messages',
430
-                    'ee_edit_global_messages',
431
-                    'ee_edit_messages',
432
-                    'ee_edit_others_messages',
433
-                    'ee_delete_messages',
434
-                    'ee_delete_others_messages',
435
-                    'ee_delete_global_messages',
436
-                    'ee_send_message',
437
-                    // tickets
438
-                    'ee_read_default_tickets',
439
-                    'ee_read_others_default_tickets',
440
-                    'ee_edit_default_tickets',
441
-                    'ee_edit_others_default_tickets',
442
-                    'ee_delete_default_tickets',
443
-                    'ee_delete_others_default_tickets',
444
-                    // prices
445
-                    'ee_edit_default_price',
446
-                    'ee_edit_default_prices',
447
-                    'ee_delete_default_price',
448
-                    'ee_delete_default_prices',
449
-                    'ee_edit_default_price_type',
450
-                    'ee_edit_default_price_types',
451
-                    'ee_delete_default_price_type',
452
-                    'ee_delete_default_price_types',
453
-                    'ee_read_default_prices',
454
-                    'ee_read_default_price_types',
455
-                    // registration form
456
-                    'ee_edit_questions',
457
-                    'ee_edit_system_questions',
458
-                    'ee_read_questions',
459
-                    'ee_delete_questions',
460
-                    'ee_edit_question_groups',
461
-                    'ee_read_question_groups',
462
-                    'ee_edit_system_question_groups',
463
-                    'ee_delete_question_groups',
464
-                    // event_type taxonomy
465
-                    'ee_assign_event_type',
466
-                    'ee_manage_event_types',
467
-                    'ee_edit_event_type',
468
-                    'ee_delete_event_type',
469
-                ],
470
-                EE_Capabilities::ROLE_EVENTS_ADMINISTRATOR => [
471
-                    // core wp caps
472
-                    'read',
473
-                    'read_private_pages',
474
-                    'read_private_posts',
475
-                    'edit_users',
476
-                    'edit_posts',
477
-                    'edit_pages',
478
-                    'edit_published_posts',
479
-                    'edit_published_pages',
480
-                    'edit_private_pages',
481
-                    'edit_private_posts',
482
-                    'edit_others_posts',
483
-                    'edit_others_pages',
484
-                    'publish_posts',
485
-                    'publish_pages',
486
-                    'delete_posts',
487
-                    'delete_pages',
488
-                    'delete_private_pages',
489
-                    'delete_private_posts',
490
-                    'delete_published_pages',
491
-                    'delete_published_posts',
492
-                    'delete_others_posts',
493
-                    'delete_others_pages',
494
-                    'manage_categories',
495
-                    'manage_links',
496
-                    'moderate_comments',
497
-                    'unfiltered_html',
498
-                    'upload_files',
499
-                    'export',
500
-                    'import',
501
-                    'list_users',
502
-                    'level_1', // required if user with this role shows up in author dropdowns
503
-                    // basic ee access
504
-                    'ee_read_ee',
505
-                    // events
506
-                    'ee_publish_events',
507
-                    'ee_read_private_events',
508
-                    'ee_read_others_events',
509
-                    'ee_read_event',
510
-                    'ee_read_events',
511
-                    'ee_edit_event',
512
-                    'ee_edit_events',
513
-                    'ee_edit_published_events',
514
-                    'ee_edit_others_events',
515
-                    'ee_edit_private_events',
516
-                    'ee_delete_published_events',
517
-                    'ee_delete_private_events',
518
-                    'ee_delete_event',
519
-                    'ee_delete_events',
520
-                    'ee_delete_others_events',
521
-                    // event categories
522
-                    'ee_manage_event_categories',
523
-                    'ee_edit_event_category',
524
-                    'ee_delete_event_category',
525
-                    'ee_assign_event_category',
526
-                    // venues
527
-                    'ee_publish_venues',
528
-                    'ee_read_venue',
529
-                    'ee_read_venues',
530
-                    'ee_read_others_venues',
531
-                    'ee_read_private_venues',
532
-                    'ee_edit_venue',
533
-                    'ee_edit_venues',
534
-                    'ee_edit_others_venues',
535
-                    'ee_edit_published_venues',
536
-                    'ee_edit_private_venues',
537
-                    'ee_delete_venue',
538
-                    'ee_delete_venues',
539
-                    'ee_delete_others_venues',
540
-                    'ee_delete_private_venues',
541
-                    'ee_delete_published_venues',
542
-                    // venue categories
543
-                    'ee_manage_venue_categories',
544
-                    'ee_edit_venue_category',
545
-                    'ee_delete_venue_category',
546
-                    'ee_assign_venue_category',
547
-                    // contacts
548
-                    'ee_read_contacts',
549
-                    'ee_edit_contacts',
550
-                    'ee_delete_contacts',
551
-                    // registrations
552
-                    'ee_read_registrations',
553
-                    'ee_read_others_registrations',
554
-                    'ee_edit_registration',
555
-                    'ee_edit_registrations',
556
-                    'ee_edit_others_registrations',
557
-                    'ee_delete_registration',
558
-                    'ee_delete_registrations',
559
-                    'ee_delete_others_registrations',
560
-                    // checkins
561
-                    'ee_read_others_checkins',
562
-                    'ee_read_checkins',
563
-                    'ee_edit_checkins',
564
-                    'ee_edit_others_checkins',
565
-                    'ee_delete_checkins',
566
-                    'ee_delete_others_checkins',
567
-                    // transactions && payments
568
-                    'ee_read_transaction',
569
-                    'ee_read_transactions',
570
-                    'ee_edit_payments',
571
-                    'ee_delete_payments',
572
-                    // messages
573
-                    'ee_read_messages',
574
-                    'ee_read_others_messages',
575
-                    'ee_read_global_messages',
576
-                    'ee_edit_global_messages',
577
-                    'ee_edit_messages',
578
-                    'ee_edit_others_messages',
579
-                    'ee_delete_messages',
580
-                    'ee_delete_others_messages',
581
-                    'ee_delete_global_messages',
582
-                    'ee_send_message',
583
-                    // tickets
584
-                    'ee_read_default_tickets',
585
-                    'ee_read_others_default_tickets',
586
-                    'ee_edit_default_tickets',
587
-                    'ee_edit_others_default_tickets',
588
-                    'ee_delete_default_tickets',
589
-                    'ee_delete_others_default_tickets',
590
-                    // prices
591
-                    'ee_edit_default_price',
592
-                    'ee_edit_default_prices',
593
-                    'ee_delete_default_price',
594
-                    'ee_delete_default_prices',
595
-                    'ee_edit_default_price_type',
596
-                    'ee_edit_default_price_types',
597
-                    'ee_delete_default_price_type',
598
-                    'ee_delete_default_price_types',
599
-                    'ee_read_default_prices',
600
-                    'ee_read_default_price_types',
601
-                    // registration form
602
-                    'ee_edit_questions',
603
-                    'ee_edit_system_questions',
604
-                    'ee_read_questions',
605
-                    'ee_delete_questions',
606
-                    'ee_edit_question_groups',
607
-                    'ee_read_question_groups',
608
-                    'ee_edit_system_question_groups',
609
-                    'ee_delete_question_groups',
610
-                    // event_type taxonomy
611
-                    'ee_assign_event_type',
612
-                    'ee_manage_event_types',
613
-                    'ee_edit_event_type',
614
-                    'ee_delete_event_type',
615
-                ],
616
-            ]
617
-        );
618
-    }
619
-
620
-
621
-    /**
622
-     * @return bool
623
-     * @throws EE_Error
624
-     */
625
-    private function setupCapabilitiesMap(): bool
626
-    {
627
-        // if the initialization process hasn't even started, then we need to call init_caps()
628
-        if ($this->initialized === null) {
629
-            return $this->init_caps();
630
-        }
631
-        // unless resetting, get caps from db if we haven't already
632
-        $this->capabilities_map = $this->reset || ! empty($this->capabilities_map)
633
-            ? $this->capabilities_map
634
-            : get_option(self::option_name, []);
635
-        return true;
636
-    }
637
-
638
-
639
-    /**
640
-     * @param bool $update
641
-     * @return bool
642
-     */
643
-    private function updateCapabilitiesMap(bool $update = true): bool
644
-    {
645
-        return $update && update_option(self::option_name, $this->capabilities_map);
646
-    }
647
-
648
-
649
-    /**
650
-     * Adds capabilities to roles.
651
-     *
652
-     * @param array $capabilities_to_add array of capabilities to add, indexed by roles.
653
-     *                                   Note that this should ONLY be called on activation hook
654
-     *                                   otherwise the caps will be added on every request.
655
-     * @return bool
656
-     * @throws EE_Error
657
-     * @since 4.9.42
658
-     */
659
-    public function addCaps(array $capabilities_to_add): bool
660
-    {
661
-        // don't do anything if the capabilities map can not be initialized
662
-        if (! $this->setupCapabilitiesMap()) {
663
-            return false;
664
-        }
665
-        // and filter the array so others can get in on the fun during resets
666
-        $capabilities_to_add     = apply_filters(
667
-            'FHEE__EE_Capabilities__addCaps__capabilities_to_add',
668
-            $capabilities_to_add,
669
-            $this->reset,
670
-            $this->capabilities_map
671
-        );
672
-        $update_capabilities_map = false;
673
-        // if not reset, see what caps are new for each role. if they're new, add them.
674
-        foreach ($capabilities_to_add as $role => $caps_for_role) {
675
-            if (is_array($caps_for_role)) {
676
-                foreach ($caps_for_role as $cap) {
677
-                    if (
678
-                        ! $this->capHasBeenAddedToRole($role, $cap)
679
-                        && $this->add_cap_to_role($role, $cap, true, false)
680
-                    ) {
681
-                        $update_capabilities_map = true;
682
-                    }
683
-                }
684
-            }
685
-        }
686
-        // now let's just save the cap that has been set but only if there's been a change.
687
-        $updated = $this->updateCapabilitiesMap($update_capabilities_map);
688
-        $this->flushWpUser($updated);
689
-        do_action('AHEE__EE_Capabilities__addCaps__complete', $this->capabilities_map, $updated);
690
-        return $updated;
691
-    }
692
-
693
-
694
-    /**
695
-     * Loops through the capabilities map and removes the role caps specified by the incoming array
696
-     *
697
-     * @param array $caps_map map of capabilities to be removed (indexed by roles)
698
-     * @return bool
699
-     * @throws EE_Error
700
-     */
701
-    public function removeCaps(array $caps_map): bool
702
-    {
703
-        // don't do anything if the capabilities map can not be initialized
704
-        if (! $this->setupCapabilitiesMap()) {
705
-            return false;
706
-        }
707
-        $update_capabilities_map = false;
708
-        foreach ($caps_map as $role => $caps_for_role) {
709
-            if (is_array($caps_for_role)) {
710
-                foreach ($caps_for_role as $cap) {
711
-                    if (
712
-                        $this->capHasBeenAddedToRole($role, $cap)
713
-                        && $this->remove_cap_from_role($role, $cap, false)
714
-                    ) {
715
-                        $update_capabilities_map = true;
716
-                    }
717
-                }
718
-            }
719
-        }
720
-        // maybe resave the caps
721
-        $updated = $this->updateCapabilitiesMap($update_capabilities_map);
722
-        $this->flushWpUser($updated);
723
-        return $updated;
724
-    }
725
-
726
-
727
-    /**
728
-     * This ensures that the WP User object cached on the $current_user global in WP has the latest capabilities from
729
-     * the roles on that user.
730
-     *
731
-     * @param bool $flush Default is to flush the WP_User object.  If false, then this method effectively does nothing.
732
-     */
733
-    private function flushWpUser(bool $flush = true)
734
-    {
735
-        if ($flush) {
736
-            $user = wp_get_current_user();
737
-            if ($user instanceof WP_User) {
738
-                $user->get_role_caps();
739
-            }
740
-        }
741
-    }
742
-
743
-
744
-    /**
745
-     * This method sets a capability on a role.  Note this should only be done on activation, or if you have something
746
-     * specific to prevent the cap from being added on every page load (adding caps are persistent to the db). Note.
747
-     * this is a wrapper for $wp_role->add_cap()
748
-     *
749
-     * @param string|WP_Role $role  A WordPress role the capability is being added to
750
-     * @param string         $cap   The capability being added to the role
751
-     * @param bool           $grant Whether to grant access to this cap on this role.
752
-     * @param bool           $update_capabilities_map
753
-     * @return bool
754
-     * @throws EE_Error
755
-     * @see   wp-includes/capabilities.php
756
-     * @since 4.5.0
757
-     */
758
-    public function add_cap_to_role(
759
-        $role,
760
-        string $cap,
761
-        bool $grant = true,
762
-        bool $update_capabilities_map = true
763
-    ): bool {
764
-        // capture incoming value for $role because we may need it to create a new WP_Role
765
-        $orig_role = $role;
766
-        $role      = $role instanceof WP_Role ? $role : get_role($role);
767
-        // if the role isn't available then we create it.
768
-        if (! $role instanceof WP_Role) {
769
-            // if a plugin wants to create a specific role name then they should create the role before
770
-            // EE_Capabilities does.  Otherwise this function will create the role name from the slug:
771
-            // - removes any `ee_` namespacing from the start of the slug.
772
-            // - replaces `_` with ` ` (empty space).
773
-            // - sentence case on the resulting string.
774
-            $role_label = ucwords(str_replace(['ee_', '_'], ['', ' '], $orig_role));
775
-            $role       = add_role($orig_role, $role_label);
776
-        }
777
-        if ($role instanceof WP_Role) {
778
-            // don't do anything if the capabilities map can not be initialized
779
-            if (! $this->setupCapabilitiesMap()) {
780
-                return false;
781
-            }
782
-            if (! $this->capHasBeenAddedToRole($role->name, $cap)) {
783
-                $role->add_cap($cap, $grant);
784
-                $this->capabilities_map[ $role->name ][] = $cap;
785
-                $this->updateCapabilitiesMap($update_capabilities_map);
786
-                return true;
787
-            }
788
-        }
789
-        return false;
790
-    }
791
-
792
-
793
-    /**
794
-     * Functions similarly to add_cap_to_role except removes cap from given role.
795
-     * Wrapper for $wp_role->remove_cap()
796
-     *
797
-     * @param string|WP_Role $role A WordPress role the capability is being removed from.
798
-     * @param string         $cap  The capability being removed
799
-     * @param bool           $update_capabilities_map
800
-     * @return bool
801
-     * @throws EE_Error
802
-     * @since 4.5.0
803
-     * @see   wp-includes/capabilities.php
804
-     */
805
-    public function remove_cap_from_role($role, string $cap, bool $update_capabilities_map = true): bool
806
-    {
807
-        // don't do anything if the capabilities map can not be initialized
808
-        if (! $this->setupCapabilitiesMap()) {
809
-            return false;
810
-        }
811
-
812
-        $role = $role instanceof WP_Role ? $role : get_role($role);
813
-        if ($role instanceof WP_Role && $index = $this->capHasBeenAddedToRole($role->name, $cap, true)) {
814
-            $role->remove_cap($cap);
815
-            unset($this->capabilities_map[ $role->name ][ $index ]);
816
-            $this->updateCapabilitiesMap($update_capabilities_map);
817
-            return true;
818
-        }
819
-        return false;
820
-    }
821
-
822
-
823
-    /**
824
-     * @param string $role_name
825
-     * @param string $cap
826
-     * @param bool   $get_index
827
-     * @return bool|int|string
828
-     */
829
-    private function capHasBeenAddedToRole(string $role_name = '', string $cap = '', bool $get_index = false)
830
-    {
831
-        if (
832
-            isset($this->capabilities_map[ $role_name ])
833
-            && ($index = array_search($cap, $this->capabilities_map[ $role_name ], true)) !== false
834
-        ) {
835
-            return $get_index ? $index : true;
836
-        }
837
-        return false;
838
-    }
839
-
840
-
841
-    /**
842
-     * Wrapper for the native WP current_user_can() method.
843
-     * This is provided as a handy method for a couple things:
844
-     * 1. Using the context string it allows for targeted filtering by addons for a specific check (without having to
845
-     * write those filters wherever current_user_can is called).
846
-     * 2. Explicit passing of $id from a given context ( useful in the cases of map_meta_cap filters )
847
-     *
848
-     * @param string $cap     The cap being checked.
849
-     * @param string $context The context where the current_user_can is being called from.
850
-     * @param int    $id      Optional. Id for item where current_user_can is being called from (used in map_meta_cap()
851
-     *                        filters.
852
-     *
853
-     * @return bool  Whether user can or not.
854
-     * @since 4.5.0
855
-     *
856
-     */
857
-    public function current_user_can(string $cap, string $context, int $id = 0): bool
858
-    {
859
-        // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
860
-        $filtered_cap = apply_filters('FHEE__EE_Capabilities__current_user_can__cap__' . $context, $cap, $id);
861
-        $filtered_cap = apply_filters(
862
-            'FHEE__EE_Capabilities__current_user_can__cap',
863
-            $filtered_cap,
864
-            $context,
865
-            $cap,
866
-            $id
867
-        );
868
-        return ! empty($id)
869
-            ? current_user_can($filtered_cap, $id)
870
-            : current_user_can($filtered_cap);
871
-    }
872
-
873
-
874
-    /**
875
-     * This is a wrapper for the WP user_can() function and follows the same style as the other wrappers in this class.
876
-     *
877
-     * @param int|WP_User $user    Either the user_id or a WP_User object
878
-     * @param string      $cap     The capability string being checked
879
-     * @param string      $context The context where the user_can is being called from (used in filters).
880
-     * @param int         $id      Optional. Id for item where user_can is being called from ( used in map_meta_cap()
881
-     *                             filters)
882
-     *
883
-     * @return bool Whether user can or not.
884
-     */
885
-    public function user_can($user, string $cap, string $context, int $id = 0): bool
886
-    {
887
-        // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
888
-        $filtered_cap = apply_filters('FHEE__EE_Capabilities__user_can__cap__' . $context, $cap, $user, $id);
889
-        $filtered_cap = apply_filters(
890
-            'FHEE__EE_Capabilities__user_can__cap',
891
-            $filtered_cap,
892
-            $context,
893
-            $cap,
894
-            $user,
895
-            $id
896
-        );
897
-        return ! empty($id)
898
-            ? user_can($user, $filtered_cap, $id)
899
-            : user_can($user, $filtered_cap);
900
-    }
901
-
902
-
903
-    /**
904
-     * Wrapper for the native WP current_user_can_for_blog() method.
905
-     * This is provided as a handy method for a couple things:
906
-     * 1. Using the context string it allows for targeted filtering by addons for a specific check (without having to
907
-     * write those filters wherever current_user_can is called).
908
-     * 2. Explicit passing of $id from a given context ( useful in the cases of map_meta_cap filters )
909
-     *
910
-     * @param int    $blog_id The blog id that is being checked for.
911
-     * @param string $cap     The cap being checked.
912
-     * @param string $context The context where the current_user_can is being called from.
913
-     * @param int    $id      Optional. Id for item where current_user_can is being called from (used in map_meta_cap()
914
-     *                        filters.
915
-     *
916
-     * @return bool  Whether user can or not.
917
-     * @since 4.5.0
918
-     *
919
-     */
920
-    public function current_user_can_for_blog(int $blog_id, string $cap, string $context, int $id = 0): bool
921
-    {
922
-        $user_can = ! empty($id)
923
-            ? current_user_can_for_blog($blog_id, $cap, $id)
924
-            : current_user_can($blog_id, $cap);
925
-        // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
926
-        $user_can = apply_filters(
927
-            'FHEE__EE_Capabilities__current_user_can_for_blog__user_can__' . $context,
928
-            $user_can,
929
-            $blog_id,
930
-            $cap,
931
-            $id
932
-        );
933
-        return apply_filters(
934
-            'FHEE__EE_Capabilities__current_user_can_for_blog__user_can',
935
-            $user_can,
936
-            $context,
937
-            $blog_id,
938
-            $cap,
939
-            $id
940
-        );
941
-    }
942
-
943
-
944
-    /**
945
-     * This helper method just returns an array of registered EE capabilities.
946
-     *
947
-     * @param string|null $role If empty then the entire role/capability map is returned.
948
-     *                          Otherwise just the capabilities for the given role are returned.
949
-     * @return array
950
-     * @throws EE_Error
951
-     * @since 4.5.0
952
-     */
953
-    public function get_ee_capabilities(?string $role = EE_Capabilities::ROLE_ADMINISTRATOR): array
954
-    {
955
-        if (! $this->initialized) {
956
-            $this->init_caps();
957
-        }
958
-        if ($role === '') {
959
-            return $this->capabilities_map;
960
-        }
961
-        return $this->capabilities_map[ $role ] ?? [];
962
-    }
963
-
964
-
965
-    /**
966
-     * @param bool  $reset      If you need to reset Event Espresso's capabilities,
967
-     *                          then please use the init_caps() method with the "$reset" parameter set to "true"
968
-     * @param array $caps_map   Optional.
969
-     *                          Can be used to send a custom map of roles and capabilities for setting them up.
970
-     *                          Note that this should ONLY be called on activation hook or some other one-time
971
-     *                          task otherwise the caps will be added on every request.
972
-     * @return void
973
-     * @throws EE_Error
974
-     * @deprecated 4.9.42
975
-     */
976
-    public function init_role_caps(bool $reset = false, array $caps_map = [])
977
-    {
978
-        // If this method is called directly and reset is set as 'true',
979
-        // then display a doing it wrong notice, because we want resets to go through init_caps()
980
-        // to guarantee that everything is set up correctly.
981
-        // This prevents the capabilities map getting reset incorrectly by direct calls to this method.
982
-        if ($reset) {
983
-            EE_Error::doing_it_wrong(
984
-                __METHOD__,
985
-                sprintf(
986
-                    esc_html__(
987
-                        'The "%1$s" parameter for the "%2$s" method is deprecated. If you need to reset Event Espresso\'s capabilities, then please use the "%3$s" method with the "%1$s" parameter set to "%4$s".',
988
-                        'event_espresso'
989
-                    ),
990
-                    '$reset',
991
-                    __METHOD__ . '()',
992
-                    'EE_Capabilities::init_caps()',
993
-                    'true'
994
-                ),
995
-                '4.9.42',
996
-                '5.0.0'
997
-            );
998
-        }
999
-        $this->addCaps($caps_map);
1000
-    }
16
+	const ROLE_ADMINISTRATOR        = 'administrator';
17
+
18
+	const ROLE_EVENTS_ADMINISTRATOR = 'ee_events_administrator';
19
+
20
+
21
+	/**
22
+	 * the name of the wp option used to store caps previously initialized
23
+	 */
24
+	const option_name = 'ee_caps_initialized';
25
+
26
+	/**
27
+	 * instance of EE_Capabilities object
28
+	 *
29
+	 * @var EE_Capabilities
30
+	 */
31
+	private static $_instance;
32
+
33
+
34
+	/**
35
+	 * This is a map of caps that correspond to a default WP_Role.
36
+	 * Array is indexed by Role and values are ee capabilities.
37
+	 *
38
+	 * @since 4.5.0
39
+	 *
40
+	 * @var array
41
+	 */
42
+	private $capabilities_map = [];
43
+
44
+	/**
45
+	 * This used to hold an array of EE_Meta_Capability_Map objects
46
+	 * that define the granular capabilities mapped to for a user depending on context.
47
+	 *
48
+	 * @var EE_Meta_Capability_Map[]
49
+	 */
50
+	private $_meta_caps = [];
51
+
52
+	/**
53
+	 * The internal $capabilities_map needs to be initialized before it can be used.
54
+	 * This flag tracks whether that has happened or not.
55
+	 * But for this to work, we need three states to indicate:
56
+	 *      initialization has not occurred at all
57
+	 *      initialization has started but is not complete
58
+	 *      initialization is complete
59
+	 * The reason this is needed is because the addCaps() method
60
+	 * normally requires the $capabilities_map to be initialized,
61
+	 * but is also used during the initialization process.
62
+	 * So:
63
+	 *      If initialized === null, init_caps() will be called before any other methods will run.
64
+	 *      If initialized === false, then init_caps() is in the process of running it's logic.
65
+	 *      If initialized === true, then init_caps() has completed the initialization process.
66
+	 *
67
+	 * @var boolean|null $initialized
68
+	 */
69
+	private $initialized;
70
+
71
+	/**
72
+	 * @var boolean $reset
73
+	 */
74
+	private $reset = false;
75
+
76
+
77
+	/**
78
+	 * singleton method used to instantiate class object
79
+	 *
80
+	 * @return EE_Capabilities
81
+	 * @since 4.5.0
82
+	 *
83
+	 */
84
+	public static function instance(): EE_Capabilities
85
+	{
86
+		// check if instantiated, and if not do so.
87
+		if (! self::$_instance instanceof EE_Capabilities) {
88
+			self::$_instance = new self();
89
+		}
90
+		return self::$_instance;
91
+	}
92
+
93
+
94
+	/**
95
+	 * private constructor
96
+	 *
97
+	 * @since 4.5.0
98
+	 */
99
+	private function __construct()
100
+	{
101
+	}
102
+
103
+
104
+	/**
105
+	 * This delays the initialization of the capabilities class until EE_System core is loaded and ready.
106
+	 *
107
+	 * @param bool $reset allows for resetting the default capabilities saved on roles.  Note that this doesn't
108
+	 *                    actually REMOVE any capabilities from existing roles, it just resaves defaults roles and
109
+	 *                    ensures that they are up to date.
110
+	 *
111
+	 * @since 4.5.0
112
+	 * @return bool
113
+	 * @throws EE_Error
114
+	 */
115
+	public function init_caps(?bool $reset = false): bool
116
+	{
117
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
118
+			return false;
119
+		}
120
+		$this->reset = filter_var($reset, FILTER_VALIDATE_BOOLEAN);
121
+		// if reset, then completely delete the cache option and clear the $capabilities_map property.
122
+		if ($this->reset) {
123
+			$this->initialized      = null;
124
+			$this->capabilities_map = [];
125
+			delete_option(self::option_name);
126
+		}
127
+		if ($this->initialized === null) {
128
+			$this->initialized = false;
129
+			do_action(
130
+				'AHEE__EE_Capabilities__init_caps__before_initialization',
131
+				$this->reset
132
+			);
133
+			$this->addCaps($this->_init_caps_map());
134
+			$this->_set_meta_caps();
135
+			do_action(
136
+				'AHEE__EE_Capabilities__init_caps__after_initialization',
137
+				$this->capabilities_map
138
+			);
139
+			$this->initialized = true;
140
+		}
141
+		// reset $this->reset so that it's not stuck on true if init_caps() gets called again
142
+		$this->reset = false;
143
+		return true;
144
+	}
145
+
146
+
147
+	/**
148
+	 * This sets the meta caps property.
149
+	 *
150
+	 * @return void
151
+	 * @throws EE_Error
152
+	 * @since 4.5.0
153
+	 */
154
+	private function _set_meta_caps()
155
+	{
156
+		// get default meta caps and filter the returned array
157
+		$this->_meta_caps = apply_filters(
158
+			'FHEE__EE_Capabilities___set_meta_caps__meta_caps',
159
+			$this->_get_default_meta_caps_array()
160
+		);
161
+		// add filter for map_meta_caps but only if models can query.
162
+		if (! has_filter('map_meta_cap', [$this, 'map_meta_caps'])) {
163
+			add_filter('map_meta_cap', [$this, 'map_meta_caps'], 10, 4);
164
+		}
165
+	}
166
+
167
+
168
+	/**
169
+	 * This builds and returns the default meta_caps array only once.
170
+	 *
171
+	 * @return array
172
+	 * @throws EE_Error
173
+	 * @since  4.8.28.rc.012
174
+	 */
175
+	private function _get_default_meta_caps_array(): array
176
+	{
177
+		static $default_meta_caps = [];
178
+		// make sure we're only ever initializing the default _meta_caps array once if it's empty.
179
+		if (empty($default_meta_caps)) {
180
+			$default_meta_caps = [
181
+				// edits
182
+				new EE_Meta_Capability_Map_Edit(
183
+					'ee_edit_event',
184
+					['Event', 'ee_edit_published_events', 'ee_edit_others_events', 'ee_edit_private_events']
185
+				),
186
+				new EE_Meta_Capability_Map_Edit(
187
+					'ee_edit_venue',
188
+					['Venue', 'ee_edit_published_venues', 'ee_edit_others_venues', 'ee_edit_private_venues']
189
+				),
190
+				new EE_Meta_Capability_Map_Edit(
191
+					'ee_edit_registration',
192
+					['Registration', '', 'ee_edit_others_registrations', '']
193
+				),
194
+				new EE_Meta_Capability_Map_Edit(
195
+					'ee_edit_checkin',
196
+					['Registration', '', 'ee_edit_others_checkins', '']
197
+				),
198
+				new EE_Meta_Capability_Map_Messages_Cap(
199
+					'ee_edit_message',
200
+					['Message_Template_Group', '', 'ee_edit_others_messages', 'ee_edit_global_messages']
201
+				),
202
+				new EE_Meta_Capability_Map_Edit(
203
+					'ee_edit_default_ticket',
204
+					['Ticket', '', 'ee_edit_others_default_tickets', '']
205
+				),
206
+				new EE_Meta_Capability_Map_Registration_Form_Cap(
207
+					'ee_edit_question',
208
+					['Question', '', '', 'ee_edit_system_questions']
209
+				),
210
+				new EE_Meta_Capability_Map_Registration_Form_Cap(
211
+					'ee_edit_question_group',
212
+					['Question_Group', '', '', 'ee_edit_system_question_groups']
213
+				),
214
+				new EE_Meta_Capability_Map_Edit(
215
+					'ee_edit_payment_method',
216
+					['Payment_Method', '', 'ee_edit_others_payment_methods', '']
217
+				),
218
+				// reads
219
+				new EE_Meta_Capability_Map_Read(
220
+					'ee_read_event',
221
+					['Event', '', 'ee_read_others_events', 'ee_read_private_events']
222
+				),
223
+				new EE_Meta_Capability_Map_Read(
224
+					'ee_read_venue',
225
+					['Venue', '', 'ee_read_others_venues', 'ee_read_private_venues']
226
+				),
227
+				new EE_Meta_Capability_Map_Read(
228
+					'ee_read_registration',
229
+					['Registration', '', 'ee_read_others_registrations', '']
230
+				),
231
+				new EE_Meta_Capability_Map_Read(
232
+					'ee_read_checkin',
233
+					['Registration', '', 'ee_read_others_checkins', '']
234
+				),
235
+				new EE_Meta_Capability_Map_Messages_Cap(
236
+					'ee_read_message',
237
+					['Message_Template_Group', '', 'ee_read_others_messages', 'ee_read_global_messages']
238
+				),
239
+				new EE_Meta_Capability_Map_Read(
240
+					'ee_read_default_ticket',
241
+					['Ticket', '', 'ee_read_others_default_tickets', '']
242
+				),
243
+				new EE_Meta_Capability_Map_Read(
244
+					'ee_read_payment_method',
245
+					['Payment_Method', '', 'ee_read_others_payment_methods', '']
246
+				),
247
+				// deletes
248
+				new EE_Meta_Capability_Map_Delete(
249
+					'ee_delete_event',
250
+					[
251
+						'Event',
252
+						'ee_delete_published_events',
253
+						'ee_delete_others_events',
254
+						'ee_delete_private_events',
255
+					]
256
+				),
257
+				new EE_Meta_Capability_Map_Delete(
258
+					'ee_delete_venue',
259
+					[
260
+						'Venue',
261
+						'ee_delete_published_venues',
262
+						'ee_delete_others_venues',
263
+						'ee_delete_private_venues',
264
+					]
265
+				),
266
+				new EE_Meta_Capability_Map_Delete(
267
+					'ee_delete_registration',
268
+					['Registration', '', 'ee_delete_others_registrations', '']
269
+				),
270
+				new EE_Meta_Capability_Map_Delete(
271
+					'ee_delete_checkin',
272
+					['Registration', '', 'ee_delete_others_checkins', '']
273
+				),
274
+				new EE_Meta_Capability_Map_Messages_Cap(
275
+					'ee_delete_message',
276
+					['Message_Template_Group', '', 'ee_delete_others_messages', 'ee_delete_global_messages']
277
+				),
278
+				new EE_Meta_Capability_Map_Delete(
279
+					'ee_delete_default_ticket',
280
+					['Ticket', '', 'ee_delete_others_default_tickets', '']
281
+				),
282
+				new EE_Meta_Capability_Map_Registration_Form_Cap(
283
+					'ee_delete_question',
284
+					['Question', '', '', 'delete_system_questions']
285
+				),
286
+				new EE_Meta_Capability_Map_Registration_Form_Cap(
287
+					'ee_delete_question_group',
288
+					['Question_Group', '', '', 'delete_system_question_groups']
289
+				),
290
+				new EE_Meta_Capability_Map_Delete(
291
+					'ee_delete_payment_method',
292
+					['Payment_Method', '', 'ee_delete_others_payment_methods', '']
293
+				),
294
+			];
295
+		}
296
+		return $default_meta_caps;
297
+	}
298
+
299
+
300
+	/**
301
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
302
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
303
+	 *
304
+	 * The actual logic is carried out by implementer classes in their definition of _map_meta_caps.
305
+	 *
306
+	 * @param array  $caps    actual users capabilities
307
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
308
+	 * @param int    $user_id The user id
309
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
310
+	 * @return array actual users capabilities
311
+	 * @throws EE_Error
312
+	 * @throws ReflectionException
313
+	 * @since 4.5.0
314
+	 * @see   wp-includes/capabilities.php
315
+	 *
316
+	 */
317
+	public function map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
318
+	{
319
+		if (did_action('AHEE__EE_System__load_espresso_addons__complete')) {
320
+			// loop through our _meta_caps array
321
+			foreach ($this->_meta_caps as $meta_map) {
322
+				if (! $meta_map instanceof EE_Meta_Capability_Map) {
323
+					continue;
324
+				}
325
+				// don't load models if there is no object ID in the args
326
+				if (! empty($args[0])) {
327
+					$meta_map->ensure_is_model();
328
+				}
329
+				$caps = $meta_map->map_meta_caps($caps, $cap, $user_id, $args);
330
+			}
331
+		}
332
+		return $caps;
333
+	}
334
+
335
+
336
+	/**
337
+	 * This sets up and returns the initial capabilities map for Event Espresso
338
+	 * Note this array is filtered.
339
+	 * It is assumed that all available EE capabilities are assigned to the administrator role.
340
+	 *
341
+	 * @return array
342
+	 * @since 4.5.0
343
+	 *
344
+	 */
345
+	private function _init_caps_map(): array
346
+	{
347
+		return apply_filters(
348
+			'FHEE__EE_Capabilities__init_caps_map__caps',
349
+			[
350
+				EE_Capabilities::ROLE_ADMINISTRATOR        => [
351
+					// basic access
352
+					'ee_read_ee',
353
+					// gateways
354
+					/**
355
+					 * note that with payment method capabilities, although we've implemented
356
+					 * capability mapping which will be used for accessing payment methods owned by
357
+					 * other users.  This is not fully implemented yet in the payment method ui.
358
+					 * Currently only the "plural" caps are in active use.
359
+					 * (Specific payment method caps are in use as well).
360
+					 **/
361
+					'ee_manage_gateways',
362
+					'ee_read_payment_methods',
363
+					'ee_read_others_payment_methods',
364
+					'ee_edit_payment_methods',
365
+					'ee_edit_others_payment_methods',
366
+					'ee_delete_payment_methods',
367
+					// events
368
+					'ee_publish_events',
369
+					'ee_read_private_events',
370
+					'ee_read_others_events',
371
+					'ee_read_events',
372
+					'ee_edit_events',
373
+					'ee_edit_published_events',
374
+					'ee_edit_others_events',
375
+					'ee_edit_private_events',
376
+					'ee_delete_published_events',
377
+					'ee_delete_private_events',
378
+					'ee_delete_events',
379
+					'ee_delete_others_events',
380
+					// event categories
381
+					'ee_manage_event_categories',
382
+					'ee_edit_event_category',
383
+					'ee_delete_event_category',
384
+					'ee_assign_event_category',
385
+					// venues
386
+					'ee_publish_venues',
387
+					'ee_read_venues',
388
+					'ee_read_others_venues',
389
+					'ee_read_private_venues',
390
+					'ee_edit_venues',
391
+					'ee_edit_others_venues',
392
+					'ee_edit_published_venues',
393
+					'ee_edit_private_venues',
394
+					'ee_delete_venues',
395
+					'ee_delete_others_venues',
396
+					'ee_delete_private_venues',
397
+					'ee_delete_published_venues',
398
+					// venue categories
399
+					'ee_manage_venue_categories',
400
+					'ee_edit_venue_category',
401
+					'ee_delete_venue_category',
402
+					'ee_assign_venue_category',
403
+					// contacts
404
+					'ee_read_contacts',
405
+					'ee_edit_contacts',
406
+					'ee_delete_contacts',
407
+					// registrations
408
+					'ee_read_registrations',
409
+					'ee_read_others_registrations',
410
+					'ee_edit_registrations',
411
+					'ee_edit_others_registrations',
412
+					'ee_delete_registrations',
413
+					'ee_delete_others_registrations',
414
+					// checkins
415
+					'ee_read_others_checkins',
416
+					'ee_read_checkins',
417
+					'ee_edit_checkins',
418
+					'ee_edit_others_checkins',
419
+					'ee_delete_checkins',
420
+					'ee_delete_others_checkins',
421
+					// transactions && payments
422
+					'ee_read_transaction',
423
+					'ee_read_transactions',
424
+					'ee_edit_payments',
425
+					'ee_delete_payments',
426
+					// messages
427
+					'ee_read_messages',
428
+					'ee_read_others_messages',
429
+					'ee_read_global_messages',
430
+					'ee_edit_global_messages',
431
+					'ee_edit_messages',
432
+					'ee_edit_others_messages',
433
+					'ee_delete_messages',
434
+					'ee_delete_others_messages',
435
+					'ee_delete_global_messages',
436
+					'ee_send_message',
437
+					// tickets
438
+					'ee_read_default_tickets',
439
+					'ee_read_others_default_tickets',
440
+					'ee_edit_default_tickets',
441
+					'ee_edit_others_default_tickets',
442
+					'ee_delete_default_tickets',
443
+					'ee_delete_others_default_tickets',
444
+					// prices
445
+					'ee_edit_default_price',
446
+					'ee_edit_default_prices',
447
+					'ee_delete_default_price',
448
+					'ee_delete_default_prices',
449
+					'ee_edit_default_price_type',
450
+					'ee_edit_default_price_types',
451
+					'ee_delete_default_price_type',
452
+					'ee_delete_default_price_types',
453
+					'ee_read_default_prices',
454
+					'ee_read_default_price_types',
455
+					// registration form
456
+					'ee_edit_questions',
457
+					'ee_edit_system_questions',
458
+					'ee_read_questions',
459
+					'ee_delete_questions',
460
+					'ee_edit_question_groups',
461
+					'ee_read_question_groups',
462
+					'ee_edit_system_question_groups',
463
+					'ee_delete_question_groups',
464
+					// event_type taxonomy
465
+					'ee_assign_event_type',
466
+					'ee_manage_event_types',
467
+					'ee_edit_event_type',
468
+					'ee_delete_event_type',
469
+				],
470
+				EE_Capabilities::ROLE_EVENTS_ADMINISTRATOR => [
471
+					// core wp caps
472
+					'read',
473
+					'read_private_pages',
474
+					'read_private_posts',
475
+					'edit_users',
476
+					'edit_posts',
477
+					'edit_pages',
478
+					'edit_published_posts',
479
+					'edit_published_pages',
480
+					'edit_private_pages',
481
+					'edit_private_posts',
482
+					'edit_others_posts',
483
+					'edit_others_pages',
484
+					'publish_posts',
485
+					'publish_pages',
486
+					'delete_posts',
487
+					'delete_pages',
488
+					'delete_private_pages',
489
+					'delete_private_posts',
490
+					'delete_published_pages',
491
+					'delete_published_posts',
492
+					'delete_others_posts',
493
+					'delete_others_pages',
494
+					'manage_categories',
495
+					'manage_links',
496
+					'moderate_comments',
497
+					'unfiltered_html',
498
+					'upload_files',
499
+					'export',
500
+					'import',
501
+					'list_users',
502
+					'level_1', // required if user with this role shows up in author dropdowns
503
+					// basic ee access
504
+					'ee_read_ee',
505
+					// events
506
+					'ee_publish_events',
507
+					'ee_read_private_events',
508
+					'ee_read_others_events',
509
+					'ee_read_event',
510
+					'ee_read_events',
511
+					'ee_edit_event',
512
+					'ee_edit_events',
513
+					'ee_edit_published_events',
514
+					'ee_edit_others_events',
515
+					'ee_edit_private_events',
516
+					'ee_delete_published_events',
517
+					'ee_delete_private_events',
518
+					'ee_delete_event',
519
+					'ee_delete_events',
520
+					'ee_delete_others_events',
521
+					// event categories
522
+					'ee_manage_event_categories',
523
+					'ee_edit_event_category',
524
+					'ee_delete_event_category',
525
+					'ee_assign_event_category',
526
+					// venues
527
+					'ee_publish_venues',
528
+					'ee_read_venue',
529
+					'ee_read_venues',
530
+					'ee_read_others_venues',
531
+					'ee_read_private_venues',
532
+					'ee_edit_venue',
533
+					'ee_edit_venues',
534
+					'ee_edit_others_venues',
535
+					'ee_edit_published_venues',
536
+					'ee_edit_private_venues',
537
+					'ee_delete_venue',
538
+					'ee_delete_venues',
539
+					'ee_delete_others_venues',
540
+					'ee_delete_private_venues',
541
+					'ee_delete_published_venues',
542
+					// venue categories
543
+					'ee_manage_venue_categories',
544
+					'ee_edit_venue_category',
545
+					'ee_delete_venue_category',
546
+					'ee_assign_venue_category',
547
+					// contacts
548
+					'ee_read_contacts',
549
+					'ee_edit_contacts',
550
+					'ee_delete_contacts',
551
+					// registrations
552
+					'ee_read_registrations',
553
+					'ee_read_others_registrations',
554
+					'ee_edit_registration',
555
+					'ee_edit_registrations',
556
+					'ee_edit_others_registrations',
557
+					'ee_delete_registration',
558
+					'ee_delete_registrations',
559
+					'ee_delete_others_registrations',
560
+					// checkins
561
+					'ee_read_others_checkins',
562
+					'ee_read_checkins',
563
+					'ee_edit_checkins',
564
+					'ee_edit_others_checkins',
565
+					'ee_delete_checkins',
566
+					'ee_delete_others_checkins',
567
+					// transactions && payments
568
+					'ee_read_transaction',
569
+					'ee_read_transactions',
570
+					'ee_edit_payments',
571
+					'ee_delete_payments',
572
+					// messages
573
+					'ee_read_messages',
574
+					'ee_read_others_messages',
575
+					'ee_read_global_messages',
576
+					'ee_edit_global_messages',
577
+					'ee_edit_messages',
578
+					'ee_edit_others_messages',
579
+					'ee_delete_messages',
580
+					'ee_delete_others_messages',
581
+					'ee_delete_global_messages',
582
+					'ee_send_message',
583
+					// tickets
584
+					'ee_read_default_tickets',
585
+					'ee_read_others_default_tickets',
586
+					'ee_edit_default_tickets',
587
+					'ee_edit_others_default_tickets',
588
+					'ee_delete_default_tickets',
589
+					'ee_delete_others_default_tickets',
590
+					// prices
591
+					'ee_edit_default_price',
592
+					'ee_edit_default_prices',
593
+					'ee_delete_default_price',
594
+					'ee_delete_default_prices',
595
+					'ee_edit_default_price_type',
596
+					'ee_edit_default_price_types',
597
+					'ee_delete_default_price_type',
598
+					'ee_delete_default_price_types',
599
+					'ee_read_default_prices',
600
+					'ee_read_default_price_types',
601
+					// registration form
602
+					'ee_edit_questions',
603
+					'ee_edit_system_questions',
604
+					'ee_read_questions',
605
+					'ee_delete_questions',
606
+					'ee_edit_question_groups',
607
+					'ee_read_question_groups',
608
+					'ee_edit_system_question_groups',
609
+					'ee_delete_question_groups',
610
+					// event_type taxonomy
611
+					'ee_assign_event_type',
612
+					'ee_manage_event_types',
613
+					'ee_edit_event_type',
614
+					'ee_delete_event_type',
615
+				],
616
+			]
617
+		);
618
+	}
619
+
620
+
621
+	/**
622
+	 * @return bool
623
+	 * @throws EE_Error
624
+	 */
625
+	private function setupCapabilitiesMap(): bool
626
+	{
627
+		// if the initialization process hasn't even started, then we need to call init_caps()
628
+		if ($this->initialized === null) {
629
+			return $this->init_caps();
630
+		}
631
+		// unless resetting, get caps from db if we haven't already
632
+		$this->capabilities_map = $this->reset || ! empty($this->capabilities_map)
633
+			? $this->capabilities_map
634
+			: get_option(self::option_name, []);
635
+		return true;
636
+	}
637
+
638
+
639
+	/**
640
+	 * @param bool $update
641
+	 * @return bool
642
+	 */
643
+	private function updateCapabilitiesMap(bool $update = true): bool
644
+	{
645
+		return $update && update_option(self::option_name, $this->capabilities_map);
646
+	}
647
+
648
+
649
+	/**
650
+	 * Adds capabilities to roles.
651
+	 *
652
+	 * @param array $capabilities_to_add array of capabilities to add, indexed by roles.
653
+	 *                                   Note that this should ONLY be called on activation hook
654
+	 *                                   otherwise the caps will be added on every request.
655
+	 * @return bool
656
+	 * @throws EE_Error
657
+	 * @since 4.9.42
658
+	 */
659
+	public function addCaps(array $capabilities_to_add): bool
660
+	{
661
+		// don't do anything if the capabilities map can not be initialized
662
+		if (! $this->setupCapabilitiesMap()) {
663
+			return false;
664
+		}
665
+		// and filter the array so others can get in on the fun during resets
666
+		$capabilities_to_add     = apply_filters(
667
+			'FHEE__EE_Capabilities__addCaps__capabilities_to_add',
668
+			$capabilities_to_add,
669
+			$this->reset,
670
+			$this->capabilities_map
671
+		);
672
+		$update_capabilities_map = false;
673
+		// if not reset, see what caps are new for each role. if they're new, add them.
674
+		foreach ($capabilities_to_add as $role => $caps_for_role) {
675
+			if (is_array($caps_for_role)) {
676
+				foreach ($caps_for_role as $cap) {
677
+					if (
678
+						! $this->capHasBeenAddedToRole($role, $cap)
679
+						&& $this->add_cap_to_role($role, $cap, true, false)
680
+					) {
681
+						$update_capabilities_map = true;
682
+					}
683
+				}
684
+			}
685
+		}
686
+		// now let's just save the cap that has been set but only if there's been a change.
687
+		$updated = $this->updateCapabilitiesMap($update_capabilities_map);
688
+		$this->flushWpUser($updated);
689
+		do_action('AHEE__EE_Capabilities__addCaps__complete', $this->capabilities_map, $updated);
690
+		return $updated;
691
+	}
692
+
693
+
694
+	/**
695
+	 * Loops through the capabilities map and removes the role caps specified by the incoming array
696
+	 *
697
+	 * @param array $caps_map map of capabilities to be removed (indexed by roles)
698
+	 * @return bool
699
+	 * @throws EE_Error
700
+	 */
701
+	public function removeCaps(array $caps_map): bool
702
+	{
703
+		// don't do anything if the capabilities map can not be initialized
704
+		if (! $this->setupCapabilitiesMap()) {
705
+			return false;
706
+		}
707
+		$update_capabilities_map = false;
708
+		foreach ($caps_map as $role => $caps_for_role) {
709
+			if (is_array($caps_for_role)) {
710
+				foreach ($caps_for_role as $cap) {
711
+					if (
712
+						$this->capHasBeenAddedToRole($role, $cap)
713
+						&& $this->remove_cap_from_role($role, $cap, false)
714
+					) {
715
+						$update_capabilities_map = true;
716
+					}
717
+				}
718
+			}
719
+		}
720
+		// maybe resave the caps
721
+		$updated = $this->updateCapabilitiesMap($update_capabilities_map);
722
+		$this->flushWpUser($updated);
723
+		return $updated;
724
+	}
725
+
726
+
727
+	/**
728
+	 * This ensures that the WP User object cached on the $current_user global in WP has the latest capabilities from
729
+	 * the roles on that user.
730
+	 *
731
+	 * @param bool $flush Default is to flush the WP_User object.  If false, then this method effectively does nothing.
732
+	 */
733
+	private function flushWpUser(bool $flush = true)
734
+	{
735
+		if ($flush) {
736
+			$user = wp_get_current_user();
737
+			if ($user instanceof WP_User) {
738
+				$user->get_role_caps();
739
+			}
740
+		}
741
+	}
742
+
743
+
744
+	/**
745
+	 * This method sets a capability on a role.  Note this should only be done on activation, or if you have something
746
+	 * specific to prevent the cap from being added on every page load (adding caps are persistent to the db). Note.
747
+	 * this is a wrapper for $wp_role->add_cap()
748
+	 *
749
+	 * @param string|WP_Role $role  A WordPress role the capability is being added to
750
+	 * @param string         $cap   The capability being added to the role
751
+	 * @param bool           $grant Whether to grant access to this cap on this role.
752
+	 * @param bool           $update_capabilities_map
753
+	 * @return bool
754
+	 * @throws EE_Error
755
+	 * @see   wp-includes/capabilities.php
756
+	 * @since 4.5.0
757
+	 */
758
+	public function add_cap_to_role(
759
+		$role,
760
+		string $cap,
761
+		bool $grant = true,
762
+		bool $update_capabilities_map = true
763
+	): bool {
764
+		// capture incoming value for $role because we may need it to create a new WP_Role
765
+		$orig_role = $role;
766
+		$role      = $role instanceof WP_Role ? $role : get_role($role);
767
+		// if the role isn't available then we create it.
768
+		if (! $role instanceof WP_Role) {
769
+			// if a plugin wants to create a specific role name then they should create the role before
770
+			// EE_Capabilities does.  Otherwise this function will create the role name from the slug:
771
+			// - removes any `ee_` namespacing from the start of the slug.
772
+			// - replaces `_` with ` ` (empty space).
773
+			// - sentence case on the resulting string.
774
+			$role_label = ucwords(str_replace(['ee_', '_'], ['', ' '], $orig_role));
775
+			$role       = add_role($orig_role, $role_label);
776
+		}
777
+		if ($role instanceof WP_Role) {
778
+			// don't do anything if the capabilities map can not be initialized
779
+			if (! $this->setupCapabilitiesMap()) {
780
+				return false;
781
+			}
782
+			if (! $this->capHasBeenAddedToRole($role->name, $cap)) {
783
+				$role->add_cap($cap, $grant);
784
+				$this->capabilities_map[ $role->name ][] = $cap;
785
+				$this->updateCapabilitiesMap($update_capabilities_map);
786
+				return true;
787
+			}
788
+		}
789
+		return false;
790
+	}
791
+
792
+
793
+	/**
794
+	 * Functions similarly to add_cap_to_role except removes cap from given role.
795
+	 * Wrapper for $wp_role->remove_cap()
796
+	 *
797
+	 * @param string|WP_Role $role A WordPress role the capability is being removed from.
798
+	 * @param string         $cap  The capability being removed
799
+	 * @param bool           $update_capabilities_map
800
+	 * @return bool
801
+	 * @throws EE_Error
802
+	 * @since 4.5.0
803
+	 * @see   wp-includes/capabilities.php
804
+	 */
805
+	public function remove_cap_from_role($role, string $cap, bool $update_capabilities_map = true): bool
806
+	{
807
+		// don't do anything if the capabilities map can not be initialized
808
+		if (! $this->setupCapabilitiesMap()) {
809
+			return false;
810
+		}
811
+
812
+		$role = $role instanceof WP_Role ? $role : get_role($role);
813
+		if ($role instanceof WP_Role && $index = $this->capHasBeenAddedToRole($role->name, $cap, true)) {
814
+			$role->remove_cap($cap);
815
+			unset($this->capabilities_map[ $role->name ][ $index ]);
816
+			$this->updateCapabilitiesMap($update_capabilities_map);
817
+			return true;
818
+		}
819
+		return false;
820
+	}
821
+
822
+
823
+	/**
824
+	 * @param string $role_name
825
+	 * @param string $cap
826
+	 * @param bool   $get_index
827
+	 * @return bool|int|string
828
+	 */
829
+	private function capHasBeenAddedToRole(string $role_name = '', string $cap = '', bool $get_index = false)
830
+	{
831
+		if (
832
+			isset($this->capabilities_map[ $role_name ])
833
+			&& ($index = array_search($cap, $this->capabilities_map[ $role_name ], true)) !== false
834
+		) {
835
+			return $get_index ? $index : true;
836
+		}
837
+		return false;
838
+	}
839
+
840
+
841
+	/**
842
+	 * Wrapper for the native WP current_user_can() method.
843
+	 * This is provided as a handy method for a couple things:
844
+	 * 1. Using the context string it allows for targeted filtering by addons for a specific check (without having to
845
+	 * write those filters wherever current_user_can is called).
846
+	 * 2. Explicit passing of $id from a given context ( useful in the cases of map_meta_cap filters )
847
+	 *
848
+	 * @param string $cap     The cap being checked.
849
+	 * @param string $context The context where the current_user_can is being called from.
850
+	 * @param int    $id      Optional. Id for item where current_user_can is being called from (used in map_meta_cap()
851
+	 *                        filters.
852
+	 *
853
+	 * @return bool  Whether user can or not.
854
+	 * @since 4.5.0
855
+	 *
856
+	 */
857
+	public function current_user_can(string $cap, string $context, int $id = 0): bool
858
+	{
859
+		// apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
860
+		$filtered_cap = apply_filters('FHEE__EE_Capabilities__current_user_can__cap__' . $context, $cap, $id);
861
+		$filtered_cap = apply_filters(
862
+			'FHEE__EE_Capabilities__current_user_can__cap',
863
+			$filtered_cap,
864
+			$context,
865
+			$cap,
866
+			$id
867
+		);
868
+		return ! empty($id)
869
+			? current_user_can($filtered_cap, $id)
870
+			: current_user_can($filtered_cap);
871
+	}
872
+
873
+
874
+	/**
875
+	 * This is a wrapper for the WP user_can() function and follows the same style as the other wrappers in this class.
876
+	 *
877
+	 * @param int|WP_User $user    Either the user_id or a WP_User object
878
+	 * @param string      $cap     The capability string being checked
879
+	 * @param string      $context The context where the user_can is being called from (used in filters).
880
+	 * @param int         $id      Optional. Id for item where user_can is being called from ( used in map_meta_cap()
881
+	 *                             filters)
882
+	 *
883
+	 * @return bool Whether user can or not.
884
+	 */
885
+	public function user_can($user, string $cap, string $context, int $id = 0): bool
886
+	{
887
+		// apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
888
+		$filtered_cap = apply_filters('FHEE__EE_Capabilities__user_can__cap__' . $context, $cap, $user, $id);
889
+		$filtered_cap = apply_filters(
890
+			'FHEE__EE_Capabilities__user_can__cap',
891
+			$filtered_cap,
892
+			$context,
893
+			$cap,
894
+			$user,
895
+			$id
896
+		);
897
+		return ! empty($id)
898
+			? user_can($user, $filtered_cap, $id)
899
+			: user_can($user, $filtered_cap);
900
+	}
901
+
902
+
903
+	/**
904
+	 * Wrapper for the native WP current_user_can_for_blog() method.
905
+	 * This is provided as a handy method for a couple things:
906
+	 * 1. Using the context string it allows for targeted filtering by addons for a specific check (without having to
907
+	 * write those filters wherever current_user_can is called).
908
+	 * 2. Explicit passing of $id from a given context ( useful in the cases of map_meta_cap filters )
909
+	 *
910
+	 * @param int    $blog_id The blog id that is being checked for.
911
+	 * @param string $cap     The cap being checked.
912
+	 * @param string $context The context where the current_user_can is being called from.
913
+	 * @param int    $id      Optional. Id for item where current_user_can is being called from (used in map_meta_cap()
914
+	 *                        filters.
915
+	 *
916
+	 * @return bool  Whether user can or not.
917
+	 * @since 4.5.0
918
+	 *
919
+	 */
920
+	public function current_user_can_for_blog(int $blog_id, string $cap, string $context, int $id = 0): bool
921
+	{
922
+		$user_can = ! empty($id)
923
+			? current_user_can_for_blog($blog_id, $cap, $id)
924
+			: current_user_can($blog_id, $cap);
925
+		// apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
926
+		$user_can = apply_filters(
927
+			'FHEE__EE_Capabilities__current_user_can_for_blog__user_can__' . $context,
928
+			$user_can,
929
+			$blog_id,
930
+			$cap,
931
+			$id
932
+		);
933
+		return apply_filters(
934
+			'FHEE__EE_Capabilities__current_user_can_for_blog__user_can',
935
+			$user_can,
936
+			$context,
937
+			$blog_id,
938
+			$cap,
939
+			$id
940
+		);
941
+	}
942
+
943
+
944
+	/**
945
+	 * This helper method just returns an array of registered EE capabilities.
946
+	 *
947
+	 * @param string|null $role If empty then the entire role/capability map is returned.
948
+	 *                          Otherwise just the capabilities for the given role are returned.
949
+	 * @return array
950
+	 * @throws EE_Error
951
+	 * @since 4.5.0
952
+	 */
953
+	public function get_ee_capabilities(?string $role = EE_Capabilities::ROLE_ADMINISTRATOR): array
954
+	{
955
+		if (! $this->initialized) {
956
+			$this->init_caps();
957
+		}
958
+		if ($role === '') {
959
+			return $this->capabilities_map;
960
+		}
961
+		return $this->capabilities_map[ $role ] ?? [];
962
+	}
963
+
964
+
965
+	/**
966
+	 * @param bool  $reset      If you need to reset Event Espresso's capabilities,
967
+	 *                          then please use the init_caps() method with the "$reset" parameter set to "true"
968
+	 * @param array $caps_map   Optional.
969
+	 *                          Can be used to send a custom map of roles and capabilities for setting them up.
970
+	 *                          Note that this should ONLY be called on activation hook or some other one-time
971
+	 *                          task otherwise the caps will be added on every request.
972
+	 * @return void
973
+	 * @throws EE_Error
974
+	 * @deprecated 4.9.42
975
+	 */
976
+	public function init_role_caps(bool $reset = false, array $caps_map = [])
977
+	{
978
+		// If this method is called directly and reset is set as 'true',
979
+		// then display a doing it wrong notice, because we want resets to go through init_caps()
980
+		// to guarantee that everything is set up correctly.
981
+		// This prevents the capabilities map getting reset incorrectly by direct calls to this method.
982
+		if ($reset) {
983
+			EE_Error::doing_it_wrong(
984
+				__METHOD__,
985
+				sprintf(
986
+					esc_html__(
987
+						'The "%1$s" parameter for the "%2$s" method is deprecated. If you need to reset Event Espresso\'s capabilities, then please use the "%3$s" method with the "%1$s" parameter set to "%4$s".',
988
+						'event_espresso'
989
+					),
990
+					'$reset',
991
+					__METHOD__ . '()',
992
+					'EE_Capabilities::init_caps()',
993
+					'true'
994
+				),
995
+				'4.9.42',
996
+				'5.0.0'
997
+			);
998
+		}
999
+		$this->addCaps($caps_map);
1000
+	}
1001 1001
 }
1002 1002
 
1003 1003
 
@@ -1013,158 +1013,158 @@  discard block
 block discarded – undo
1013 1013
  */
1014 1014
 abstract class EE_Meta_Capability_Map
1015 1015
 {
1016
-    /**
1017
-     * @var EEM_Base
1018
-     */
1019
-    protected $_model;
1020
-
1021
-    /**
1022
-     * @var string
1023
-     */
1024
-    protected $_model_name;
1025
-
1026
-    /**
1027
-     * @var string
1028
-     */
1029
-    public $meta_cap;
1030
-
1031
-    /**
1032
-     * @var string
1033
-     */
1034
-    public $published_cap = '';
1035
-
1036
-    /**
1037
-     * @var string
1038
-     */
1039
-    public $others_cap = '';
1040
-
1041
-    /**
1042
-     * @var string
1043
-     */
1044
-    public $private_cap = '';
1045
-
1046
-
1047
-    /**
1048
-     * constructor.
1049
-     * Receives the setup arguments for the map.
1050
-     *
1051
-     * @param string $meta_cap   What meta capability is this mapping.
1052
-     * @param array  $map_values array {
1053
-     *                           //array of values that MUST match a count of 4.  It's okay to send an empty string for
1054
-     *                           capabilities that don't get mapped to.
1055
-     *
1056
-     * @type         $map_values [0] string A string representing the model name. Required.  String's
1057
-     *                               should always be used when Menu Maps are registered via the
1058
-     *                               plugin API as models are not allowed to be instantiated when
1059
-     *                               in maintenance mode 2 (migrations).
1060
-     * @type         $map_values [1] string represents the capability used for published. Optional.
1061
-     * @type         $map_values [2] string represents the capability used for "others". Optional.
1062
-     * @type         $map_values [3] string represents the capability used for private. Optional.
1063
-     *                               }
1064
-     * @throws EE_Error
1065
-     * @since                        4.5.0
1066
-     *
1067
-     */
1068
-    public function __construct(string $meta_cap, array $map_values)
1069
-    {
1070
-        $this->meta_cap = $meta_cap;
1071
-        // verify there are four args in the $map_values array;
1072
-        if (count($map_values) !== 4) {
1073
-            throw new EE_Error(
1074
-                sprintf(
1075
-                    esc_html__(
1076
-                        'Incoming $map_values array should have a count of four values in it.  This is what was given: %s',
1077
-                        'event_espresso'
1078
-                    ),
1079
-                    '<br>' . print_r($map_values, true)
1080
-                )
1081
-            );
1082
-        }
1083
-        // set properties
1084
-        $this->_model        = null;
1085
-        $this->_model_name   = $map_values[0];
1086
-        $this->published_cap = (string) $map_values[1];
1087
-        $this->others_cap    = (string) $map_values[2];
1088
-        $this->private_cap   = (string) $map_values[3];
1089
-    }
1090
-
1091
-
1092
-    /**
1093
-     * Makes it so this object stops filtering caps
1094
-     */
1095
-    public function remove_filters()
1096
-    {
1097
-        remove_filter('map_meta_cap', [$this, 'map_meta_caps']);
1098
-    }
1099
-
1100
-
1101
-    /**
1102
-     * This method ensures that the $model property is converted from the model name string to a proper EEM_Base class
1103
-     *
1104
-     * @return void
1105
-     * @throws EE_Error
1106
-     * @throws ReflectionException
1107
-     * @since 4.5.0
1108
-     */
1109
-    public function ensure_is_model()
1110
-    {
1111
-        // is it already instantiated?
1112
-        if ($this->_model instanceof EEM_Base) {
1113
-            return;
1114
-        }
1115
-        // ensure model name is string
1116
-        $this->_model_name = (string) $this->_model_name;
1117
-        // error proof if the name has EEM in it
1118
-        $this->_model_name = str_replace('EEM', '', $this->_model_name);
1119
-        $this->_model      = EE_Registry::instance()->load_model($this->_model_name);
1120
-        if (! $this->_model instanceof EEM_Base) {
1121
-            throw new EE_Error(
1122
-                sprintf(
1123
-                    esc_html__(
1124
-                        'This string passed in to %s to represent a EEM_Base model class was not able to be used to instantiate the class.   Please ensure that the string is a match for the EEM_Base model name (not including the EEM_ part). This was given: %s',
1125
-                        'event_espresso'
1126
-                    ),
1127
-                    get_class($this),
1128
-                    $this->_model
1129
-                )
1130
-            );
1131
-        }
1132
-    }
1133
-
1134
-
1135
-    /**
1136
-     *
1137
-     * @param $caps
1138
-     * @param $cap
1139
-     * @param $user_id
1140
-     * @param $args
1141
-     *
1142
-     * @return array
1143
-     * @since 4.6.x
1144
-     *
1145
-     * @see   EE_Meta_Capability_Map::_map_meta_caps() for docs on params.
1146
-     */
1147
-    public function map_meta_caps($caps, $cap, $user_id, $args): array
1148
-    {
1149
-        return $this->_map_meta_caps($caps, $cap, (int) $user_id, $args);
1150
-    }
1151
-
1152
-
1153
-    /**
1154
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1155
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1156
-     *
1157
-     * @param array  $caps    actual users capabilities
1158
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1159
-     * @param int    $user_id The user id
1160
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1161
-     *
1162
-     * @return array   actual users capabilities
1163
-     * @see   wp-includes/capabilities.php
1164
-     *
1165
-     * @since 4.5.0
1166
-     */
1167
-    abstract protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array;
1016
+	/**
1017
+	 * @var EEM_Base
1018
+	 */
1019
+	protected $_model;
1020
+
1021
+	/**
1022
+	 * @var string
1023
+	 */
1024
+	protected $_model_name;
1025
+
1026
+	/**
1027
+	 * @var string
1028
+	 */
1029
+	public $meta_cap;
1030
+
1031
+	/**
1032
+	 * @var string
1033
+	 */
1034
+	public $published_cap = '';
1035
+
1036
+	/**
1037
+	 * @var string
1038
+	 */
1039
+	public $others_cap = '';
1040
+
1041
+	/**
1042
+	 * @var string
1043
+	 */
1044
+	public $private_cap = '';
1045
+
1046
+
1047
+	/**
1048
+	 * constructor.
1049
+	 * Receives the setup arguments for the map.
1050
+	 *
1051
+	 * @param string $meta_cap   What meta capability is this mapping.
1052
+	 * @param array  $map_values array {
1053
+	 *                           //array of values that MUST match a count of 4.  It's okay to send an empty string for
1054
+	 *                           capabilities that don't get mapped to.
1055
+	 *
1056
+	 * @type         $map_values [0] string A string representing the model name. Required.  String's
1057
+	 *                               should always be used when Menu Maps are registered via the
1058
+	 *                               plugin API as models are not allowed to be instantiated when
1059
+	 *                               in maintenance mode 2 (migrations).
1060
+	 * @type         $map_values [1] string represents the capability used for published. Optional.
1061
+	 * @type         $map_values [2] string represents the capability used for "others". Optional.
1062
+	 * @type         $map_values [3] string represents the capability used for private. Optional.
1063
+	 *                               }
1064
+	 * @throws EE_Error
1065
+	 * @since                        4.5.0
1066
+	 *
1067
+	 */
1068
+	public function __construct(string $meta_cap, array $map_values)
1069
+	{
1070
+		$this->meta_cap = $meta_cap;
1071
+		// verify there are four args in the $map_values array;
1072
+		if (count($map_values) !== 4) {
1073
+			throw new EE_Error(
1074
+				sprintf(
1075
+					esc_html__(
1076
+						'Incoming $map_values array should have a count of four values in it.  This is what was given: %s',
1077
+						'event_espresso'
1078
+					),
1079
+					'<br>' . print_r($map_values, true)
1080
+				)
1081
+			);
1082
+		}
1083
+		// set properties
1084
+		$this->_model        = null;
1085
+		$this->_model_name   = $map_values[0];
1086
+		$this->published_cap = (string) $map_values[1];
1087
+		$this->others_cap    = (string) $map_values[2];
1088
+		$this->private_cap   = (string) $map_values[3];
1089
+	}
1090
+
1091
+
1092
+	/**
1093
+	 * Makes it so this object stops filtering caps
1094
+	 */
1095
+	public function remove_filters()
1096
+	{
1097
+		remove_filter('map_meta_cap', [$this, 'map_meta_caps']);
1098
+	}
1099
+
1100
+
1101
+	/**
1102
+	 * This method ensures that the $model property is converted from the model name string to a proper EEM_Base class
1103
+	 *
1104
+	 * @return void
1105
+	 * @throws EE_Error
1106
+	 * @throws ReflectionException
1107
+	 * @since 4.5.0
1108
+	 */
1109
+	public function ensure_is_model()
1110
+	{
1111
+		// is it already instantiated?
1112
+		if ($this->_model instanceof EEM_Base) {
1113
+			return;
1114
+		}
1115
+		// ensure model name is string
1116
+		$this->_model_name = (string) $this->_model_name;
1117
+		// error proof if the name has EEM in it
1118
+		$this->_model_name = str_replace('EEM', '', $this->_model_name);
1119
+		$this->_model      = EE_Registry::instance()->load_model($this->_model_name);
1120
+		if (! $this->_model instanceof EEM_Base) {
1121
+			throw new EE_Error(
1122
+				sprintf(
1123
+					esc_html__(
1124
+						'This string passed in to %s to represent a EEM_Base model class was not able to be used to instantiate the class.   Please ensure that the string is a match for the EEM_Base model name (not including the EEM_ part). This was given: %s',
1125
+						'event_espresso'
1126
+					),
1127
+					get_class($this),
1128
+					$this->_model
1129
+				)
1130
+			);
1131
+		}
1132
+	}
1133
+
1134
+
1135
+	/**
1136
+	 *
1137
+	 * @param $caps
1138
+	 * @param $cap
1139
+	 * @param $user_id
1140
+	 * @param $args
1141
+	 *
1142
+	 * @return array
1143
+	 * @since 4.6.x
1144
+	 *
1145
+	 * @see   EE_Meta_Capability_Map::_map_meta_caps() for docs on params.
1146
+	 */
1147
+	public function map_meta_caps($caps, $cap, $user_id, $args): array
1148
+	{
1149
+		return $this->_map_meta_caps($caps, $cap, (int) $user_id, $args);
1150
+	}
1151
+
1152
+
1153
+	/**
1154
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1155
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1156
+	 *
1157
+	 * @param array  $caps    actual users capabilities
1158
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1159
+	 * @param int    $user_id The user id
1160
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1161
+	 *
1162
+	 * @return array   actual users capabilities
1163
+	 * @see   wp-includes/capabilities.php
1164
+	 *
1165
+	 * @since 4.5.0
1166
+	 */
1167
+	abstract protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array;
1168 1168
 }
1169 1169
 
1170 1170
 
@@ -1179,80 +1179,80 @@  discard block
 block discarded – undo
1179 1179
  */
1180 1180
 class EE_Meta_Capability_Map_Edit extends EE_Meta_Capability_Map
1181 1181
 {
1182
-    /**
1183
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1184
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1185
-     *
1186
-     * @param array  $caps    actual users capabilities
1187
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1188
-     * @param int    $user_id The user id
1189
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1190
-     *
1191
-     * @return array   actual users capabilities
1192
-     * @throws EE_Error
1193
-     * @throws EE_Error
1194
-     * @since 4.5.0
1195
-     * @see   wp-includes/capabilities.php
1196
-     *
1197
-     */
1198
-    protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1199
-    {
1200
-        // only process if we're checking our mapped_cap
1201
-        if ($cap !== $this->meta_cap) {
1202
-            return $caps;
1203
-        }
1204
-
1205
-        // okay it is a meta cap so let's first remove that cap from the $caps array.
1206
-        if (($key = array_search($cap, $caps)) !== false) {
1207
-            unset($caps[ $key ]);
1208
-        }
1209
-
1210
-        /** @var EE_Base_Class $obj */
1211
-        $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1212
-        // if no obj then let's just do cap
1213
-        if (! $obj instanceof EE_Base_Class) {
1214
-            $caps[] = 'do_not_allow';
1215
-            return $caps;
1216
-        }
1217
-        $caps[] = $cap . 's';
1218
-        if ($obj instanceof EE_CPT_Base) {
1219
-            // if the item author is set and the user is the author...
1220
-            if ($obj->wp_user() && $user_id === $obj->wp_user()) {
1221
-                // if obj is published...
1222
-                if ($obj->status() === 'publish') {
1223
-                    $caps[] = $this->published_cap;
1224
-                }
1225
-            } else {
1226
-                // the user is trying to edit someone else's obj
1227
-                if (! empty($this->others_cap)) {
1228
-                    $caps[] = $this->others_cap;
1229
-                }
1230
-                if (! empty($this->published_cap) && $obj->status() === 'publish') {
1231
-                    $caps[] = $this->published_cap;
1232
-                } elseif (! empty($this->private_cap) && $obj->status() === 'private') {
1233
-                    $caps[] = $this->private_cap;
1234
-                }
1235
-            }
1236
-        } else {
1237
-            // not a cpt object so handled differently
1238
-            $has_cap = false;
1239
-            try {
1240
-                $has_cap = method_exists($obj, 'wp_user')
1241
-                           && $obj->wp_user()
1242
-                           && $obj->wp_user() === $user_id;
1243
-            } catch (Exception $e) {
1244
-                if (WP_DEBUG) {
1245
-                    EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1246
-                }
1247
-            }
1248
-            if (! $has_cap) {
1249
-                if (! empty($this->others_cap)) {
1250
-                    $caps[] = $this->others_cap;
1251
-                }
1252
-            }
1253
-        }
1254
-        return $caps;
1255
-    }
1182
+	/**
1183
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1184
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1185
+	 *
1186
+	 * @param array  $caps    actual users capabilities
1187
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1188
+	 * @param int    $user_id The user id
1189
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1190
+	 *
1191
+	 * @return array   actual users capabilities
1192
+	 * @throws EE_Error
1193
+	 * @throws EE_Error
1194
+	 * @since 4.5.0
1195
+	 * @see   wp-includes/capabilities.php
1196
+	 *
1197
+	 */
1198
+	protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1199
+	{
1200
+		// only process if we're checking our mapped_cap
1201
+		if ($cap !== $this->meta_cap) {
1202
+			return $caps;
1203
+		}
1204
+
1205
+		// okay it is a meta cap so let's first remove that cap from the $caps array.
1206
+		if (($key = array_search($cap, $caps)) !== false) {
1207
+			unset($caps[ $key ]);
1208
+		}
1209
+
1210
+		/** @var EE_Base_Class $obj */
1211
+		$obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1212
+		// if no obj then let's just do cap
1213
+		if (! $obj instanceof EE_Base_Class) {
1214
+			$caps[] = 'do_not_allow';
1215
+			return $caps;
1216
+		}
1217
+		$caps[] = $cap . 's';
1218
+		if ($obj instanceof EE_CPT_Base) {
1219
+			// if the item author is set and the user is the author...
1220
+			if ($obj->wp_user() && $user_id === $obj->wp_user()) {
1221
+				// if obj is published...
1222
+				if ($obj->status() === 'publish') {
1223
+					$caps[] = $this->published_cap;
1224
+				}
1225
+			} else {
1226
+				// the user is trying to edit someone else's obj
1227
+				if (! empty($this->others_cap)) {
1228
+					$caps[] = $this->others_cap;
1229
+				}
1230
+				if (! empty($this->published_cap) && $obj->status() === 'publish') {
1231
+					$caps[] = $this->published_cap;
1232
+				} elseif (! empty($this->private_cap) && $obj->status() === 'private') {
1233
+					$caps[] = $this->private_cap;
1234
+				}
1235
+			}
1236
+		} else {
1237
+			// not a cpt object so handled differently
1238
+			$has_cap = false;
1239
+			try {
1240
+				$has_cap = method_exists($obj, 'wp_user')
1241
+						   && $obj->wp_user()
1242
+						   && $obj->wp_user() === $user_id;
1243
+			} catch (Exception $e) {
1244
+				if (WP_DEBUG) {
1245
+					EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1246
+				}
1247
+			}
1248
+			if (! $has_cap) {
1249
+				if (! empty($this->others_cap)) {
1250
+					$caps[] = $this->others_cap;
1251
+				}
1252
+			}
1253
+		}
1254
+		return $caps;
1255
+	}
1256 1256
 }
1257 1257
 
1258 1258
 
@@ -1268,25 +1268,25 @@  discard block
 block discarded – undo
1268 1268
  */
1269 1269
 class EE_Meta_Capability_Map_Delete extends EE_Meta_Capability_Map_Edit
1270 1270
 {
1271
-    /**
1272
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1273
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1274
-     *
1275
-     * @param array  $caps    actual users capabilities
1276
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1277
-     * @param int    $user_id The user id
1278
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1279
-     *
1280
-     * @return array   actual users capabilities
1281
-     * @throws EE_Error
1282
-     * @since 4.5.0
1283
-     * @see   wp-includes/capabilities.php
1284
-     *
1285
-     */
1286
-    protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1287
-    {
1288
-        return parent::_map_meta_caps($caps, $cap, $user_id, $args);
1289
-    }
1271
+	/**
1272
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1273
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1274
+	 *
1275
+	 * @param array  $caps    actual users capabilities
1276
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1277
+	 * @param int    $user_id The user id
1278
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1279
+	 *
1280
+	 * @return array   actual users capabilities
1281
+	 * @throws EE_Error
1282
+	 * @since 4.5.0
1283
+	 * @see   wp-includes/capabilities.php
1284
+	 *
1285
+	 */
1286
+	protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1287
+	{
1288
+		return parent::_map_meta_caps($caps, $cap, $user_id, $args);
1289
+	}
1290 1290
 }
1291 1291
 
1292 1292
 
@@ -1301,85 +1301,85 @@  discard block
 block discarded – undo
1301 1301
  */
1302 1302
 class EE_Meta_Capability_Map_Read extends EE_Meta_Capability_Map
1303 1303
 {
1304
-    /**
1305
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1306
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1307
-     *
1308
-     * @param array  $caps    actual users capabilities
1309
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1310
-     * @param int    $user_id The user id
1311
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1312
-     *
1313
-     * @return array   actual users capabilities
1314
-     * @throws EE_Error
1315
-     * @throws EE_Error
1316
-     * @since 4.5.0
1317
-     * @see   wp-includes/capabilities.php
1318
-     *
1319
-     */
1320
-    protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1321
-    {
1322
-        // only process if we're checking our mapped cap;
1323
-        if ($cap !== $this->meta_cap) {
1324
-            return $caps;
1325
-        }
1326
-
1327
-        // okay it is a meta cap so let's first remove that cap from the $caps array.
1328
-        if (($key = array_search($cap, $caps)) !== false) {
1329
-            unset($caps[ $key ]);
1330
-        }
1331
-
1332
-        $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1333
-        // if no obj then let's just do cap
1334
-        if (! $obj instanceof EE_Base_Class) {
1335
-            $caps[] = 'do_not_allow';
1336
-            return $caps;
1337
-        }
1338
-
1339
-        $caps[] = $cap . 's';
1340
-        if ($obj instanceof EE_CPT_Base) {
1341
-            $status_obj = get_post_status_object($obj->status());
1342
-            if ($status_obj->public) {
1343
-                return $caps;
1344
-            }
1345
-            // if the item author is set and the user is not the author...
1346
-            if ($obj->wp_user() && $obj->wp_user() !== $user_id) {
1347
-                if (! empty($this->others_cap)) {
1348
-                    $caps[] = $this->others_cap;
1349
-                }
1350
-            }
1351
-            // yes this means that if users created the private post, they are able to see it regardless of private cap.
1352
-            if (
1353
-                $status_obj->private
1354
-                && ! empty($this->private_cap)
1355
-                && $obj->wp_user() !== $user_id
1356
-            ) {
1357
-                // the user is trying to view a private object for an object they don't own.
1358
-                $caps[] = $this->private_cap;
1359
-            }
1360
-        } else {
1361
-            // not a cpt object so handled differently
1362
-            $has_cap = false;
1363
-            try {
1364
-                $has_cap = method_exists($obj, 'wp_user')
1365
-                           && $obj->wp_user()
1366
-                           && $obj->wp_user() === $user_id;
1367
-            } catch (Exception $e) {
1368
-                if (WP_DEBUG) {
1369
-                    EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1370
-                }
1371
-            }
1372
-            if (! $has_cap) {
1373
-                if (! empty($this->private_cap)) {
1374
-                    $caps[] = $this->private_cap;
1375
-                }
1376
-                if (! empty($this->others_cap)) {
1377
-                    $caps[] = $this->others_cap;
1378
-                }
1379
-            }
1380
-        }
1381
-        return $caps;
1382
-    }
1304
+	/**
1305
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1306
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1307
+	 *
1308
+	 * @param array  $caps    actual users capabilities
1309
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1310
+	 * @param int    $user_id The user id
1311
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1312
+	 *
1313
+	 * @return array   actual users capabilities
1314
+	 * @throws EE_Error
1315
+	 * @throws EE_Error
1316
+	 * @since 4.5.0
1317
+	 * @see   wp-includes/capabilities.php
1318
+	 *
1319
+	 */
1320
+	protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1321
+	{
1322
+		// only process if we're checking our mapped cap;
1323
+		if ($cap !== $this->meta_cap) {
1324
+			return $caps;
1325
+		}
1326
+
1327
+		// okay it is a meta cap so let's first remove that cap from the $caps array.
1328
+		if (($key = array_search($cap, $caps)) !== false) {
1329
+			unset($caps[ $key ]);
1330
+		}
1331
+
1332
+		$obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1333
+		// if no obj then let's just do cap
1334
+		if (! $obj instanceof EE_Base_Class) {
1335
+			$caps[] = 'do_not_allow';
1336
+			return $caps;
1337
+		}
1338
+
1339
+		$caps[] = $cap . 's';
1340
+		if ($obj instanceof EE_CPT_Base) {
1341
+			$status_obj = get_post_status_object($obj->status());
1342
+			if ($status_obj->public) {
1343
+				return $caps;
1344
+			}
1345
+			// if the item author is set and the user is not the author...
1346
+			if ($obj->wp_user() && $obj->wp_user() !== $user_id) {
1347
+				if (! empty($this->others_cap)) {
1348
+					$caps[] = $this->others_cap;
1349
+				}
1350
+			}
1351
+			// yes this means that if users created the private post, they are able to see it regardless of private cap.
1352
+			if (
1353
+				$status_obj->private
1354
+				&& ! empty($this->private_cap)
1355
+				&& $obj->wp_user() !== $user_id
1356
+			) {
1357
+				// the user is trying to view a private object for an object they don't own.
1358
+				$caps[] = $this->private_cap;
1359
+			}
1360
+		} else {
1361
+			// not a cpt object so handled differently
1362
+			$has_cap = false;
1363
+			try {
1364
+				$has_cap = method_exists($obj, 'wp_user')
1365
+						   && $obj->wp_user()
1366
+						   && $obj->wp_user() === $user_id;
1367
+			} catch (Exception $e) {
1368
+				if (WP_DEBUG) {
1369
+					EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1370
+				}
1371
+			}
1372
+			if (! $has_cap) {
1373
+				if (! empty($this->private_cap)) {
1374
+					$caps[] = $this->private_cap;
1375
+				}
1376
+				if (! empty($this->others_cap)) {
1377
+					$caps[] = $this->others_cap;
1378
+				}
1379
+			}
1380
+		}
1381
+		return $caps;
1382
+	}
1383 1383
 }
1384 1384
 
1385 1385
 
@@ -1395,55 +1395,55 @@  discard block
 block discarded – undo
1395 1395
  */
1396 1396
 class EE_Meta_Capability_Map_Messages_Cap extends EE_Meta_Capability_Map
1397 1397
 {
1398
-    /**
1399
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1400
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1401
-     *
1402
-     * @param array  $caps    actual users capabilities
1403
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1404
-     * @param int    $user_id The user id
1405
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1406
-     *
1407
-     * @return array   actual users capabilities
1408
-     * @throws EE_Error
1409
-     * @throws EE_Error
1410
-     * @since 4.5.0
1411
-     * @see   wp-includes/capabilities.php
1412
-     *
1413
-     */
1414
-    protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1415
-    {
1416
-        // only process if we're checking our mapped_cap
1417
-        if ($cap !== $this->meta_cap) {
1418
-            return $caps;
1419
-        }
1420
-
1421
-        // okay it is a meta cap so let's first remove that cap from the $caps array.
1422
-        if (($key = array_search($cap, $caps)) !== false) {
1423
-            unset($caps[ $key ]);
1424
-        }
1425
-
1426
-        $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1427
-        // if no obj then let's just do cap
1428
-        if (! $obj instanceof EE_Message_Template_Group) {
1429
-            $caps[] = 'do_not_allow';
1430
-            return $caps;
1431
-        }
1432
-        $caps[]    = $cap . 's';
1433
-        $is_global = $obj->is_global();
1434
-        if ($obj->wp_user() && $obj->wp_user() === $user_id) {
1435
-            if ($is_global) {
1436
-                $caps[] = $this->private_cap;
1437
-            }
1438
-        } else {
1439
-            if ($is_global) {
1440
-                $caps[] = $this->private_cap;
1441
-            } else {
1442
-                $caps[] = $this->others_cap;
1443
-            }
1444
-        }
1445
-        return $caps;
1446
-    }
1398
+	/**
1399
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1400
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1401
+	 *
1402
+	 * @param array  $caps    actual users capabilities
1403
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1404
+	 * @param int    $user_id The user id
1405
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1406
+	 *
1407
+	 * @return array   actual users capabilities
1408
+	 * @throws EE_Error
1409
+	 * @throws EE_Error
1410
+	 * @since 4.5.0
1411
+	 * @see   wp-includes/capabilities.php
1412
+	 *
1413
+	 */
1414
+	protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1415
+	{
1416
+		// only process if we're checking our mapped_cap
1417
+		if ($cap !== $this->meta_cap) {
1418
+			return $caps;
1419
+		}
1420
+
1421
+		// okay it is a meta cap so let's first remove that cap from the $caps array.
1422
+		if (($key = array_search($cap, $caps)) !== false) {
1423
+			unset($caps[ $key ]);
1424
+		}
1425
+
1426
+		$obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1427
+		// if no obj then let's just do cap
1428
+		if (! $obj instanceof EE_Message_Template_Group) {
1429
+			$caps[] = 'do_not_allow';
1430
+			return $caps;
1431
+		}
1432
+		$caps[]    = $cap . 's';
1433
+		$is_global = $obj->is_global();
1434
+		if ($obj->wp_user() && $obj->wp_user() === $user_id) {
1435
+			if ($is_global) {
1436
+				$caps[] = $this->private_cap;
1437
+			}
1438
+		} else {
1439
+			if ($is_global) {
1440
+				$caps[] = $this->private_cap;
1441
+			} else {
1442
+				$caps[] = $this->others_cap;
1443
+			}
1444
+		}
1445
+		return $caps;
1446
+	}
1447 1447
 }
1448 1448
 
1449 1449
 
@@ -1459,42 +1459,42 @@  discard block
 block discarded – undo
1459 1459
  */
1460 1460
 class EE_Meta_Capability_Map_Registration_Form_Cap extends EE_Meta_Capability_Map
1461 1461
 {
1462
-    /**
1463
-     * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1464
-     * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1465
-     *
1466
-     * @param array  $caps    actual users capabilities
1467
-     * @param string $cap     initial capability name that is being checked (the "map" key)
1468
-     * @param int    $user_id The user id
1469
-     * @param array  $args    Adds context to the cap. Typically the object ID.
1470
-     * @return array   actual users capabilities
1471
-     * @throws EE_Error
1472
-     * @throws EE_Error
1473
-     * @since 4.5.0
1474
-     * @see   wp-includes/capabilities.php
1475
-     */
1476
-    protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1477
-    {
1478
-        // only process if we're checking our mapped_cap
1479
-        if ($cap !== $this->meta_cap) {
1480
-            return $caps;
1481
-        }
1482
-        // okay it is a meta cap so let's first remove that cap from the $caps array.
1483
-        if (($key = array_search($cap, $caps)) !== false) {
1484
-            unset($caps[ $key ]);
1485
-        }
1486
-        $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1487
-        // if no obj then let's just do cap
1488
-        if (! $obj instanceof EE_Base_Class) {
1489
-            $caps[] = 'do_not_allow';
1490
-            return $caps;
1491
-        }
1492
-        $caps[]    = $cap . 's';
1493
-        $is_system = $obj instanceof EE_Question_Group ? $obj->system_group() : false;
1494
-        $is_system = $obj instanceof EE_Question ? $obj->is_system_question() : $is_system;
1495
-        if ($is_system) {
1496
-            $caps[] = $this->private_cap;
1497
-        }
1498
-        return $caps;
1499
-    }
1462
+	/**
1463
+	 * This is the callback for the wp map_meta_caps() function which allows for ensuring certain caps that act as a
1464
+	 * "meta" for other caps ( i.e. ee_edit_event is a meta for ee_edit_others_events ) work as expected.
1465
+	 *
1466
+	 * @param array  $caps    actual users capabilities
1467
+	 * @param string $cap     initial capability name that is being checked (the "map" key)
1468
+	 * @param int    $user_id The user id
1469
+	 * @param array  $args    Adds context to the cap. Typically the object ID.
1470
+	 * @return array   actual users capabilities
1471
+	 * @throws EE_Error
1472
+	 * @throws EE_Error
1473
+	 * @since 4.5.0
1474
+	 * @see   wp-includes/capabilities.php
1475
+	 */
1476
+	protected function _map_meta_caps(array $caps, string $cap, int $user_id, array $args): array
1477
+	{
1478
+		// only process if we're checking our mapped_cap
1479
+		if ($cap !== $this->meta_cap) {
1480
+			return $caps;
1481
+		}
1482
+		// okay it is a meta cap so let's first remove that cap from the $caps array.
1483
+		if (($key = array_search($cap, $caps)) !== false) {
1484
+			unset($caps[ $key ]);
1485
+		}
1486
+		$obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1487
+		// if no obj then let's just do cap
1488
+		if (! $obj instanceof EE_Base_Class) {
1489
+			$caps[] = 'do_not_allow';
1490
+			return $caps;
1491
+		}
1492
+		$caps[]    = $cap . 's';
1493
+		$is_system = $obj instanceof EE_Question_Group ? $obj->system_group() : false;
1494
+		$is_system = $obj instanceof EE_Question ? $obj->is_system_question() : $is_system;
1495
+		if ($is_system) {
1496
+			$caps[] = $this->private_cap;
1497
+		}
1498
+		return $caps;
1499
+	}
1500 1500
 }
Please login to merge, or discard this patch.
Spacing   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -84,7 +84,7 @@  discard block
 block discarded – undo
84 84
     public static function instance(): EE_Capabilities
85 85
     {
86 86
         // check if instantiated, and if not do so.
87
-        if (! self::$_instance instanceof EE_Capabilities) {
87
+        if ( ! self::$_instance instanceof EE_Capabilities) {
88 88
             self::$_instance = new self();
89 89
         }
90 90
         return self::$_instance;
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
      */
115 115
     public function init_caps(?bool $reset = false): bool
116 116
     {
117
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
117
+        if ( ! EE_Maintenance_Mode::instance()->models_can_query()) {
118 118
             return false;
119 119
         }
120 120
         $this->reset = filter_var($reset, FILTER_VALIDATE_BOOLEAN);
@@ -159,7 +159,7 @@  discard block
 block discarded – undo
159 159
             $this->_get_default_meta_caps_array()
160 160
         );
161 161
         // add filter for map_meta_caps but only if models can query.
162
-        if (! has_filter('map_meta_cap', [$this, 'map_meta_caps'])) {
162
+        if ( ! has_filter('map_meta_cap', [$this, 'map_meta_caps'])) {
163 163
             add_filter('map_meta_cap', [$this, 'map_meta_caps'], 10, 4);
164 164
         }
165 165
     }
@@ -319,11 +319,11 @@  discard block
 block discarded – undo
319 319
         if (did_action('AHEE__EE_System__load_espresso_addons__complete')) {
320 320
             // loop through our _meta_caps array
321 321
             foreach ($this->_meta_caps as $meta_map) {
322
-                if (! $meta_map instanceof EE_Meta_Capability_Map) {
322
+                if ( ! $meta_map instanceof EE_Meta_Capability_Map) {
323 323
                     continue;
324 324
                 }
325 325
                 // don't load models if there is no object ID in the args
326
-                if (! empty($args[0])) {
326
+                if ( ! empty($args[0])) {
327 327
                     $meta_map->ensure_is_model();
328 328
                 }
329 329
                 $caps = $meta_map->map_meta_caps($caps, $cap, $user_id, $args);
@@ -659,11 +659,11 @@  discard block
 block discarded – undo
659 659
     public function addCaps(array $capabilities_to_add): bool
660 660
     {
661 661
         // don't do anything if the capabilities map can not be initialized
662
-        if (! $this->setupCapabilitiesMap()) {
662
+        if ( ! $this->setupCapabilitiesMap()) {
663 663
             return false;
664 664
         }
665 665
         // and filter the array so others can get in on the fun during resets
666
-        $capabilities_to_add     = apply_filters(
666
+        $capabilities_to_add = apply_filters(
667 667
             'FHEE__EE_Capabilities__addCaps__capabilities_to_add',
668 668
             $capabilities_to_add,
669 669
             $this->reset,
@@ -701,7 +701,7 @@  discard block
 block discarded – undo
701 701
     public function removeCaps(array $caps_map): bool
702 702
     {
703 703
         // don't do anything if the capabilities map can not be initialized
704
-        if (! $this->setupCapabilitiesMap()) {
704
+        if ( ! $this->setupCapabilitiesMap()) {
705 705
             return false;
706 706
         }
707 707
         $update_capabilities_map = false;
@@ -765,7 +765,7 @@  discard block
 block discarded – undo
765 765
         $orig_role = $role;
766 766
         $role      = $role instanceof WP_Role ? $role : get_role($role);
767 767
         // if the role isn't available then we create it.
768
-        if (! $role instanceof WP_Role) {
768
+        if ( ! $role instanceof WP_Role) {
769 769
             // if a plugin wants to create a specific role name then they should create the role before
770 770
             // EE_Capabilities does.  Otherwise this function will create the role name from the slug:
771 771
             // - removes any `ee_` namespacing from the start of the slug.
@@ -776,12 +776,12 @@  discard block
 block discarded – undo
776 776
         }
777 777
         if ($role instanceof WP_Role) {
778 778
             // don't do anything if the capabilities map can not be initialized
779
-            if (! $this->setupCapabilitiesMap()) {
779
+            if ( ! $this->setupCapabilitiesMap()) {
780 780
                 return false;
781 781
             }
782
-            if (! $this->capHasBeenAddedToRole($role->name, $cap)) {
782
+            if ( ! $this->capHasBeenAddedToRole($role->name, $cap)) {
783 783
                 $role->add_cap($cap, $grant);
784
-                $this->capabilities_map[ $role->name ][] = $cap;
784
+                $this->capabilities_map[$role->name][] = $cap;
785 785
                 $this->updateCapabilitiesMap($update_capabilities_map);
786 786
                 return true;
787 787
             }
@@ -805,14 +805,14 @@  discard block
 block discarded – undo
805 805
     public function remove_cap_from_role($role, string $cap, bool $update_capabilities_map = true): bool
806 806
     {
807 807
         // don't do anything if the capabilities map can not be initialized
808
-        if (! $this->setupCapabilitiesMap()) {
808
+        if ( ! $this->setupCapabilitiesMap()) {
809 809
             return false;
810 810
         }
811 811
 
812 812
         $role = $role instanceof WP_Role ? $role : get_role($role);
813 813
         if ($role instanceof WP_Role && $index = $this->capHasBeenAddedToRole($role->name, $cap, true)) {
814 814
             $role->remove_cap($cap);
815
-            unset($this->capabilities_map[ $role->name ][ $index ]);
815
+            unset($this->capabilities_map[$role->name][$index]);
816 816
             $this->updateCapabilitiesMap($update_capabilities_map);
817 817
             return true;
818 818
         }
@@ -829,8 +829,8 @@  discard block
 block discarded – undo
829 829
     private function capHasBeenAddedToRole(string $role_name = '', string $cap = '', bool $get_index = false)
830 830
     {
831 831
         if (
832
-            isset($this->capabilities_map[ $role_name ])
833
-            && ($index = array_search($cap, $this->capabilities_map[ $role_name ], true)) !== false
832
+            isset($this->capabilities_map[$role_name])
833
+            && ($index = array_search($cap, $this->capabilities_map[$role_name], true)) !== false
834 834
         ) {
835 835
             return $get_index ? $index : true;
836 836
         }
@@ -857,7 +857,7 @@  discard block
 block discarded – undo
857 857
     public function current_user_can(string $cap, string $context, int $id = 0): bool
858 858
     {
859 859
         // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
860
-        $filtered_cap = apply_filters('FHEE__EE_Capabilities__current_user_can__cap__' . $context, $cap, $id);
860
+        $filtered_cap = apply_filters('FHEE__EE_Capabilities__current_user_can__cap__'.$context, $cap, $id);
861 861
         $filtered_cap = apply_filters(
862 862
             'FHEE__EE_Capabilities__current_user_can__cap',
863 863
             $filtered_cap,
@@ -885,7 +885,7 @@  discard block
 block discarded – undo
885 885
     public function user_can($user, string $cap, string $context, int $id = 0): bool
886 886
     {
887 887
         // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
888
-        $filtered_cap = apply_filters('FHEE__EE_Capabilities__user_can__cap__' . $context, $cap, $user, $id);
888
+        $filtered_cap = apply_filters('FHEE__EE_Capabilities__user_can__cap__'.$context, $cap, $user, $id);
889 889
         $filtered_cap = apply_filters(
890 890
             'FHEE__EE_Capabilities__user_can__cap',
891 891
             $filtered_cap,
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
             : current_user_can($blog_id, $cap);
925 925
         // apply filters (both a global on just the cap, and context specific.  Global overrides context specific)
926 926
         $user_can = apply_filters(
927
-            'FHEE__EE_Capabilities__current_user_can_for_blog__user_can__' . $context,
927
+            'FHEE__EE_Capabilities__current_user_can_for_blog__user_can__'.$context,
928 928
             $user_can,
929 929
             $blog_id,
930 930
             $cap,
@@ -952,13 +952,13 @@  discard block
 block discarded – undo
952 952
      */
953 953
     public function get_ee_capabilities(?string $role = EE_Capabilities::ROLE_ADMINISTRATOR): array
954 954
     {
955
-        if (! $this->initialized) {
955
+        if ( ! $this->initialized) {
956 956
             $this->init_caps();
957 957
         }
958 958
         if ($role === '') {
959 959
             return $this->capabilities_map;
960 960
         }
961
-        return $this->capabilities_map[ $role ] ?? [];
961
+        return $this->capabilities_map[$role] ?? [];
962 962
     }
963 963
 
964 964
 
@@ -988,7 +988,7 @@  discard block
 block discarded – undo
988 988
                         'event_espresso'
989 989
                     ),
990 990
                     '$reset',
991
-                    __METHOD__ . '()',
991
+                    __METHOD__.'()',
992 992
                     'EE_Capabilities::init_caps()',
993 993
                     'true'
994 994
                 ),
@@ -1076,7 +1076,7 @@  discard block
 block discarded – undo
1076 1076
                         'Incoming $map_values array should have a count of four values in it.  This is what was given: %s',
1077 1077
                         'event_espresso'
1078 1078
                     ),
1079
-                    '<br>' . print_r($map_values, true)
1079
+                    '<br>'.print_r($map_values, true)
1080 1080
                 )
1081 1081
             );
1082 1082
         }
@@ -1117,7 +1117,7 @@  discard block
 block discarded – undo
1117 1117
         // error proof if the name has EEM in it
1118 1118
         $this->_model_name = str_replace('EEM', '', $this->_model_name);
1119 1119
         $this->_model      = EE_Registry::instance()->load_model($this->_model_name);
1120
-        if (! $this->_model instanceof EEM_Base) {
1120
+        if ( ! $this->_model instanceof EEM_Base) {
1121 1121
             throw new EE_Error(
1122 1122
                 sprintf(
1123 1123
                     esc_html__(
@@ -1204,17 +1204,17 @@  discard block
 block discarded – undo
1204 1204
 
1205 1205
         // okay it is a meta cap so let's first remove that cap from the $caps array.
1206 1206
         if (($key = array_search($cap, $caps)) !== false) {
1207
-            unset($caps[ $key ]);
1207
+            unset($caps[$key]);
1208 1208
         }
1209 1209
 
1210 1210
         /** @var EE_Base_Class $obj */
1211 1211
         $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1212 1212
         // if no obj then let's just do cap
1213
-        if (! $obj instanceof EE_Base_Class) {
1213
+        if ( ! $obj instanceof EE_Base_Class) {
1214 1214
             $caps[] = 'do_not_allow';
1215 1215
             return $caps;
1216 1216
         }
1217
-        $caps[] = $cap . 's';
1217
+        $caps[] = $cap.'s';
1218 1218
         if ($obj instanceof EE_CPT_Base) {
1219 1219
             // if the item author is set and the user is the author...
1220 1220
             if ($obj->wp_user() && $user_id === $obj->wp_user()) {
@@ -1224,12 +1224,12 @@  discard block
 block discarded – undo
1224 1224
                 }
1225 1225
             } else {
1226 1226
                 // the user is trying to edit someone else's obj
1227
-                if (! empty($this->others_cap)) {
1227
+                if ( ! empty($this->others_cap)) {
1228 1228
                     $caps[] = $this->others_cap;
1229 1229
                 }
1230
-                if (! empty($this->published_cap) && $obj->status() === 'publish') {
1230
+                if ( ! empty($this->published_cap) && $obj->status() === 'publish') {
1231 1231
                     $caps[] = $this->published_cap;
1232
-                } elseif (! empty($this->private_cap) && $obj->status() === 'private') {
1232
+                } elseif ( ! empty($this->private_cap) && $obj->status() === 'private') {
1233 1233
                     $caps[] = $this->private_cap;
1234 1234
                 }
1235 1235
             }
@@ -1245,8 +1245,8 @@  discard block
 block discarded – undo
1245 1245
                     EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1246 1246
                 }
1247 1247
             }
1248
-            if (! $has_cap) {
1249
-                if (! empty($this->others_cap)) {
1248
+            if ( ! $has_cap) {
1249
+                if ( ! empty($this->others_cap)) {
1250 1250
                     $caps[] = $this->others_cap;
1251 1251
                 }
1252 1252
             }
@@ -1326,17 +1326,17 @@  discard block
 block discarded – undo
1326 1326
 
1327 1327
         // okay it is a meta cap so let's first remove that cap from the $caps array.
1328 1328
         if (($key = array_search($cap, $caps)) !== false) {
1329
-            unset($caps[ $key ]);
1329
+            unset($caps[$key]);
1330 1330
         }
1331 1331
 
1332 1332
         $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1333 1333
         // if no obj then let's just do cap
1334
-        if (! $obj instanceof EE_Base_Class) {
1334
+        if ( ! $obj instanceof EE_Base_Class) {
1335 1335
             $caps[] = 'do_not_allow';
1336 1336
             return $caps;
1337 1337
         }
1338 1338
 
1339
-        $caps[] = $cap . 's';
1339
+        $caps[] = $cap.'s';
1340 1340
         if ($obj instanceof EE_CPT_Base) {
1341 1341
             $status_obj = get_post_status_object($obj->status());
1342 1342
             if ($status_obj->public) {
@@ -1344,7 +1344,7 @@  discard block
 block discarded – undo
1344 1344
             }
1345 1345
             // if the item author is set and the user is not the author...
1346 1346
             if ($obj->wp_user() && $obj->wp_user() !== $user_id) {
1347
-                if (! empty($this->others_cap)) {
1347
+                if ( ! empty($this->others_cap)) {
1348 1348
                     $caps[] = $this->others_cap;
1349 1349
                 }
1350 1350
             }
@@ -1369,11 +1369,11 @@  discard block
 block discarded – undo
1369 1369
                     EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1370 1370
                 }
1371 1371
             }
1372
-            if (! $has_cap) {
1373
-                if (! empty($this->private_cap)) {
1372
+            if ( ! $has_cap) {
1373
+                if ( ! empty($this->private_cap)) {
1374 1374
                     $caps[] = $this->private_cap;
1375 1375
                 }
1376
-                if (! empty($this->others_cap)) {
1376
+                if ( ! empty($this->others_cap)) {
1377 1377
                     $caps[] = $this->others_cap;
1378 1378
                 }
1379 1379
             }
@@ -1420,16 +1420,16 @@  discard block
 block discarded – undo
1420 1420
 
1421 1421
         // okay it is a meta cap so let's first remove that cap from the $caps array.
1422 1422
         if (($key = array_search($cap, $caps)) !== false) {
1423
-            unset($caps[ $key ]);
1423
+            unset($caps[$key]);
1424 1424
         }
1425 1425
 
1426 1426
         $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1427 1427
         // if no obj then let's just do cap
1428
-        if (! $obj instanceof EE_Message_Template_Group) {
1428
+        if ( ! $obj instanceof EE_Message_Template_Group) {
1429 1429
             $caps[] = 'do_not_allow';
1430 1430
             return $caps;
1431 1431
         }
1432
-        $caps[]    = $cap . 's';
1432
+        $caps[]    = $cap.'s';
1433 1433
         $is_global = $obj->is_global();
1434 1434
         if ($obj->wp_user() && $obj->wp_user() === $user_id) {
1435 1435
             if ($is_global) {
@@ -1481,15 +1481,15 @@  discard block
 block discarded – undo
1481 1481
         }
1482 1482
         // okay it is a meta cap so let's first remove that cap from the $caps array.
1483 1483
         if (($key = array_search($cap, $caps)) !== false) {
1484
-            unset($caps[ $key ]);
1484
+            unset($caps[$key]);
1485 1485
         }
1486 1486
         $obj = ! empty($args[0]) ? $this->_model->get_one_by_ID($args[0]) : null;
1487 1487
         // if no obj then let's just do cap
1488
-        if (! $obj instanceof EE_Base_Class) {
1488
+        if ( ! $obj instanceof EE_Base_Class) {
1489 1489
             $caps[] = 'do_not_allow';
1490 1490
             return $caps;
1491 1491
         }
1492
-        $caps[]    = $cap . 's';
1492
+        $caps[]    = $cap.'s';
1493 1493
         $is_system = $obj instanceof EE_Question_Group ? $obj->system_group() : false;
1494 1494
         $is_system = $obj instanceof EE_Question ? $obj->is_system_question() : $is_system;
1495 1495
         if ($is_system) {
Please login to merge, or discard this patch.