Completed
Branch FET/allow-prices-to-be-more-pr... (276f1f)
by
unknown
17:51 queued 14:33
created
core/db_classes/EE_Ticket.class.php 2 patches
Indentation   +1827 added lines, -1827 removed lines patch added patch discarded remove patch
@@ -13,1835 +13,1835 @@
 block discarded – undo
13 13
  */
14 14
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
15 15
 {
16
-    /**
17
-     * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
18
-     */
19
-    const sold_out = 'TKS';
20
-
21
-    /**
22
-     *
23
-     */
24
-    const expired = 'TKE';
25
-
26
-    /**
27
-     *
28
-     */
29
-    const archived = 'TKA';
30
-
31
-    /**
32
-     *
33
-     */
34
-    const pending = 'TKP';
35
-
36
-    /**
37
-     *
38
-     */
39
-    const onsale = 'TKO';
40
-
41
-    /**
42
-     * extra meta key for tracking ticket reservations
43
-     *
44
-     * @type string
45
-     */
46
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
47
-
48
-    /**
49
-     * cached result from method of the same name
50
-     *
51
-     * @var float $_ticket_total_with_taxes
52
-     */
53
-    private $_ticket_total_with_taxes;
54
-
55
-
56
-    /**
57
-     * @param array  $props_n_values          incoming values
58
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
59
-     *                                        used.)
60
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
61
-     *                                        date_format and the second value is the time format
62
-     * @return EE_Ticket
63
-     * @throws EE_Error
64
-     * @throws ReflectionException
65
-     */
66
-    public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
67
-    {
68
-        $ticket = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
69
-        if (! $ticket instanceof EE_Ticket) {
70
-            $ticket = new EE_Ticket($props_n_values, false, $timezone, $date_formats);
71
-        }
72
-        return $ticket;
73
-    }
74
-
75
-
76
-    /**
77
-     * @param array  $props_n_values  incoming values from the database
78
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
79
-     *                                the website will be used.
80
-     * @return EE_Ticket
81
-     * @throws EE_Error
82
-     * @throws ReflectionException
83
-     */
84
-    public static function new_instance_from_db($props_n_values = [], $timezone = null)
85
-    {
86
-        return new self($props_n_values, true, $timezone);
87
-    }
88
-
89
-
90
-    /**
91
-     * @param array  $props_n_values
92
-     * @param bool   $bydb
93
-     * @param string $timezone
94
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
95
-     *                                        date_format and the second value is the time format
96
-     * @throws EE_Error
97
-     * @throws ReflectionException
98
-     */
99
-    protected function __construct($props_n_values = [], $bydb = false, $timezone = '', $date_formats = [])
100
-    {
101
-        parent::__construct($props_n_values, $bydb, $timezone, $date_formats);
102
-    }
103
-
104
-
105
-    /**
106
-     * @return bool
107
-     * @throws EE_Error|ReflectionException
108
-     */
109
-    public function parent()
110
-    {
111
-        return $this->get('TKT_parent');
112
-    }
113
-
114
-
115
-    /**
116
-     * return if a ticket has quantities available for purchase
117
-     *
118
-     * @param int $DTT_ID the primary key for a particular datetime
119
-     * @return boolean
120
-     * @throws EE_Error
121
-     * @throws ReflectionException
122
-     */
123
-    public function available($DTT_ID = 0)
124
-    {
125
-        // are we checking availability for a particular datetime ?
126
-        if ($DTT_ID) {
127
-            // get that datetime object
128
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
129
-            // if  ticket sales for this datetime have exceeded the reg limit...
130
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
131
-                return false;
132
-            }
133
-        }
134
-        // datetime is still open for registration, but is this ticket sold out ?
135
-        return $this->qty() < 1 || $this->qty() > $this->sold();
136
-    }
137
-
138
-
139
-    /**
140
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
141
-     *
142
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
143
-     *                               relevant status const
144
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
145
-     *                               further processing
146
-     * @return mixed status int if the display string isn't requested
147
-     * @throws EE_Error
148
-     * @throws ReflectionException
149
-     */
150
-    public function ticket_status($display = false, $remaining = null)
151
-    {
152
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
153
-        if (! $remaining) {
154
-            return $display
155
-                ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence')
156
-                : EE_Ticket::sold_out;
157
-        }
158
-        if ($this->get('TKT_deleted')) {
159
-            return $display
160
-                ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence')
161
-                : EE_Ticket::archived;
162
-        }
163
-        if ($this->is_expired()) {
164
-            return $display
165
-                ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence')
166
-                : EE_Ticket::expired;
167
-        }
168
-        if ($this->is_pending()) {
169
-            return $display
170
-                ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence')
171
-                : EE_Ticket::pending;
172
-        }
173
-        if ($this->is_on_sale()) {
174
-            return $display
175
-                ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence')
176
-                : EE_Ticket::onsale;
177
-        }
178
-        return '';
179
-    }
180
-
181
-
182
-    /**
183
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
184
-     * considering ALL the factors used for figuring that out.
185
-     *
186
-     * @access public
187
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
188
-     * @return boolean         true = tickets remaining, false not.
189
-     * @throws EE_Error|ReflectionException
190
-     */
191
-    public function is_remaining($DTT_ID = 0)
192
-    {
193
-        $num_remaining = $this->remaining($DTT_ID);
194
-        if ($num_remaining === 0) {
195
-            return false;
196
-        }
197
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
198
-            return false;
199
-        }
200
-        return true;
201
-    }
202
-
203
-
204
-    /**
205
-     * return the total number of tickets available for purchase
206
-     *
207
-     * @param int $DTT_ID  the primary key for a particular datetime.
208
-     *                     set to 0 for all related datetimes
209
-     * @return int
210
-     * @throws EE_Error|ReflectionException
211
-     */
212
-    public function remaining($DTT_ID = 0)
213
-    {
214
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
215
-    }
216
-
217
-
218
-    /**
219
-     * Gets min
220
-     *
221
-     * @return int
222
-     * @throws EE_Error
223
-     * @throws ReflectionException
224
-     */
225
-    public function min()
226
-    {
227
-        return $this->get('TKT_min');
228
-    }
229
-
230
-
231
-    /**
232
-     * return if a ticket is no longer available cause its available dates have expired.
233
-     *
234
-     * @return boolean
235
-     * @throws EE_Error
236
-     * @throws ReflectionException
237
-     */
238
-    public function is_expired()
239
-    {
240
-        return ($this->get_raw('TKT_end_date') < time());
241
-    }
242
-
243
-
244
-    /**
245
-     * Return if a ticket is yet to go on sale or not
246
-     *
247
-     * @return boolean
248
-     * @throws EE_Error
249
-     * @throws ReflectionException
250
-     */
251
-    public function is_pending()
252
-    {
253
-        return ($this->get_raw('TKT_start_date') > time());
254
-    }
255
-
256
-
257
-    /**
258
-     * Return if a ticket is on sale or not
259
-     *
260
-     * @return boolean
261
-     * @throws EE_Error
262
-     * @throws ReflectionException
263
-     */
264
-    public function is_on_sale()
265
-    {
266
-        return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
267
-    }
268
-
269
-
270
-    /**
271
-     * This returns the chronologically last datetime that this ticket is associated with
272
-     *
273
-     * @param string $date_format
274
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
275
-     *                            the end date ie: Jan 01 "to" Dec 31
276
-     * @return string
277
-     * @throws EE_Error
278
-     * @throws ReflectionException
279
-     */
280
-    public function date_range($date_format = '', $conjunction = ' - ')
281
-    {
282
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
283
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
284
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
285
-            : '';
286
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
287
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
288
-            : '';
289
-
290
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
291
-    }
292
-
293
-
294
-    /**
295
-     * This returns the chronologically first datetime that this ticket is associated with
296
-     *
297
-     * @return EE_Datetime
298
-     * @throws EE_Error|ReflectionException
299
-     */
300
-    public function first_datetime()
301
-    {
302
-        $datetimes = $this->datetimes(['limit' => 1]);
303
-        return reset($datetimes);
304
-    }
305
-
306
-
307
-    /**
308
-     * Gets all the datetimes this ticket can be used for attending.
309
-     * Unless otherwise specified, orders datetimes by start date.
310
-     *
311
-     * @param array $query_params
312
-     * @return EE_Datetime[]|EE_Base_Class[]
313
-     * @throws EE_Error
314
-     * @throws ReflectionException
315
-     */
316
-    public function datetimes($query_params = [])
317
-    {
318
-        if (! isset($query_params['order_by'])) {
319
-            $query_params['order_by']['DTT_order'] = 'ASC';
320
-        }
321
-        return $this->get_many_related('Datetime', $query_params);
322
-    }
323
-
324
-
325
-    /**
326
-     * This returns the chronologically last datetime that this ticket is associated with
327
-     *
328
-     * @return EE_Datetime
329
-     * @throws EE_Error|ReflectionException
330
-     */
331
-    public function last_datetime()
332
-    {
333
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
334
-        return end($datetimes);
335
-    }
336
-
337
-
338
-    /**
339
-     * This returns the total tickets sold depending on the given parameters.
340
-     *
341
-     * @param string $get_sold_for  Can be one of two options: 'ticket', 'datetime'.
342
-     *                              'ticket' = total ticket sales for all datetimes this ticket is related to
343
-     *                              'datetime' = total ticket sales for a specified datetime (required $dtt_id)
344
-     *                              'datetime' = total ticket sales in the datetime_ticket table.
345
-     *                              If $dtt_id is not given then we return an array of sales indexed by datetime.
346
-     *                              If $dtt_id IS given then we return the tickets sold for that given datetime.
347
-     * @param int    $dtt_id        [optional] include the dtt_id with $what = 'datetime'.
348
-     * @return mixed (array|int)    how many tickets have sold
349
-     * @throws EE_Error|ReflectionException
350
-     */
351
-    public function tickets_sold($get_sold_for = 'ticket', $dtt_id = null)
352
-    {
353
-        $total        = 0;
354
-        $tickets_sold = $this->_all_tickets_sold();
355
-
356
-        if ($get_sold_for === 'ticket') {
357
-            return $tickets_sold['ticket'];
358
-        }
359
-
360
-        if (empty($tickets_sold['datetime'])) {
361
-            return $total;
362
-        }
363
-
364
-        if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
365
-            EE_Error::add_error(
366
-                esc_html__(
367
-                    'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
368
-                    'event_espresso'
369
-                ),
370
-                __FILE__,
371
-                __FUNCTION__,
372
-                __LINE__
373
-            );
374
-            return $total;
375
-        }
376
-        return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
377
-    }
378
-
379
-
380
-    /**
381
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
382
-     *
383
-     * @return EE_Ticket[]
384
-     * @throws EE_Error
385
-     * @throws ReflectionException
386
-     */
387
-    protected function _all_tickets_sold()
388
-    {
389
-        $datetimes    = $this->get_many_related('Datetime');
390
-        $tickets_sold = [];
391
-        if (! empty($datetimes)) {
392
-            foreach ($datetimes as $datetime) {
393
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
394
-            }
395
-        }
396
-        // Tickets sold
397
-        $tickets_sold['ticket'] = $this->sold();
398
-        return $tickets_sold;
399
-    }
400
-
401
-
402
-    /**
403
-     * This returns the base price object for the ticket.
404
-     *
405
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
406
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
407
-     * @throws EE_Error
408
-     * @throws ReflectionException
409
-     */
410
-    public function base_price($return_array = false)
411
-    {
412
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
413
-        return $return_array
414
-            ? $this->get_many_related('Price', [$_where])
415
-            : $this->get_first_related('Price', [$_where]);
416
-    }
417
-
418
-
419
-    /**
420
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
421
-     *
422
-     * @access public
423
-     * @return EE_Price[]
424
-     * @throws EE_Error|ReflectionException
425
-     */
426
-    public function price_modifiers()
427
-    {
428
-        $query_params = [
429
-            0 => [
430
-                'Price_Type.PBT_ID' => [
431
-                    'NOT IN',
432
-                    [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
433
-                ],
434
-            ],
435
-        ];
436
-        return $this->prices($query_params);
437
-    }
438
-
439
-
440
-    /**
441
-     * Gets all the prices that combine to form the final price of this ticket
442
-     *
443
-     * @param array $query_params
444
-     * @return EE_Price[]|EE_Base_Class[]
445
-     * @throws EE_Error
446
-     * @throws ReflectionException
447
-     * @see  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
448
-     */
449
-    public function prices($query_params = [])
450
-    {
451
-        return $this->get_many_related('Price', $query_params);
452
-    }
453
-
454
-
455
-    /**
456
-     * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
457
-     *
458
-     * @param array $query_params
459
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
460
-     * @throws EE_Error
461
-     * @throws ReflectionException
462
-     * @see  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
463
-     */
464
-    public function datetime_tickets($query_params = [])
465
-    {
466
-        return $this->get_many_related('Datetime_Ticket', $query_params);
467
-    }
468
-
469
-
470
-    /**
471
-     * Gets all the datetimes from the db ordered by DTT_order
472
-     *
473
-     * @param boolean $show_expired
474
-     * @param boolean $show_deleted
475
-     * @return EE_Datetime[]
476
-     * @throws EE_Error|ReflectionException
477
-     */
478
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
479
-    {
480
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
481
-            $this->ID(),
482
-            $show_expired,
483
-            $show_deleted
484
-        );
485
-    }
486
-
487
-
488
-    /**
489
-     * Gets ID
490
-     *
491
-     * @return int
492
-     * @throws EE_Error
493
-     * @throws ReflectionException
494
-     */
495
-    public function ID()
496
-    {
497
-        return (int) $this->get('TKT_ID');
498
-    }
499
-
500
-
501
-    /**
502
-     * get the author of the ticket.
503
-     *
504
-     * @return int
505
-     * @throws EE_Error
506
-     * @throws ReflectionException
507
-     * @since 4.5.0
508
-     */
509
-    public function wp_user()
510
-    {
511
-        return $this->get('TKT_wp_user');
512
-    }
513
-
514
-
515
-    /**
516
-     * Gets the template for the ticket
517
-     *
518
-     * @return EE_Ticket_Template|EE_Base_Class
519
-     * @throws EE_Error
520
-     * @throws ReflectionException
521
-     */
522
-    public function template()
523
-    {
524
-        return $this->get_first_related('Ticket_Template');
525
-    }
526
-
527
-
528
-    /**
529
-     * Simply returns an array of EE_Price objects that are taxes.
530
-     *
531
-     * @return EE_Price[]
532
-     * @throws EE_Error
533
-     */
534
-    public function get_ticket_taxes_for_admin()
535
-    {
536
-        return EE_Taxes::get_taxes_for_admin();
537
-    }
538
-
539
-
540
-    /**
541
-     * @return float
542
-     * @throws EE_Error
543
-     * @throws ReflectionException
544
-     */
545
-    public function ticket_price()
546
-    {
547
-        return $this->get('TKT_price');
548
-    }
549
-
550
-
551
-    /**
552
-     * @param string|null $schema
553
-     * @return mixed
554
-     * @throws EE_Error
555
-     * @throws ReflectionException
556
-     */
557
-    public function pretty_price($schema = 'localized_currency')
558
-    {
559
-        return $this->get_pretty('TKT_price', $schema);
560
-    }
561
-
562
-
563
-    /**
564
-     * @return bool
565
-     * @throws EE_Error|ReflectionException
566
-     */
567
-    public function is_free()
568
-    {
569
-        return $this->get_ticket_total_with_taxes() === (float) 0;
570
-    }
571
-
572
-
573
-    /**
574
-     * get_ticket_total_with_taxes
575
-     *
576
-     * @param bool $no_cache
577
-     * @return float
578
-     * @throws EE_Error|ReflectionException
579
-     */
580
-    public function get_ticket_total_with_taxes($no_cache = false)
581
-    {
582
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
583
-            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
584
-        }
585
-        return (float) $this->_ticket_total_with_taxes;
586
-    }
587
-
588
-
589
-    /**
590
-     * @throws EE_Error
591
-     * @throws ReflectionException
592
-     */
593
-    public function ensure_TKT_Price_correct()
594
-    {
595
-        $this->set_price(EE_Taxes::get_subtotal_for_admin($this));
596
-        $this->save();
597
-    }
598
-
599
-
600
-    /**
601
-     * @return float
602
-     * @throws EE_Error|ReflectionException
603
-     */
604
-    public function get_ticket_subtotal()
605
-    {
606
-        return EE_Taxes::get_subtotal_for_admin($this);
607
-    }
608
-
609
-
610
-    /**
611
-     * Returns the total taxes applied to this ticket
612
-     *
613
-     * @return float
614
-     * @throws EE_Error|ReflectionException
615
-     */
616
-    public function get_ticket_taxes_total_for_admin()
617
-    {
618
-        return EE_Taxes::get_total_taxes_for_admin($this);
619
-    }
620
-
621
-
622
-    /**
623
-     * Sets name
624
-     *
625
-     * @param string $name
626
-     * @throws EE_Error
627
-     * @throws ReflectionException
628
-     */
629
-    public function set_name($name)
630
-    {
631
-        $this->set('TKT_name', $name);
632
-    }
633
-
634
-
635
-    /**
636
-     * Gets description
637
-     *
638
-     * @return string
639
-     * @throws EE_Error
640
-     * @throws ReflectionException
641
-     */
642
-    public function description()
643
-    {
644
-        return $this->get('TKT_description');
645
-    }
646
-
647
-
648
-    /**
649
-     * Sets description
650
-     *
651
-     * @param string $description
652
-     * @throws EE_Error
653
-     * @throws ReflectionException
654
-     */
655
-    public function set_description($description)
656
-    {
657
-        $this->set('TKT_description', $description);
658
-    }
659
-
660
-
661
-    /**
662
-     * Gets start_date
663
-     *
664
-     * @param string $date_format
665
-     * @param string $time_format
666
-     * @return string
667
-     * @throws EE_Error
668
-     * @throws ReflectionException
669
-     */
670
-    public function start_date($date_format = '', $time_format = '')
671
-    {
672
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
673
-    }
674
-
675
-
676
-    /**
677
-     * Sets start_date
678
-     *
679
-     * @param string $start_date
680
-     * @return void
681
-     * @throws EE_Error
682
-     * @throws ReflectionException
683
-     */
684
-    public function set_start_date($start_date)
685
-    {
686
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
687
-    }
688
-
689
-
690
-    /**
691
-     * Gets end_date
692
-     *
693
-     * @param string $date_format
694
-     * @param string $time_format
695
-     * @return string
696
-     * @throws EE_Error
697
-     * @throws ReflectionException
698
-     */
699
-    public function end_date($date_format = '', $time_format = '')
700
-    {
701
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
702
-    }
703
-
704
-
705
-    /**
706
-     * Sets end_date
707
-     *
708
-     * @param string $end_date
709
-     * @return void
710
-     * @throws EE_Error
711
-     * @throws ReflectionException
712
-     */
713
-    public function set_end_date($end_date)
714
-    {
715
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
716
-    }
717
-
718
-
719
-    /**
720
-     * Sets sell until time
721
-     *
722
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
723
-     * @throws EE_Error
724
-     * @throws ReflectionException
725
-     * @since 4.5.0
726
-     */
727
-    public function set_end_time($time)
728
-    {
729
-        $this->_set_time_for($time, 'TKT_end_date');
730
-    }
731
-
732
-
733
-    /**
734
-     * Sets min
735
-     *
736
-     * @param int $min
737
-     * @return void
738
-     * @throws EE_Error
739
-     * @throws ReflectionException
740
-     */
741
-    public function set_min($min)
742
-    {
743
-        $this->set('TKT_min', $min);
744
-    }
745
-
746
-
747
-    /**
748
-     * Gets max
749
-     *
750
-     * @return int
751
-     * @throws EE_Error
752
-     * @throws ReflectionException
753
-     */
754
-    public function max()
755
-    {
756
-        return $this->get('TKT_max');
757
-    }
758
-
759
-
760
-    /**
761
-     * Sets max
762
-     *
763
-     * @param int $max
764
-     * @return void
765
-     * @throws EE_Error
766
-     * @throws ReflectionException
767
-     */
768
-    public function set_max($max)
769
-    {
770
-        $this->set('TKT_max', $max);
771
-    }
772
-
773
-
774
-    /**
775
-     * Sets price
776
-     *
777
-     * @param float $price
778
-     * @return void
779
-     * @throws EE_Error
780
-     * @throws ReflectionException
781
-     */
782
-    public function set_price($price)
783
-    {
784
-        $this->set('TKT_price', $price);
785
-    }
786
-
787
-
788
-    /**
789
-     * Gets sold
790
-     *
791
-     * @return int
792
-     * @throws EE_Error
793
-     * @throws ReflectionException
794
-     */
795
-    public function sold()
796
-    {
797
-        return $this->get_raw('TKT_sold');
798
-    }
799
-
800
-
801
-    /**
802
-     * Sets sold
803
-     *
804
-     * @param int $sold
805
-     * @return void
806
-     * @throws EE_Error
807
-     * @throws ReflectionException
808
-     */
809
-    public function set_sold($sold)
810
-    {
811
-        // sold can not go below zero
812
-        $sold = max(0, absint($sold));
813
-        $this->set('TKT_sold', $sold);
814
-    }
815
-
816
-
817
-    /**
818
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
819
-     * associated datetimes.
820
-     *
821
-     * @param int $qty
822
-     * @return boolean
823
-     * @throws EE_Error
824
-     * @throws InvalidArgumentException
825
-     * @throws InvalidDataTypeException
826
-     * @throws InvalidInterfaceException
827
-     * @throws ReflectionException
828
-     * @since 4.9.80.p
829
-     */
830
-    public function increaseSold($qty = 1)
831
-    {
832
-        $qty = absint($qty);
833
-        // increment sold and decrement reserved datetime quantities simultaneously
834
-        // don't worry about failures, because they must have already had a spot reserved
835
-        $this->increaseSoldForDatetimes($qty);
836
-        // Increment and decrement ticket quantities simultaneously
837
-        $success = $this->adjustNumericFieldsInDb(
838
-            [
839
-                'TKT_reserved' => $qty * -1,
840
-                'TKT_sold'     => $qty,
841
-            ]
842
-        );
843
-        do_action(
844
-            'AHEE__EE_Ticket__increase_sold',
845
-            $this,
846
-            $qty,
847
-            $this->sold(),
848
-            $success
849
-        );
850
-        return $success;
851
-    }
852
-
853
-
854
-    /**
855
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
856
-     *
857
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
858
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
859
-     * @param EE_Datetime[] $datetimes
860
-     * @throws EE_Error
861
-     * @throws InvalidArgumentException
862
-     * @throws InvalidDataTypeException
863
-     * @throws InvalidInterfaceException
864
-     * @throws ReflectionException
865
-     * @since 4.9.80.p
866
-     */
867
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
868
-    {
869
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
870
-        foreach ($datetimes as $datetime) {
871
-            $datetime->increaseSold($qty);
872
-        }
873
-    }
874
-
875
-
876
-    /**
877
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
878
-     * DB and then updates the model objects.
879
-     * Does not affect the reserved counts.
880
-     *
881
-     * @param int $qty
882
-     * @return boolean
883
-     * @throws EE_Error
884
-     * @throws InvalidArgumentException
885
-     * @throws InvalidDataTypeException
886
-     * @throws InvalidInterfaceException
887
-     * @throws ReflectionException
888
-     * @since 4.9.80.p
889
-     */
890
-    public function decreaseSold($qty = 1)
891
-    {
892
-        $qty = absint($qty);
893
-        $this->decreaseSoldForDatetimes($qty);
894
-        $success = $this->adjustNumericFieldsInDb(
895
-            [
896
-                'TKT_sold' => $qty * -1,
897
-            ]
898
-        );
899
-        do_action(
900
-            'AHEE__EE_Ticket__decrease_sold',
901
-            $this,
902
-            $qty,
903
-            $this->sold(),
904
-            $success
905
-        );
906
-        return $success;
907
-    }
908
-
909
-
910
-    /**
911
-     * Decreases sold on related datetimes
912
-     *
913
-     * @param int           $qty
914
-     * @param EE_Datetime[] $datetimes
915
-     * @return void
916
-     * @throws EE_Error
917
-     * @throws InvalidArgumentException
918
-     * @throws InvalidDataTypeException
919
-     * @throws InvalidInterfaceException
920
-     * @throws ReflectionException
921
-     * @since 4.9.80.p
922
-     */
923
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
924
-    {
925
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
926
-        if (is_array($datetimes)) {
927
-            foreach ($datetimes as $datetime) {
928
-                if ($datetime instanceof EE_Datetime) {
929
-                    $datetime->decreaseSold($qty);
930
-                }
931
-            }
932
-        }
933
-    }
934
-
935
-
936
-    /**
937
-     * Gets qty of reserved tickets
938
-     *
939
-     * @return int
940
-     * @throws EE_Error
941
-     * @throws ReflectionException
942
-     */
943
-    public function reserved()
944
-    {
945
-        return $this->get_raw('TKT_reserved');
946
-    }
947
-
948
-
949
-    /**
950
-     * Sets reserved
951
-     *
952
-     * @param int $reserved
953
-     * @return void
954
-     * @throws EE_Error
955
-     * @throws ReflectionException
956
-     */
957
-    public function set_reserved($reserved)
958
-    {
959
-        // reserved can not go below zero
960
-        $reserved = max(0, (int) $reserved);
961
-        $this->set('TKT_reserved', $reserved);
962
-    }
963
-
964
-
965
-    /**
966
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
967
-     *
968
-     * @param int    $qty
969
-     * @param string $source
970
-     * @return bool whether we successfully reserved the ticket or not.
971
-     * @throws EE_Error
972
-     * @throws InvalidArgumentException
973
-     * @throws ReflectionException
974
-     * @throws InvalidDataTypeException
975
-     * @throws InvalidInterfaceException
976
-     * @since 4.9.80.p
977
-     */
978
-    public function increaseReserved($qty = 1, $source = 'unknown')
979
-    {
980
-        $qty = absint($qty);
981
-        do_action(
982
-            'AHEE__EE_Ticket__increase_reserved__begin',
983
-            $this,
984
-            $qty,
985
-            $source
986
-        );
987
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
988
-        $success                         = false;
989
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
990
-        if ($datetimes_adjusted_successfully) {
991
-            $success = $this->incrementFieldConditionallyInDb(
992
-                'TKT_reserved',
993
-                'TKT_sold',
994
-                'TKT_qty',
995
-                $qty
996
-            );
997
-            if (! $success) {
998
-                // The datetimes were successfully bumped, but not the
999
-                // ticket. So we need to manually rollback the datetimes.
1000
-                $this->decreaseReservedForDatetimes($qty);
1001
-            }
1002
-        }
1003
-        do_action(
1004
-            'AHEE__EE_Ticket__increase_reserved',
1005
-            $this,
1006
-            $qty,
1007
-            $this->reserved(),
1008
-            $success
1009
-        );
1010
-        return $success;
1011
-    }
1012
-
1013
-
1014
-    /**
1015
-     * Increases reserved counts on related datetimes
1016
-     *
1017
-     * @param int           $qty
1018
-     * @param EE_Datetime[] $datetimes
1019
-     * @return boolean indicating success
1020
-     * @throws EE_Error
1021
-     * @throws InvalidArgumentException
1022
-     * @throws InvalidDataTypeException
1023
-     * @throws InvalidInterfaceException
1024
-     * @throws ReflectionException
1025
-     * @since 4.9.80.p
1026
-     */
1027
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1028
-    {
1029
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1030
-        $datetimes_updated = [];
1031
-        $limit_exceeded    = false;
1032
-        if (is_array($datetimes)) {
1033
-            foreach ($datetimes as $datetime) {
1034
-                if ($datetime instanceof EE_Datetime) {
1035
-                    if ($datetime->increaseReserved($qty)) {
1036
-                        $datetimes_updated[] = $datetime;
1037
-                    } else {
1038
-                        $limit_exceeded = true;
1039
-                        break;
1040
-                    }
1041
-                }
1042
-            }
1043
-            // If somewhere along the way we detected a datetime whose
1044
-            // limit was exceeded, do a manual rollback.
1045
-            if ($limit_exceeded) {
1046
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1047
-                return false;
1048
-            }
1049
-        }
1050
-        return true;
1051
-    }
1052
-
1053
-
1054
-    /**
1055
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1056
-     *
1057
-     * @param int    $qty
1058
-     * @param bool   $adjust_datetimes
1059
-     * @param string $source
1060
-     * @return boolean
1061
-     * @throws EE_Error
1062
-     * @throws InvalidArgumentException
1063
-     * @throws ReflectionException
1064
-     * @throws InvalidDataTypeException
1065
-     * @throws InvalidInterfaceException
1066
-     * @since 4.9.80.p
1067
-     */
1068
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1069
-    {
1070
-        $qty = absint($qty);
1071
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1072
-        if ($adjust_datetimes) {
1073
-            $this->decreaseReservedForDatetimes($qty);
1074
-        }
1075
-        $success = $this->adjustNumericFieldsInDb(
1076
-            [
1077
-                'TKT_reserved' => $qty * -1,
1078
-            ]
1079
-        );
1080
-        do_action(
1081
-            'AHEE__EE_Ticket__decrease_reserved',
1082
-            $this,
1083
-            $qty,
1084
-            $this->reserved(),
1085
-            $success
1086
-        );
1087
-        return $success;
1088
-    }
1089
-
1090
-
1091
-    /**
1092
-     * Decreases the reserved count on the specified datetimes.
1093
-     *
1094
-     * @param int           $qty
1095
-     * @param EE_Datetime[] $datetimes
1096
-     * @throws EE_Error
1097
-     * @throws InvalidArgumentException
1098
-     * @throws ReflectionException
1099
-     * @throws InvalidDataTypeException
1100
-     * @throws InvalidInterfaceException
1101
-     * @since 4.9.80.p
1102
-     */
1103
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1104
-    {
1105
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1106
-        foreach ($datetimes as $datetime) {
1107
-            if ($datetime instanceof EE_Datetime) {
1108
-                $datetime->decreaseReserved($qty);
1109
-            }
1110
-        }
1111
-    }
1112
-
1113
-
1114
-    /**
1115
-     * Gets ticket quantity
1116
-     *
1117
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1118
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1119
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1120
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1121
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1122
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1123
-     * @return int
1124
-     * @throws EE_Error
1125
-     * @throws ReflectionException
1126
-     */
1127
-    public function qty($context = '')
1128
-    {
1129
-        switch ($context) {
1130
-            case 'reg_limit':
1131
-                return $this->real_quantity_on_ticket();
1132
-            case 'saleable':
1133
-                return $this->real_quantity_on_ticket('saleable');
1134
-            default:
1135
-                return $this->get_raw('TKT_qty');
1136
-        }
1137
-    }
1138
-
1139
-
1140
-    /**
1141
-     * Gets ticket quantity
1142
-     *
1143
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1144
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1145
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1146
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1147
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1148
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1149
-     *                            set to 0 for all related datetimes
1150
-     * @return int
1151
-     * @throws EE_Error
1152
-     * @throws ReflectionException
1153
-     */
1154
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1155
-    {
1156
-        $raw = $this->get_raw('TKT_qty');
1157
-        // return immediately if it's zero
1158
-        if ($raw === 0) {
1159
-            return $raw;
1160
-        }
1161
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1162
-        // ensure qty doesn't exceed raw value for THIS ticket
1163
-        $qty = min(EE_INF, $raw);
1164
-        // echo "\n . qty: " . $qty . '<br />';
1165
-        // calculate this ticket's total sales and reservations
1166
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1167
-        // echo "\n . sold: " . $this->sold() . '<br />';
1168
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1169
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1170
-        // first we need to calculate the maximum number of tickets available for the datetime
1171
-        // do we want data for one datetime or all of them ?
1172
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1173
-        $datetimes    = $this->datetimes($query_params);
1174
-        if (is_array($datetimes) && ! empty($datetimes)) {
1175
-            foreach ($datetimes as $datetime) {
1176
-                if ($datetime instanceof EE_Datetime) {
1177
-                    $datetime->refresh_from_db();
1178
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1179
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1180
-                    // initialize with no restrictions for each datetime
1181
-                    // but adjust datetime qty based on datetime reg limit
1182
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1183
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1184
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1185
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1186
-                    // and reservations for this datetime, that do NOT include sales and reservations
1187
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1188
-                    if ($context === 'saleable') {
1189
-                        $datetime_qty = max(
1190
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1191
-                            0
1192
-                        );
1193
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1194
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1195
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1196
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1197
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1198
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1199
-                    }
1200
-                    $qty = min($datetime_qty, $qty);
1201
-                    // echo "\n . . qty: " . $qty . '<br />';
1202
-                }
1203
-            }
1204
-        }
1205
-        // NOW that we know the  maximum number of tickets available for the datetime
1206
-        // we can finally factor in the details for this specific ticket
1207
-        if ($qty > 0 && $context === 'saleable') {
1208
-            // and subtract the sales for THIS ticket
1209
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1210
-            // echo "\n . qty: " . $qty . '<br />';
1211
-        }
1212
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1213
-        return $qty;
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1219
-     *
1220
-     * @param int $qty
1221
-     * @return void
1222
-     * @throws EE_Error
1223
-     * @throws ReflectionException
1224
-     */
1225
-    public function set_qty($qty)
1226
-    {
1227
-        $datetimes = $this->datetimes();
1228
-        foreach ($datetimes as $datetime) {
1229
-            if ($datetime instanceof EE_Datetime) {
1230
-                $qty = min($qty, $datetime->reg_limit());
1231
-            }
1232
-        }
1233
-        $this->set('TKT_qty', $qty);
1234
-    }
1235
-
1236
-
1237
-    /**
1238
-     * Gets uses
1239
-     *
1240
-     * @return int
1241
-     * @throws EE_Error
1242
-     * @throws ReflectionException
1243
-     */
1244
-    public function uses()
1245
-    {
1246
-        return $this->get('TKT_uses');
1247
-    }
1248
-
1249
-
1250
-    /**
1251
-     * Sets uses
1252
-     *
1253
-     * @param int $uses
1254
-     * @return void
1255
-     * @throws EE_Error
1256
-     * @throws ReflectionException
1257
-     */
1258
-    public function set_uses($uses)
1259
-    {
1260
-        $this->set('TKT_uses', $uses);
1261
-    }
1262
-
1263
-
1264
-    /**
1265
-     * returns whether ticket is required or not.
1266
-     *
1267
-     * @return boolean
1268
-     * @throws EE_Error
1269
-     * @throws ReflectionException
1270
-     */
1271
-    public function required()
1272
-    {
1273
-        return $this->get('TKT_required');
1274
-    }
1275
-
1276
-
1277
-    /**
1278
-     * sets the TKT_required property
1279
-     *
1280
-     * @param boolean $required
1281
-     * @return void
1282
-     * @throws EE_Error
1283
-     * @throws ReflectionException
1284
-     */
1285
-    public function set_required($required)
1286
-    {
1287
-        $this->set('TKT_required', $required);
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * Gets taxable
1293
-     *
1294
-     * @return boolean
1295
-     * @throws EE_Error
1296
-     * @throws ReflectionException
1297
-     */
1298
-    public function taxable()
1299
-    {
1300
-        return $this->get('TKT_taxable');
1301
-    }
1302
-
1303
-
1304
-    /**
1305
-     * Sets taxable
1306
-     *
1307
-     * @param boolean $taxable
1308
-     * @return void
1309
-     * @throws EE_Error
1310
-     * @throws ReflectionException
1311
-     */
1312
-    public function set_taxable($taxable)
1313
-    {
1314
-        $this->set('TKT_taxable', $taxable);
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * Gets is_default
1320
-     *
1321
-     * @return boolean
1322
-     * @throws EE_Error
1323
-     * @throws ReflectionException
1324
-     */
1325
-    public function is_default()
1326
-    {
1327
-        return $this->get('TKT_is_default');
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Sets is_default
1333
-     *
1334
-     * @param boolean $is_default
1335
-     * @return void
1336
-     * @throws EE_Error
1337
-     * @throws ReflectionException
1338
-     */
1339
-    public function set_is_default($is_default)
1340
-    {
1341
-        $this->set('TKT_is_default', $is_default);
1342
-    }
1343
-
1344
-
1345
-    /**
1346
-     * Gets order
1347
-     *
1348
-     * @return int
1349
-     * @throws EE_Error
1350
-     * @throws ReflectionException
1351
-     */
1352
-    public function order()
1353
-    {
1354
-        return $this->get('TKT_order');
1355
-    }
1356
-
1357
-
1358
-    /**
1359
-     * Sets order
1360
-     *
1361
-     * @param int $order
1362
-     * @return void
1363
-     * @throws EE_Error
1364
-     * @throws ReflectionException
1365
-     */
1366
-    public function set_order($order)
1367
-    {
1368
-        $this->set('TKT_order', $order);
1369
-    }
1370
-
1371
-
1372
-    /**
1373
-     * Gets row
1374
-     *
1375
-     * @return int
1376
-     * @throws EE_Error
1377
-     * @throws ReflectionException
1378
-     */
1379
-    public function row()
1380
-    {
1381
-        return $this->get('TKT_row');
1382
-    }
1383
-
1384
-
1385
-    /**
1386
-     * Sets row
1387
-     *
1388
-     * @param int $row
1389
-     * @return void
1390
-     * @throws EE_Error
1391
-     * @throws ReflectionException
1392
-     */
1393
-    public function set_row($row)
1394
-    {
1395
-        $this->set('TKT_row', $row);
1396
-    }
1397
-
1398
-
1399
-    /**
1400
-     * Gets deleted
1401
-     *
1402
-     * @return boolean
1403
-     * @throws EE_Error
1404
-     * @throws ReflectionException
1405
-     */
1406
-    public function deleted()
1407
-    {
1408
-        return $this->get('TKT_deleted');
1409
-    }
1410
-
1411
-
1412
-    /**
1413
-     * Sets deleted
1414
-     *
1415
-     * @param boolean $deleted
1416
-     * @return void
1417
-     * @throws EE_Error
1418
-     * @throws ReflectionException
1419
-     */
1420
-    public function set_deleted($deleted)
1421
-    {
1422
-        $this->set('TKT_deleted', $deleted);
1423
-    }
1424
-
1425
-
1426
-    /**
1427
-     * Gets parent
1428
-     *
1429
-     * @return int
1430
-     * @throws EE_Error
1431
-     * @throws ReflectionException
1432
-     */
1433
-    public function parent_ID()
1434
-    {
1435
-        return $this->get('TKT_parent');
1436
-    }
1437
-
1438
-
1439
-    /**
1440
-     * Sets parent
1441
-     *
1442
-     * @param int $parent
1443
-     * @return void
1444
-     * @throws EE_Error
1445
-     * @throws ReflectionException
1446
-     */
1447
-    public function set_parent_ID($parent)
1448
-    {
1449
-        $this->set('TKT_parent', $parent);
1450
-    }
1451
-
1452
-
1453
-    /**
1454
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1455
-     *
1456
-     * @return string
1457
-     * @throws EE_Error
1458
-     * @throws ReflectionException
1459
-     */
1460
-    public function name_and_info()
1461
-    {
1462
-        $times = [];
1463
-        foreach ($this->datetimes() as $datetime) {
1464
-            $times[] = $datetime->start_date_and_time();
1465
-        }
1466
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1467
-    }
1468
-
1469
-
1470
-    /**
1471
-     * Gets name
1472
-     *
1473
-     * @return string
1474
-     * @throws EE_Error
1475
-     * @throws ReflectionException
1476
-     */
1477
-    public function name()
1478
-    {
1479
-        return $this->get('TKT_name');
1480
-    }
1481
-
1482
-
1483
-    /**
1484
-     * Gets price
1485
-     *
1486
-     * @return float
1487
-     * @throws EE_Error
1488
-     * @throws ReflectionException
1489
-     */
1490
-    public function price()
1491
-    {
1492
-        return $this->get('TKT_price');
1493
-    }
1494
-
1495
-
1496
-    /**
1497
-     * Gets all the registrations for this ticket
1498
-     *
1499
-     * @param array $query_params
1500
-     * @return EE_Registration[]|EE_Base_Class[]
1501
-     * @throws EE_Error
1502
-     * @throws ReflectionException
1503
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1504
-     */
1505
-    public function registrations($query_params = [])
1506
-    {
1507
-        return $this->get_many_related('Registration', $query_params);
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1513
-     *
1514
-     * @return int
1515
-     * @throws EE_Error
1516
-     * @throws ReflectionException
1517
-     */
1518
-    public function update_tickets_sold()
1519
-    {
1520
-        $count_regs_for_this_ticket = $this->count_registrations(
1521
-            [
1522
-                [
1523
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1524
-                    'REG_deleted' => 0,
1525
-                ],
1526
-            ]
1527
-        );
1528
-        $this->set_sold($count_regs_for_this_ticket);
1529
-        $this->save();
1530
-        return $count_regs_for_this_ticket;
1531
-    }
1532
-
1533
-
1534
-    /**
1535
-     * Counts the registrations for this ticket
1536
-     *
1537
-     * @param array $query_params
1538
-     * @return int
1539
-     * @throws EE_Error
1540
-     * @throws ReflectionException
1541
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1542
-     */
1543
-    public function count_registrations($query_params = [])
1544
-    {
1545
-        return $this->count_related('Registration', $query_params);
1546
-    }
1547
-
1548
-
1549
-    /**
1550
-     * Implementation for EEI_Has_Icon interface method.
1551
-     *
1552
-     * @return string
1553
-     * @see EEI_Visual_Representation for comments
1554
-     */
1555
-    public function get_icon()
1556
-    {
1557
-        return '<span class="dashicons dashicons-tickets-alt"></span>';
1558
-    }
1559
-
1560
-
1561
-    /**
1562
-     * Implementation of the EEI_Event_Relation interface method
1563
-     *
1564
-     * @return EE_Event
1565
-     * @throws EE_Error
1566
-     * @throws UnexpectedEntityException
1567
-     * @throws ReflectionException
1568
-     * @see EEI_Event_Relation for comments
1569
-     */
1570
-    public function get_related_event()
1571
-    {
1572
-        // get one datetime to use for getting the event
1573
-        $datetime = $this->first_datetime();
1574
-        if (! $datetime instanceof EE_Datetime) {
1575
-            throw new UnexpectedEntityException(
1576
-                $datetime,
1577
-                'EE_Datetime',
1578
-                sprintf(
1579
-                    esc_html__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1580
-                    $this->name()
1581
-                )
1582
-            );
1583
-        }
1584
-        $event = $datetime->event();
1585
-        if (! $event instanceof EE_Event) {
1586
-            throw new UnexpectedEntityException(
1587
-                $event,
1588
-                'EE_Event',
1589
-                sprintf(
1590
-                    esc_html__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1591
-                    $this->name()
1592
-                )
1593
-            );
1594
-        }
1595
-        return $event;
1596
-    }
1597
-
1598
-
1599
-    /**
1600
-     * Implementation of the EEI_Event_Relation interface method
1601
-     *
1602
-     * @return string
1603
-     * @throws UnexpectedEntityException
1604
-     * @throws EE_Error
1605
-     * @throws ReflectionException
1606
-     * @see EEI_Event_Relation for comments
1607
-     */
1608
-    public function get_event_name()
1609
-    {
1610
-        $event = $this->get_related_event();
1611
-        return $event instanceof EE_Event ? $event->name() : '';
1612
-    }
1613
-
1614
-
1615
-    /**
1616
-     * Implementation of the EEI_Event_Relation interface method
1617
-     *
1618
-     * @return int
1619
-     * @throws UnexpectedEntityException
1620
-     * @throws EE_Error
1621
-     * @throws ReflectionException
1622
-     * @see EEI_Event_Relation for comments
1623
-     */
1624
-    public function get_event_ID()
1625
-    {
1626
-        $event = $this->get_related_event();
1627
-        return $event instanceof EE_Event ? $event->ID() : 0;
1628
-    }
1629
-
1630
-
1631
-    /**
1632
-     * This simply returns whether a ticket can be permanently deleted or not.
1633
-     * The criteria for determining this is whether the ticket has any related registrations.
1634
-     * If there are none then it can be permanently deleted.
1635
-     *
1636
-     * @return bool
1637
-     * @throws EE_Error
1638
-     * @throws ReflectionException
1639
-     */
1640
-    public function is_permanently_deleteable()
1641
-    {
1642
-        return $this->count_registrations() === 0;
1643
-    }
1644
-
1645
-
1646
-    /*******************************************************************
16
+	/**
17
+	 * The following constants are used by the ticket_status() method to indicate whether a ticket is on sale or not.
18
+	 */
19
+	const sold_out = 'TKS';
20
+
21
+	/**
22
+	 *
23
+	 */
24
+	const expired = 'TKE';
25
+
26
+	/**
27
+	 *
28
+	 */
29
+	const archived = 'TKA';
30
+
31
+	/**
32
+	 *
33
+	 */
34
+	const pending = 'TKP';
35
+
36
+	/**
37
+	 *
38
+	 */
39
+	const onsale = 'TKO';
40
+
41
+	/**
42
+	 * extra meta key for tracking ticket reservations
43
+	 *
44
+	 * @type string
45
+	 */
46
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
47
+
48
+	/**
49
+	 * cached result from method of the same name
50
+	 *
51
+	 * @var float $_ticket_total_with_taxes
52
+	 */
53
+	private $_ticket_total_with_taxes;
54
+
55
+
56
+	/**
57
+	 * @param array  $props_n_values          incoming values
58
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
59
+	 *                                        used.)
60
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
61
+	 *                                        date_format and the second value is the time format
62
+	 * @return EE_Ticket
63
+	 * @throws EE_Error
64
+	 * @throws ReflectionException
65
+	 */
66
+	public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
67
+	{
68
+		$ticket = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
69
+		if (! $ticket instanceof EE_Ticket) {
70
+			$ticket = new EE_Ticket($props_n_values, false, $timezone, $date_formats);
71
+		}
72
+		return $ticket;
73
+	}
74
+
75
+
76
+	/**
77
+	 * @param array  $props_n_values  incoming values from the database
78
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
79
+	 *                                the website will be used.
80
+	 * @return EE_Ticket
81
+	 * @throws EE_Error
82
+	 * @throws ReflectionException
83
+	 */
84
+	public static function new_instance_from_db($props_n_values = [], $timezone = null)
85
+	{
86
+		return new self($props_n_values, true, $timezone);
87
+	}
88
+
89
+
90
+	/**
91
+	 * @param array  $props_n_values
92
+	 * @param bool   $bydb
93
+	 * @param string $timezone
94
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
95
+	 *                                        date_format and the second value is the time format
96
+	 * @throws EE_Error
97
+	 * @throws ReflectionException
98
+	 */
99
+	protected function __construct($props_n_values = [], $bydb = false, $timezone = '', $date_formats = [])
100
+	{
101
+		parent::__construct($props_n_values, $bydb, $timezone, $date_formats);
102
+	}
103
+
104
+
105
+	/**
106
+	 * @return bool
107
+	 * @throws EE_Error|ReflectionException
108
+	 */
109
+	public function parent()
110
+	{
111
+		return $this->get('TKT_parent');
112
+	}
113
+
114
+
115
+	/**
116
+	 * return if a ticket has quantities available for purchase
117
+	 *
118
+	 * @param int $DTT_ID the primary key for a particular datetime
119
+	 * @return boolean
120
+	 * @throws EE_Error
121
+	 * @throws ReflectionException
122
+	 */
123
+	public function available($DTT_ID = 0)
124
+	{
125
+		// are we checking availability for a particular datetime ?
126
+		if ($DTT_ID) {
127
+			// get that datetime object
128
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
129
+			// if  ticket sales for this datetime have exceeded the reg limit...
130
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
131
+				return false;
132
+			}
133
+		}
134
+		// datetime is still open for registration, but is this ticket sold out ?
135
+		return $this->qty() < 1 || $this->qty() > $this->sold();
136
+	}
137
+
138
+
139
+	/**
140
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
141
+	 *
142
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
143
+	 *                               relevant status const
144
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
145
+	 *                               further processing
146
+	 * @return mixed status int if the display string isn't requested
147
+	 * @throws EE_Error
148
+	 * @throws ReflectionException
149
+	 */
150
+	public function ticket_status($display = false, $remaining = null)
151
+	{
152
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
153
+		if (! $remaining) {
154
+			return $display
155
+				? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence')
156
+				: EE_Ticket::sold_out;
157
+		}
158
+		if ($this->get('TKT_deleted')) {
159
+			return $display
160
+				? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence')
161
+				: EE_Ticket::archived;
162
+		}
163
+		if ($this->is_expired()) {
164
+			return $display
165
+				? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence')
166
+				: EE_Ticket::expired;
167
+		}
168
+		if ($this->is_pending()) {
169
+			return $display
170
+				? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence')
171
+				: EE_Ticket::pending;
172
+		}
173
+		if ($this->is_on_sale()) {
174
+			return $display
175
+				? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence')
176
+				: EE_Ticket::onsale;
177
+		}
178
+		return '';
179
+	}
180
+
181
+
182
+	/**
183
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
184
+	 * considering ALL the factors used for figuring that out.
185
+	 *
186
+	 * @access public
187
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
188
+	 * @return boolean         true = tickets remaining, false not.
189
+	 * @throws EE_Error|ReflectionException
190
+	 */
191
+	public function is_remaining($DTT_ID = 0)
192
+	{
193
+		$num_remaining = $this->remaining($DTT_ID);
194
+		if ($num_remaining === 0) {
195
+			return false;
196
+		}
197
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
198
+			return false;
199
+		}
200
+		return true;
201
+	}
202
+
203
+
204
+	/**
205
+	 * return the total number of tickets available for purchase
206
+	 *
207
+	 * @param int $DTT_ID  the primary key for a particular datetime.
208
+	 *                     set to 0 for all related datetimes
209
+	 * @return int
210
+	 * @throws EE_Error|ReflectionException
211
+	 */
212
+	public function remaining($DTT_ID = 0)
213
+	{
214
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
215
+	}
216
+
217
+
218
+	/**
219
+	 * Gets min
220
+	 *
221
+	 * @return int
222
+	 * @throws EE_Error
223
+	 * @throws ReflectionException
224
+	 */
225
+	public function min()
226
+	{
227
+		return $this->get('TKT_min');
228
+	}
229
+
230
+
231
+	/**
232
+	 * return if a ticket is no longer available cause its available dates have expired.
233
+	 *
234
+	 * @return boolean
235
+	 * @throws EE_Error
236
+	 * @throws ReflectionException
237
+	 */
238
+	public function is_expired()
239
+	{
240
+		return ($this->get_raw('TKT_end_date') < time());
241
+	}
242
+
243
+
244
+	/**
245
+	 * Return if a ticket is yet to go on sale or not
246
+	 *
247
+	 * @return boolean
248
+	 * @throws EE_Error
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function is_pending()
252
+	{
253
+		return ($this->get_raw('TKT_start_date') > time());
254
+	}
255
+
256
+
257
+	/**
258
+	 * Return if a ticket is on sale or not
259
+	 *
260
+	 * @return boolean
261
+	 * @throws EE_Error
262
+	 * @throws ReflectionException
263
+	 */
264
+	public function is_on_sale()
265
+	{
266
+		return ($this->get_raw('TKT_start_date') < time() && $this->get_raw('TKT_end_date') > time());
267
+	}
268
+
269
+
270
+	/**
271
+	 * This returns the chronologically last datetime that this ticket is associated with
272
+	 *
273
+	 * @param string $date_format
274
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
275
+	 *                            the end date ie: Jan 01 "to" Dec 31
276
+	 * @return string
277
+	 * @throws EE_Error
278
+	 * @throws ReflectionException
279
+	 */
280
+	public function date_range($date_format = '', $conjunction = ' - ')
281
+	{
282
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
283
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
284
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
285
+			: '';
286
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
287
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
288
+			: '';
289
+
290
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
291
+	}
292
+
293
+
294
+	/**
295
+	 * This returns the chronologically first datetime that this ticket is associated with
296
+	 *
297
+	 * @return EE_Datetime
298
+	 * @throws EE_Error|ReflectionException
299
+	 */
300
+	public function first_datetime()
301
+	{
302
+		$datetimes = $this->datetimes(['limit' => 1]);
303
+		return reset($datetimes);
304
+	}
305
+
306
+
307
+	/**
308
+	 * Gets all the datetimes this ticket can be used for attending.
309
+	 * Unless otherwise specified, orders datetimes by start date.
310
+	 *
311
+	 * @param array $query_params
312
+	 * @return EE_Datetime[]|EE_Base_Class[]
313
+	 * @throws EE_Error
314
+	 * @throws ReflectionException
315
+	 */
316
+	public function datetimes($query_params = [])
317
+	{
318
+		if (! isset($query_params['order_by'])) {
319
+			$query_params['order_by']['DTT_order'] = 'ASC';
320
+		}
321
+		return $this->get_many_related('Datetime', $query_params);
322
+	}
323
+
324
+
325
+	/**
326
+	 * This returns the chronologically last datetime that this ticket is associated with
327
+	 *
328
+	 * @return EE_Datetime
329
+	 * @throws EE_Error|ReflectionException
330
+	 */
331
+	public function last_datetime()
332
+	{
333
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
334
+		return end($datetimes);
335
+	}
336
+
337
+
338
+	/**
339
+	 * This returns the total tickets sold depending on the given parameters.
340
+	 *
341
+	 * @param string $get_sold_for  Can be one of two options: 'ticket', 'datetime'.
342
+	 *                              'ticket' = total ticket sales for all datetimes this ticket is related to
343
+	 *                              'datetime' = total ticket sales for a specified datetime (required $dtt_id)
344
+	 *                              'datetime' = total ticket sales in the datetime_ticket table.
345
+	 *                              If $dtt_id is not given then we return an array of sales indexed by datetime.
346
+	 *                              If $dtt_id IS given then we return the tickets sold for that given datetime.
347
+	 * @param int    $dtt_id        [optional] include the dtt_id with $what = 'datetime'.
348
+	 * @return mixed (array|int)    how many tickets have sold
349
+	 * @throws EE_Error|ReflectionException
350
+	 */
351
+	public function tickets_sold($get_sold_for = 'ticket', $dtt_id = null)
352
+	{
353
+		$total        = 0;
354
+		$tickets_sold = $this->_all_tickets_sold();
355
+
356
+		if ($get_sold_for === 'ticket') {
357
+			return $tickets_sold['ticket'];
358
+		}
359
+
360
+		if (empty($tickets_sold['datetime'])) {
361
+			return $total;
362
+		}
363
+
364
+		if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
365
+			EE_Error::add_error(
366
+				esc_html__(
367
+					'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
368
+					'event_espresso'
369
+				),
370
+				__FILE__,
371
+				__FUNCTION__,
372
+				__LINE__
373
+			);
374
+			return $total;
375
+		}
376
+		return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
377
+	}
378
+
379
+
380
+	/**
381
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
382
+	 *
383
+	 * @return EE_Ticket[]
384
+	 * @throws EE_Error
385
+	 * @throws ReflectionException
386
+	 */
387
+	protected function _all_tickets_sold()
388
+	{
389
+		$datetimes    = $this->get_many_related('Datetime');
390
+		$tickets_sold = [];
391
+		if (! empty($datetimes)) {
392
+			foreach ($datetimes as $datetime) {
393
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
394
+			}
395
+		}
396
+		// Tickets sold
397
+		$tickets_sold['ticket'] = $this->sold();
398
+		return $tickets_sold;
399
+	}
400
+
401
+
402
+	/**
403
+	 * This returns the base price object for the ticket.
404
+	 *
405
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
406
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
407
+	 * @throws EE_Error
408
+	 * @throws ReflectionException
409
+	 */
410
+	public function base_price($return_array = false)
411
+	{
412
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
413
+		return $return_array
414
+			? $this->get_many_related('Price', [$_where])
415
+			: $this->get_first_related('Price', [$_where]);
416
+	}
417
+
418
+
419
+	/**
420
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
421
+	 *
422
+	 * @access public
423
+	 * @return EE_Price[]
424
+	 * @throws EE_Error|ReflectionException
425
+	 */
426
+	public function price_modifiers()
427
+	{
428
+		$query_params = [
429
+			0 => [
430
+				'Price_Type.PBT_ID' => [
431
+					'NOT IN',
432
+					[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
433
+				],
434
+			],
435
+		];
436
+		return $this->prices($query_params);
437
+	}
438
+
439
+
440
+	/**
441
+	 * Gets all the prices that combine to form the final price of this ticket
442
+	 *
443
+	 * @param array $query_params
444
+	 * @return EE_Price[]|EE_Base_Class[]
445
+	 * @throws EE_Error
446
+	 * @throws ReflectionException
447
+	 * @see  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
448
+	 */
449
+	public function prices($query_params = [])
450
+	{
451
+		return $this->get_many_related('Price', $query_params);
452
+	}
453
+
454
+
455
+	/**
456
+	 * Gets all the ticket applicabilities (ie, relations between datetimes and tickets)
457
+	 *
458
+	 * @param array $query_params
459
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
460
+	 * @throws EE_Error
461
+	 * @throws ReflectionException
462
+	 * @see  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
463
+	 */
464
+	public function datetime_tickets($query_params = [])
465
+	{
466
+		return $this->get_many_related('Datetime_Ticket', $query_params);
467
+	}
468
+
469
+
470
+	/**
471
+	 * Gets all the datetimes from the db ordered by DTT_order
472
+	 *
473
+	 * @param boolean $show_expired
474
+	 * @param boolean $show_deleted
475
+	 * @return EE_Datetime[]
476
+	 * @throws EE_Error|ReflectionException
477
+	 */
478
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
479
+	{
480
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
481
+			$this->ID(),
482
+			$show_expired,
483
+			$show_deleted
484
+		);
485
+	}
486
+
487
+
488
+	/**
489
+	 * Gets ID
490
+	 *
491
+	 * @return int
492
+	 * @throws EE_Error
493
+	 * @throws ReflectionException
494
+	 */
495
+	public function ID()
496
+	{
497
+		return (int) $this->get('TKT_ID');
498
+	}
499
+
500
+
501
+	/**
502
+	 * get the author of the ticket.
503
+	 *
504
+	 * @return int
505
+	 * @throws EE_Error
506
+	 * @throws ReflectionException
507
+	 * @since 4.5.0
508
+	 */
509
+	public function wp_user()
510
+	{
511
+		return $this->get('TKT_wp_user');
512
+	}
513
+
514
+
515
+	/**
516
+	 * Gets the template for the ticket
517
+	 *
518
+	 * @return EE_Ticket_Template|EE_Base_Class
519
+	 * @throws EE_Error
520
+	 * @throws ReflectionException
521
+	 */
522
+	public function template()
523
+	{
524
+		return $this->get_first_related('Ticket_Template');
525
+	}
526
+
527
+
528
+	/**
529
+	 * Simply returns an array of EE_Price objects that are taxes.
530
+	 *
531
+	 * @return EE_Price[]
532
+	 * @throws EE_Error
533
+	 */
534
+	public function get_ticket_taxes_for_admin()
535
+	{
536
+		return EE_Taxes::get_taxes_for_admin();
537
+	}
538
+
539
+
540
+	/**
541
+	 * @return float
542
+	 * @throws EE_Error
543
+	 * @throws ReflectionException
544
+	 */
545
+	public function ticket_price()
546
+	{
547
+		return $this->get('TKT_price');
548
+	}
549
+
550
+
551
+	/**
552
+	 * @param string|null $schema
553
+	 * @return mixed
554
+	 * @throws EE_Error
555
+	 * @throws ReflectionException
556
+	 */
557
+	public function pretty_price($schema = 'localized_currency')
558
+	{
559
+		return $this->get_pretty('TKT_price', $schema);
560
+	}
561
+
562
+
563
+	/**
564
+	 * @return bool
565
+	 * @throws EE_Error|ReflectionException
566
+	 */
567
+	public function is_free()
568
+	{
569
+		return $this->get_ticket_total_with_taxes() === (float) 0;
570
+	}
571
+
572
+
573
+	/**
574
+	 * get_ticket_total_with_taxes
575
+	 *
576
+	 * @param bool $no_cache
577
+	 * @return float
578
+	 * @throws EE_Error|ReflectionException
579
+	 */
580
+	public function get_ticket_total_with_taxes($no_cache = false)
581
+	{
582
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
583
+			$this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
584
+		}
585
+		return (float) $this->_ticket_total_with_taxes;
586
+	}
587
+
588
+
589
+	/**
590
+	 * @throws EE_Error
591
+	 * @throws ReflectionException
592
+	 */
593
+	public function ensure_TKT_Price_correct()
594
+	{
595
+		$this->set_price(EE_Taxes::get_subtotal_for_admin($this));
596
+		$this->save();
597
+	}
598
+
599
+
600
+	/**
601
+	 * @return float
602
+	 * @throws EE_Error|ReflectionException
603
+	 */
604
+	public function get_ticket_subtotal()
605
+	{
606
+		return EE_Taxes::get_subtotal_for_admin($this);
607
+	}
608
+
609
+
610
+	/**
611
+	 * Returns the total taxes applied to this ticket
612
+	 *
613
+	 * @return float
614
+	 * @throws EE_Error|ReflectionException
615
+	 */
616
+	public function get_ticket_taxes_total_for_admin()
617
+	{
618
+		return EE_Taxes::get_total_taxes_for_admin($this);
619
+	}
620
+
621
+
622
+	/**
623
+	 * Sets name
624
+	 *
625
+	 * @param string $name
626
+	 * @throws EE_Error
627
+	 * @throws ReflectionException
628
+	 */
629
+	public function set_name($name)
630
+	{
631
+		$this->set('TKT_name', $name);
632
+	}
633
+
634
+
635
+	/**
636
+	 * Gets description
637
+	 *
638
+	 * @return string
639
+	 * @throws EE_Error
640
+	 * @throws ReflectionException
641
+	 */
642
+	public function description()
643
+	{
644
+		return $this->get('TKT_description');
645
+	}
646
+
647
+
648
+	/**
649
+	 * Sets description
650
+	 *
651
+	 * @param string $description
652
+	 * @throws EE_Error
653
+	 * @throws ReflectionException
654
+	 */
655
+	public function set_description($description)
656
+	{
657
+		$this->set('TKT_description', $description);
658
+	}
659
+
660
+
661
+	/**
662
+	 * Gets start_date
663
+	 *
664
+	 * @param string $date_format
665
+	 * @param string $time_format
666
+	 * @return string
667
+	 * @throws EE_Error
668
+	 * @throws ReflectionException
669
+	 */
670
+	public function start_date($date_format = '', $time_format = '')
671
+	{
672
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
673
+	}
674
+
675
+
676
+	/**
677
+	 * Sets start_date
678
+	 *
679
+	 * @param string $start_date
680
+	 * @return void
681
+	 * @throws EE_Error
682
+	 * @throws ReflectionException
683
+	 */
684
+	public function set_start_date($start_date)
685
+	{
686
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
687
+	}
688
+
689
+
690
+	/**
691
+	 * Gets end_date
692
+	 *
693
+	 * @param string $date_format
694
+	 * @param string $time_format
695
+	 * @return string
696
+	 * @throws EE_Error
697
+	 * @throws ReflectionException
698
+	 */
699
+	public function end_date($date_format = '', $time_format = '')
700
+	{
701
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
702
+	}
703
+
704
+
705
+	/**
706
+	 * Sets end_date
707
+	 *
708
+	 * @param string $end_date
709
+	 * @return void
710
+	 * @throws EE_Error
711
+	 * @throws ReflectionException
712
+	 */
713
+	public function set_end_date($end_date)
714
+	{
715
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
716
+	}
717
+
718
+
719
+	/**
720
+	 * Sets sell until time
721
+	 *
722
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
723
+	 * @throws EE_Error
724
+	 * @throws ReflectionException
725
+	 * @since 4.5.0
726
+	 */
727
+	public function set_end_time($time)
728
+	{
729
+		$this->_set_time_for($time, 'TKT_end_date');
730
+	}
731
+
732
+
733
+	/**
734
+	 * Sets min
735
+	 *
736
+	 * @param int $min
737
+	 * @return void
738
+	 * @throws EE_Error
739
+	 * @throws ReflectionException
740
+	 */
741
+	public function set_min($min)
742
+	{
743
+		$this->set('TKT_min', $min);
744
+	}
745
+
746
+
747
+	/**
748
+	 * Gets max
749
+	 *
750
+	 * @return int
751
+	 * @throws EE_Error
752
+	 * @throws ReflectionException
753
+	 */
754
+	public function max()
755
+	{
756
+		return $this->get('TKT_max');
757
+	}
758
+
759
+
760
+	/**
761
+	 * Sets max
762
+	 *
763
+	 * @param int $max
764
+	 * @return void
765
+	 * @throws EE_Error
766
+	 * @throws ReflectionException
767
+	 */
768
+	public function set_max($max)
769
+	{
770
+		$this->set('TKT_max', $max);
771
+	}
772
+
773
+
774
+	/**
775
+	 * Sets price
776
+	 *
777
+	 * @param float $price
778
+	 * @return void
779
+	 * @throws EE_Error
780
+	 * @throws ReflectionException
781
+	 */
782
+	public function set_price($price)
783
+	{
784
+		$this->set('TKT_price', $price);
785
+	}
786
+
787
+
788
+	/**
789
+	 * Gets sold
790
+	 *
791
+	 * @return int
792
+	 * @throws EE_Error
793
+	 * @throws ReflectionException
794
+	 */
795
+	public function sold()
796
+	{
797
+		return $this->get_raw('TKT_sold');
798
+	}
799
+
800
+
801
+	/**
802
+	 * Sets sold
803
+	 *
804
+	 * @param int $sold
805
+	 * @return void
806
+	 * @throws EE_Error
807
+	 * @throws ReflectionException
808
+	 */
809
+	public function set_sold($sold)
810
+	{
811
+		// sold can not go below zero
812
+		$sold = max(0, absint($sold));
813
+		$this->set('TKT_sold', $sold);
814
+	}
815
+
816
+
817
+	/**
818
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
819
+	 * associated datetimes.
820
+	 *
821
+	 * @param int $qty
822
+	 * @return boolean
823
+	 * @throws EE_Error
824
+	 * @throws InvalidArgumentException
825
+	 * @throws InvalidDataTypeException
826
+	 * @throws InvalidInterfaceException
827
+	 * @throws ReflectionException
828
+	 * @since 4.9.80.p
829
+	 */
830
+	public function increaseSold($qty = 1)
831
+	{
832
+		$qty = absint($qty);
833
+		// increment sold and decrement reserved datetime quantities simultaneously
834
+		// don't worry about failures, because they must have already had a spot reserved
835
+		$this->increaseSoldForDatetimes($qty);
836
+		// Increment and decrement ticket quantities simultaneously
837
+		$success = $this->adjustNumericFieldsInDb(
838
+			[
839
+				'TKT_reserved' => $qty * -1,
840
+				'TKT_sold'     => $qty,
841
+			]
842
+		);
843
+		do_action(
844
+			'AHEE__EE_Ticket__increase_sold',
845
+			$this,
846
+			$qty,
847
+			$this->sold(),
848
+			$success
849
+		);
850
+		return $success;
851
+	}
852
+
853
+
854
+	/**
855
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
856
+	 *
857
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
858
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
859
+	 * @param EE_Datetime[] $datetimes
860
+	 * @throws EE_Error
861
+	 * @throws InvalidArgumentException
862
+	 * @throws InvalidDataTypeException
863
+	 * @throws InvalidInterfaceException
864
+	 * @throws ReflectionException
865
+	 * @since 4.9.80.p
866
+	 */
867
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
868
+	{
869
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
870
+		foreach ($datetimes as $datetime) {
871
+			$datetime->increaseSold($qty);
872
+		}
873
+	}
874
+
875
+
876
+	/**
877
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
878
+	 * DB and then updates the model objects.
879
+	 * Does not affect the reserved counts.
880
+	 *
881
+	 * @param int $qty
882
+	 * @return boolean
883
+	 * @throws EE_Error
884
+	 * @throws InvalidArgumentException
885
+	 * @throws InvalidDataTypeException
886
+	 * @throws InvalidInterfaceException
887
+	 * @throws ReflectionException
888
+	 * @since 4.9.80.p
889
+	 */
890
+	public function decreaseSold($qty = 1)
891
+	{
892
+		$qty = absint($qty);
893
+		$this->decreaseSoldForDatetimes($qty);
894
+		$success = $this->adjustNumericFieldsInDb(
895
+			[
896
+				'TKT_sold' => $qty * -1,
897
+			]
898
+		);
899
+		do_action(
900
+			'AHEE__EE_Ticket__decrease_sold',
901
+			$this,
902
+			$qty,
903
+			$this->sold(),
904
+			$success
905
+		);
906
+		return $success;
907
+	}
908
+
909
+
910
+	/**
911
+	 * Decreases sold on related datetimes
912
+	 *
913
+	 * @param int           $qty
914
+	 * @param EE_Datetime[] $datetimes
915
+	 * @return void
916
+	 * @throws EE_Error
917
+	 * @throws InvalidArgumentException
918
+	 * @throws InvalidDataTypeException
919
+	 * @throws InvalidInterfaceException
920
+	 * @throws ReflectionException
921
+	 * @since 4.9.80.p
922
+	 */
923
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
924
+	{
925
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
926
+		if (is_array($datetimes)) {
927
+			foreach ($datetimes as $datetime) {
928
+				if ($datetime instanceof EE_Datetime) {
929
+					$datetime->decreaseSold($qty);
930
+				}
931
+			}
932
+		}
933
+	}
934
+
935
+
936
+	/**
937
+	 * Gets qty of reserved tickets
938
+	 *
939
+	 * @return int
940
+	 * @throws EE_Error
941
+	 * @throws ReflectionException
942
+	 */
943
+	public function reserved()
944
+	{
945
+		return $this->get_raw('TKT_reserved');
946
+	}
947
+
948
+
949
+	/**
950
+	 * Sets reserved
951
+	 *
952
+	 * @param int $reserved
953
+	 * @return void
954
+	 * @throws EE_Error
955
+	 * @throws ReflectionException
956
+	 */
957
+	public function set_reserved($reserved)
958
+	{
959
+		// reserved can not go below zero
960
+		$reserved = max(0, (int) $reserved);
961
+		$this->set('TKT_reserved', $reserved);
962
+	}
963
+
964
+
965
+	/**
966
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
967
+	 *
968
+	 * @param int    $qty
969
+	 * @param string $source
970
+	 * @return bool whether we successfully reserved the ticket or not.
971
+	 * @throws EE_Error
972
+	 * @throws InvalidArgumentException
973
+	 * @throws ReflectionException
974
+	 * @throws InvalidDataTypeException
975
+	 * @throws InvalidInterfaceException
976
+	 * @since 4.9.80.p
977
+	 */
978
+	public function increaseReserved($qty = 1, $source = 'unknown')
979
+	{
980
+		$qty = absint($qty);
981
+		do_action(
982
+			'AHEE__EE_Ticket__increase_reserved__begin',
983
+			$this,
984
+			$qty,
985
+			$source
986
+		);
987
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
988
+		$success                         = false;
989
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
990
+		if ($datetimes_adjusted_successfully) {
991
+			$success = $this->incrementFieldConditionallyInDb(
992
+				'TKT_reserved',
993
+				'TKT_sold',
994
+				'TKT_qty',
995
+				$qty
996
+			);
997
+			if (! $success) {
998
+				// The datetimes were successfully bumped, but not the
999
+				// ticket. So we need to manually rollback the datetimes.
1000
+				$this->decreaseReservedForDatetimes($qty);
1001
+			}
1002
+		}
1003
+		do_action(
1004
+			'AHEE__EE_Ticket__increase_reserved',
1005
+			$this,
1006
+			$qty,
1007
+			$this->reserved(),
1008
+			$success
1009
+		);
1010
+		return $success;
1011
+	}
1012
+
1013
+
1014
+	/**
1015
+	 * Increases reserved counts on related datetimes
1016
+	 *
1017
+	 * @param int           $qty
1018
+	 * @param EE_Datetime[] $datetimes
1019
+	 * @return boolean indicating success
1020
+	 * @throws EE_Error
1021
+	 * @throws InvalidArgumentException
1022
+	 * @throws InvalidDataTypeException
1023
+	 * @throws InvalidInterfaceException
1024
+	 * @throws ReflectionException
1025
+	 * @since 4.9.80.p
1026
+	 */
1027
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1028
+	{
1029
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1030
+		$datetimes_updated = [];
1031
+		$limit_exceeded    = false;
1032
+		if (is_array($datetimes)) {
1033
+			foreach ($datetimes as $datetime) {
1034
+				if ($datetime instanceof EE_Datetime) {
1035
+					if ($datetime->increaseReserved($qty)) {
1036
+						$datetimes_updated[] = $datetime;
1037
+					} else {
1038
+						$limit_exceeded = true;
1039
+						break;
1040
+					}
1041
+				}
1042
+			}
1043
+			// If somewhere along the way we detected a datetime whose
1044
+			// limit was exceeded, do a manual rollback.
1045
+			if ($limit_exceeded) {
1046
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1047
+				return false;
1048
+			}
1049
+		}
1050
+		return true;
1051
+	}
1052
+
1053
+
1054
+	/**
1055
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1056
+	 *
1057
+	 * @param int    $qty
1058
+	 * @param bool   $adjust_datetimes
1059
+	 * @param string $source
1060
+	 * @return boolean
1061
+	 * @throws EE_Error
1062
+	 * @throws InvalidArgumentException
1063
+	 * @throws ReflectionException
1064
+	 * @throws InvalidDataTypeException
1065
+	 * @throws InvalidInterfaceException
1066
+	 * @since 4.9.80.p
1067
+	 */
1068
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1069
+	{
1070
+		$qty = absint($qty);
1071
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1072
+		if ($adjust_datetimes) {
1073
+			$this->decreaseReservedForDatetimes($qty);
1074
+		}
1075
+		$success = $this->adjustNumericFieldsInDb(
1076
+			[
1077
+				'TKT_reserved' => $qty * -1,
1078
+			]
1079
+		);
1080
+		do_action(
1081
+			'AHEE__EE_Ticket__decrease_reserved',
1082
+			$this,
1083
+			$qty,
1084
+			$this->reserved(),
1085
+			$success
1086
+		);
1087
+		return $success;
1088
+	}
1089
+
1090
+
1091
+	/**
1092
+	 * Decreases the reserved count on the specified datetimes.
1093
+	 *
1094
+	 * @param int           $qty
1095
+	 * @param EE_Datetime[] $datetimes
1096
+	 * @throws EE_Error
1097
+	 * @throws InvalidArgumentException
1098
+	 * @throws ReflectionException
1099
+	 * @throws InvalidDataTypeException
1100
+	 * @throws InvalidInterfaceException
1101
+	 * @since 4.9.80.p
1102
+	 */
1103
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1104
+	{
1105
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1106
+		foreach ($datetimes as $datetime) {
1107
+			if ($datetime instanceof EE_Datetime) {
1108
+				$datetime->decreaseReserved($qty);
1109
+			}
1110
+		}
1111
+	}
1112
+
1113
+
1114
+	/**
1115
+	 * Gets ticket quantity
1116
+	 *
1117
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1118
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1119
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1120
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1121
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1122
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1123
+	 * @return int
1124
+	 * @throws EE_Error
1125
+	 * @throws ReflectionException
1126
+	 */
1127
+	public function qty($context = '')
1128
+	{
1129
+		switch ($context) {
1130
+			case 'reg_limit':
1131
+				return $this->real_quantity_on_ticket();
1132
+			case 'saleable':
1133
+				return $this->real_quantity_on_ticket('saleable');
1134
+			default:
1135
+				return $this->get_raw('TKT_qty');
1136
+		}
1137
+	}
1138
+
1139
+
1140
+	/**
1141
+	 * Gets ticket quantity
1142
+	 *
1143
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1144
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1145
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1146
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1147
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1148
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1149
+	 *                            set to 0 for all related datetimes
1150
+	 * @return int
1151
+	 * @throws EE_Error
1152
+	 * @throws ReflectionException
1153
+	 */
1154
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1155
+	{
1156
+		$raw = $this->get_raw('TKT_qty');
1157
+		// return immediately if it's zero
1158
+		if ($raw === 0) {
1159
+			return $raw;
1160
+		}
1161
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1162
+		// ensure qty doesn't exceed raw value for THIS ticket
1163
+		$qty = min(EE_INF, $raw);
1164
+		// echo "\n . qty: " . $qty . '<br />';
1165
+		// calculate this ticket's total sales and reservations
1166
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1167
+		// echo "\n . sold: " . $this->sold() . '<br />';
1168
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1169
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1170
+		// first we need to calculate the maximum number of tickets available for the datetime
1171
+		// do we want data for one datetime or all of them ?
1172
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1173
+		$datetimes    = $this->datetimes($query_params);
1174
+		if (is_array($datetimes) && ! empty($datetimes)) {
1175
+			foreach ($datetimes as $datetime) {
1176
+				if ($datetime instanceof EE_Datetime) {
1177
+					$datetime->refresh_from_db();
1178
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1179
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1180
+					// initialize with no restrictions for each datetime
1181
+					// but adjust datetime qty based on datetime reg limit
1182
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1183
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1184
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1185
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1186
+					// and reservations for this datetime, that do NOT include sales and reservations
1187
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1188
+					if ($context === 'saleable') {
1189
+						$datetime_qty = max(
1190
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1191
+							0
1192
+						);
1193
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1194
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1195
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1196
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1197
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1198
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1199
+					}
1200
+					$qty = min($datetime_qty, $qty);
1201
+					// echo "\n . . qty: " . $qty . '<br />';
1202
+				}
1203
+			}
1204
+		}
1205
+		// NOW that we know the  maximum number of tickets available for the datetime
1206
+		// we can finally factor in the details for this specific ticket
1207
+		if ($qty > 0 && $context === 'saleable') {
1208
+			// and subtract the sales for THIS ticket
1209
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1210
+			// echo "\n . qty: " . $qty . '<br />';
1211
+		}
1212
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1213
+		return $qty;
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1219
+	 *
1220
+	 * @param int $qty
1221
+	 * @return void
1222
+	 * @throws EE_Error
1223
+	 * @throws ReflectionException
1224
+	 */
1225
+	public function set_qty($qty)
1226
+	{
1227
+		$datetimes = $this->datetimes();
1228
+		foreach ($datetimes as $datetime) {
1229
+			if ($datetime instanceof EE_Datetime) {
1230
+				$qty = min($qty, $datetime->reg_limit());
1231
+			}
1232
+		}
1233
+		$this->set('TKT_qty', $qty);
1234
+	}
1235
+
1236
+
1237
+	/**
1238
+	 * Gets uses
1239
+	 *
1240
+	 * @return int
1241
+	 * @throws EE_Error
1242
+	 * @throws ReflectionException
1243
+	 */
1244
+	public function uses()
1245
+	{
1246
+		return $this->get('TKT_uses');
1247
+	}
1248
+
1249
+
1250
+	/**
1251
+	 * Sets uses
1252
+	 *
1253
+	 * @param int $uses
1254
+	 * @return void
1255
+	 * @throws EE_Error
1256
+	 * @throws ReflectionException
1257
+	 */
1258
+	public function set_uses($uses)
1259
+	{
1260
+		$this->set('TKT_uses', $uses);
1261
+	}
1262
+
1263
+
1264
+	/**
1265
+	 * returns whether ticket is required or not.
1266
+	 *
1267
+	 * @return boolean
1268
+	 * @throws EE_Error
1269
+	 * @throws ReflectionException
1270
+	 */
1271
+	public function required()
1272
+	{
1273
+		return $this->get('TKT_required');
1274
+	}
1275
+
1276
+
1277
+	/**
1278
+	 * sets the TKT_required property
1279
+	 *
1280
+	 * @param boolean $required
1281
+	 * @return void
1282
+	 * @throws EE_Error
1283
+	 * @throws ReflectionException
1284
+	 */
1285
+	public function set_required($required)
1286
+	{
1287
+		$this->set('TKT_required', $required);
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * Gets taxable
1293
+	 *
1294
+	 * @return boolean
1295
+	 * @throws EE_Error
1296
+	 * @throws ReflectionException
1297
+	 */
1298
+	public function taxable()
1299
+	{
1300
+		return $this->get('TKT_taxable');
1301
+	}
1302
+
1303
+
1304
+	/**
1305
+	 * Sets taxable
1306
+	 *
1307
+	 * @param boolean $taxable
1308
+	 * @return void
1309
+	 * @throws EE_Error
1310
+	 * @throws ReflectionException
1311
+	 */
1312
+	public function set_taxable($taxable)
1313
+	{
1314
+		$this->set('TKT_taxable', $taxable);
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * Gets is_default
1320
+	 *
1321
+	 * @return boolean
1322
+	 * @throws EE_Error
1323
+	 * @throws ReflectionException
1324
+	 */
1325
+	public function is_default()
1326
+	{
1327
+		return $this->get('TKT_is_default');
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Sets is_default
1333
+	 *
1334
+	 * @param boolean $is_default
1335
+	 * @return void
1336
+	 * @throws EE_Error
1337
+	 * @throws ReflectionException
1338
+	 */
1339
+	public function set_is_default($is_default)
1340
+	{
1341
+		$this->set('TKT_is_default', $is_default);
1342
+	}
1343
+
1344
+
1345
+	/**
1346
+	 * Gets order
1347
+	 *
1348
+	 * @return int
1349
+	 * @throws EE_Error
1350
+	 * @throws ReflectionException
1351
+	 */
1352
+	public function order()
1353
+	{
1354
+		return $this->get('TKT_order');
1355
+	}
1356
+
1357
+
1358
+	/**
1359
+	 * Sets order
1360
+	 *
1361
+	 * @param int $order
1362
+	 * @return void
1363
+	 * @throws EE_Error
1364
+	 * @throws ReflectionException
1365
+	 */
1366
+	public function set_order($order)
1367
+	{
1368
+		$this->set('TKT_order', $order);
1369
+	}
1370
+
1371
+
1372
+	/**
1373
+	 * Gets row
1374
+	 *
1375
+	 * @return int
1376
+	 * @throws EE_Error
1377
+	 * @throws ReflectionException
1378
+	 */
1379
+	public function row()
1380
+	{
1381
+		return $this->get('TKT_row');
1382
+	}
1383
+
1384
+
1385
+	/**
1386
+	 * Sets row
1387
+	 *
1388
+	 * @param int $row
1389
+	 * @return void
1390
+	 * @throws EE_Error
1391
+	 * @throws ReflectionException
1392
+	 */
1393
+	public function set_row($row)
1394
+	{
1395
+		$this->set('TKT_row', $row);
1396
+	}
1397
+
1398
+
1399
+	/**
1400
+	 * Gets deleted
1401
+	 *
1402
+	 * @return boolean
1403
+	 * @throws EE_Error
1404
+	 * @throws ReflectionException
1405
+	 */
1406
+	public function deleted()
1407
+	{
1408
+		return $this->get('TKT_deleted');
1409
+	}
1410
+
1411
+
1412
+	/**
1413
+	 * Sets deleted
1414
+	 *
1415
+	 * @param boolean $deleted
1416
+	 * @return void
1417
+	 * @throws EE_Error
1418
+	 * @throws ReflectionException
1419
+	 */
1420
+	public function set_deleted($deleted)
1421
+	{
1422
+		$this->set('TKT_deleted', $deleted);
1423
+	}
1424
+
1425
+
1426
+	/**
1427
+	 * Gets parent
1428
+	 *
1429
+	 * @return int
1430
+	 * @throws EE_Error
1431
+	 * @throws ReflectionException
1432
+	 */
1433
+	public function parent_ID()
1434
+	{
1435
+		return $this->get('TKT_parent');
1436
+	}
1437
+
1438
+
1439
+	/**
1440
+	 * Sets parent
1441
+	 *
1442
+	 * @param int $parent
1443
+	 * @return void
1444
+	 * @throws EE_Error
1445
+	 * @throws ReflectionException
1446
+	 */
1447
+	public function set_parent_ID($parent)
1448
+	{
1449
+		$this->set('TKT_parent', $parent);
1450
+	}
1451
+
1452
+
1453
+	/**
1454
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1455
+	 *
1456
+	 * @return string
1457
+	 * @throws EE_Error
1458
+	 * @throws ReflectionException
1459
+	 */
1460
+	public function name_and_info()
1461
+	{
1462
+		$times = [];
1463
+		foreach ($this->datetimes() as $datetime) {
1464
+			$times[] = $datetime->start_date_and_time();
1465
+		}
1466
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1467
+	}
1468
+
1469
+
1470
+	/**
1471
+	 * Gets name
1472
+	 *
1473
+	 * @return string
1474
+	 * @throws EE_Error
1475
+	 * @throws ReflectionException
1476
+	 */
1477
+	public function name()
1478
+	{
1479
+		return $this->get('TKT_name');
1480
+	}
1481
+
1482
+
1483
+	/**
1484
+	 * Gets price
1485
+	 *
1486
+	 * @return float
1487
+	 * @throws EE_Error
1488
+	 * @throws ReflectionException
1489
+	 */
1490
+	public function price()
1491
+	{
1492
+		return $this->get('TKT_price');
1493
+	}
1494
+
1495
+
1496
+	/**
1497
+	 * Gets all the registrations for this ticket
1498
+	 *
1499
+	 * @param array $query_params
1500
+	 * @return EE_Registration[]|EE_Base_Class[]
1501
+	 * @throws EE_Error
1502
+	 * @throws ReflectionException
1503
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1504
+	 */
1505
+	public function registrations($query_params = [])
1506
+	{
1507
+		return $this->get_many_related('Registration', $query_params);
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1513
+	 *
1514
+	 * @return int
1515
+	 * @throws EE_Error
1516
+	 * @throws ReflectionException
1517
+	 */
1518
+	public function update_tickets_sold()
1519
+	{
1520
+		$count_regs_for_this_ticket = $this->count_registrations(
1521
+			[
1522
+				[
1523
+					'STS_ID'      => EEM_Registration::status_id_approved,
1524
+					'REG_deleted' => 0,
1525
+				],
1526
+			]
1527
+		);
1528
+		$this->set_sold($count_regs_for_this_ticket);
1529
+		$this->save();
1530
+		return $count_regs_for_this_ticket;
1531
+	}
1532
+
1533
+
1534
+	/**
1535
+	 * Counts the registrations for this ticket
1536
+	 *
1537
+	 * @param array $query_params
1538
+	 * @return int
1539
+	 * @throws EE_Error
1540
+	 * @throws ReflectionException
1541
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1542
+	 */
1543
+	public function count_registrations($query_params = [])
1544
+	{
1545
+		return $this->count_related('Registration', $query_params);
1546
+	}
1547
+
1548
+
1549
+	/**
1550
+	 * Implementation for EEI_Has_Icon interface method.
1551
+	 *
1552
+	 * @return string
1553
+	 * @see EEI_Visual_Representation for comments
1554
+	 */
1555
+	public function get_icon()
1556
+	{
1557
+		return '<span class="dashicons dashicons-tickets-alt"></span>';
1558
+	}
1559
+
1560
+
1561
+	/**
1562
+	 * Implementation of the EEI_Event_Relation interface method
1563
+	 *
1564
+	 * @return EE_Event
1565
+	 * @throws EE_Error
1566
+	 * @throws UnexpectedEntityException
1567
+	 * @throws ReflectionException
1568
+	 * @see EEI_Event_Relation for comments
1569
+	 */
1570
+	public function get_related_event()
1571
+	{
1572
+		// get one datetime to use for getting the event
1573
+		$datetime = $this->first_datetime();
1574
+		if (! $datetime instanceof EE_Datetime) {
1575
+			throw new UnexpectedEntityException(
1576
+				$datetime,
1577
+				'EE_Datetime',
1578
+				sprintf(
1579
+					esc_html__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1580
+					$this->name()
1581
+				)
1582
+			);
1583
+		}
1584
+		$event = $datetime->event();
1585
+		if (! $event instanceof EE_Event) {
1586
+			throw new UnexpectedEntityException(
1587
+				$event,
1588
+				'EE_Event',
1589
+				sprintf(
1590
+					esc_html__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1591
+					$this->name()
1592
+				)
1593
+			);
1594
+		}
1595
+		return $event;
1596
+	}
1597
+
1598
+
1599
+	/**
1600
+	 * Implementation of the EEI_Event_Relation interface method
1601
+	 *
1602
+	 * @return string
1603
+	 * @throws UnexpectedEntityException
1604
+	 * @throws EE_Error
1605
+	 * @throws ReflectionException
1606
+	 * @see EEI_Event_Relation for comments
1607
+	 */
1608
+	public function get_event_name()
1609
+	{
1610
+		$event = $this->get_related_event();
1611
+		return $event instanceof EE_Event ? $event->name() : '';
1612
+	}
1613
+
1614
+
1615
+	/**
1616
+	 * Implementation of the EEI_Event_Relation interface method
1617
+	 *
1618
+	 * @return int
1619
+	 * @throws UnexpectedEntityException
1620
+	 * @throws EE_Error
1621
+	 * @throws ReflectionException
1622
+	 * @see EEI_Event_Relation for comments
1623
+	 */
1624
+	public function get_event_ID()
1625
+	{
1626
+		$event = $this->get_related_event();
1627
+		return $event instanceof EE_Event ? $event->ID() : 0;
1628
+	}
1629
+
1630
+
1631
+	/**
1632
+	 * This simply returns whether a ticket can be permanently deleted or not.
1633
+	 * The criteria for determining this is whether the ticket has any related registrations.
1634
+	 * If there are none then it can be permanently deleted.
1635
+	 *
1636
+	 * @return bool
1637
+	 * @throws EE_Error
1638
+	 * @throws ReflectionException
1639
+	 */
1640
+	public function is_permanently_deleteable()
1641
+	{
1642
+		return $this->count_registrations() === 0;
1643
+	}
1644
+
1645
+
1646
+	/*******************************************************************
1647 1647
      ***********************  DEPRECATED METHODS  **********************
1648 1648
      *******************************************************************/
1649 1649
 
1650 1650
 
1651
-    /**
1652
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1653
-     * associated datetimes.
1654
-     *
1655
-     * @param int $qty
1656
-     * @return void
1657
-     * @throws EE_Error
1658
-     * @throws InvalidArgumentException
1659
-     * @throws InvalidDataTypeException
1660
-     * @throws InvalidInterfaceException
1661
-     * @throws ReflectionException
1662
-     * @deprecated 4.9.80.p
1663
-     */
1664
-    public function increase_sold($qty = 1)
1665
-    {
1666
-        EE_Error::doing_it_wrong(
1667
-            __FUNCTION__,
1668
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1669
-            '4.9.80.p',
1670
-            '5.0.0.p'
1671
-        );
1672
-        $this->increaseSold($qty);
1673
-    }
1674
-
1675
-
1676
-    /**
1677
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1678
-     *
1679
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1680
-     *                 Negative means to decreases old counts (and increase reserved counts).
1681
-     * @throws EE_Error
1682
-     * @throws InvalidArgumentException
1683
-     * @throws InvalidDataTypeException
1684
-     * @throws InvalidInterfaceException
1685
-     * @throws ReflectionException
1686
-     * @deprecated 4.9.80.p
1687
-     */
1688
-    protected function _increase_sold_for_datetimes($qty)
1689
-    {
1690
-        EE_Error::doing_it_wrong(
1691
-            __FUNCTION__,
1692
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1693
-            '4.9.80.p',
1694
-            '5.0.0.p'
1695
-        );
1696
-        $this->increaseSoldForDatetimes($qty);
1697
-    }
1698
-
1699
-
1700
-    /**
1701
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1702
-     * DB and then updates the model objects.
1703
-     * Does not affect the reserved counts.
1704
-     *
1705
-     * @param int $qty
1706
-     * @return void
1707
-     * @throws EE_Error
1708
-     * @throws InvalidArgumentException
1709
-     * @throws InvalidDataTypeException
1710
-     * @throws InvalidInterfaceException
1711
-     * @throws ReflectionException
1712
-     * @deprecated 4.9.80.p
1713
-     */
1714
-    public function decrease_sold($qty = 1)
1715
-    {
1716
-        EE_Error::doing_it_wrong(
1717
-            __FUNCTION__,
1718
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1719
-            '4.9.80.p',
1720
-            '5.0.0.p'
1721
-        );
1722
-        $this->decreaseSold($qty);
1723
-    }
1724
-
1725
-
1726
-    /**
1727
-     * Decreases sold on related datetimes
1728
-     *
1729
-     * @param int $qty
1730
-     * @return void
1731
-     * @throws EE_Error
1732
-     * @throws InvalidArgumentException
1733
-     * @throws InvalidDataTypeException
1734
-     * @throws InvalidInterfaceException
1735
-     * @throws ReflectionException
1736
-     * @deprecated 4.9.80.p
1737
-     */
1738
-    protected function _decrease_sold_for_datetimes($qty = 1)
1739
-    {
1740
-        EE_Error::doing_it_wrong(
1741
-            __FUNCTION__,
1742
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1743
-            '4.9.80.p',
1744
-            '5.0.0.p'
1745
-        );
1746
-        $this->decreaseSoldForDatetimes($qty);
1747
-    }
1748
-
1749
-
1750
-    /**
1751
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1752
-     *
1753
-     * @param int    $qty
1754
-     * @param string $source
1755
-     * @return bool whether we successfully reserved the ticket or not.
1756
-     * @throws EE_Error
1757
-     * @throws InvalidArgumentException
1758
-     * @throws ReflectionException
1759
-     * @throws InvalidDataTypeException
1760
-     * @throws InvalidInterfaceException
1761
-     * @deprecated 4.9.80.p
1762
-     */
1763
-    public function increase_reserved($qty = 1, $source = 'unknown')
1764
-    {
1765
-        EE_Error::doing_it_wrong(
1766
-            __FUNCTION__,
1767
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1768
-            '4.9.80.p',
1769
-            '5.0.0.p'
1770
-        );
1771
-        return $this->increaseReserved($qty);
1772
-    }
1773
-
1774
-
1775
-    /**
1776
-     * Increases sold on related datetimes
1777
-     *
1778
-     * @param int $qty
1779
-     * @return boolean indicating success
1780
-     * @throws EE_Error
1781
-     * @throws InvalidArgumentException
1782
-     * @throws InvalidDataTypeException
1783
-     * @throws InvalidInterfaceException
1784
-     * @throws ReflectionException
1785
-     * @deprecated 4.9.80.p
1786
-     */
1787
-    protected function _increase_reserved_for_datetimes($qty = 1)
1788
-    {
1789
-        EE_Error::doing_it_wrong(
1790
-            __FUNCTION__,
1791
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1792
-            '4.9.80.p',
1793
-            '5.0.0.p'
1794
-        );
1795
-        return $this->increaseReservedForDatetimes($qty);
1796
-    }
1797
-
1798
-
1799
-    /**
1800
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1801
-     *
1802
-     * @param int    $qty
1803
-     * @param bool   $adjust_datetimes
1804
-     * @param string $source
1805
-     * @return void
1806
-     * @throws EE_Error
1807
-     * @throws InvalidArgumentException
1808
-     * @throws ReflectionException
1809
-     * @throws InvalidDataTypeException
1810
-     * @throws InvalidInterfaceException
1811
-     * @deprecated 4.9.80.p
1812
-     */
1813
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1814
-    {
1815
-        EE_Error::doing_it_wrong(
1816
-            __FUNCTION__,
1817
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1818
-            '4.9.80.p',
1819
-            '5.0.0.p'
1820
-        );
1821
-        $this->decreaseReserved($qty);
1822
-    }
1823
-
1824
-
1825
-    /**
1826
-     * Decreases reserved on related datetimes
1827
-     *
1828
-     * @param int $qty
1829
-     * @return void
1830
-     * @throws EE_Error
1831
-     * @throws InvalidArgumentException
1832
-     * @throws ReflectionException
1833
-     * @throws InvalidDataTypeException
1834
-     * @throws InvalidInterfaceException
1835
-     * @deprecated 4.9.80.p
1836
-     */
1837
-    protected function _decrease_reserved_for_datetimes($qty = 1)
1838
-    {
1839
-        EE_Error::doing_it_wrong(
1840
-            __FUNCTION__,
1841
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
1842
-            '4.9.80.p',
1843
-            '5.0.0.p'
1844
-        );
1845
-        $this->decreaseReservedForDatetimes($qty);
1846
-    }
1651
+	/**
1652
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1653
+	 * associated datetimes.
1654
+	 *
1655
+	 * @param int $qty
1656
+	 * @return void
1657
+	 * @throws EE_Error
1658
+	 * @throws InvalidArgumentException
1659
+	 * @throws InvalidDataTypeException
1660
+	 * @throws InvalidInterfaceException
1661
+	 * @throws ReflectionException
1662
+	 * @deprecated 4.9.80.p
1663
+	 */
1664
+	public function increase_sold($qty = 1)
1665
+	{
1666
+		EE_Error::doing_it_wrong(
1667
+			__FUNCTION__,
1668
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1669
+			'4.9.80.p',
1670
+			'5.0.0.p'
1671
+		);
1672
+		$this->increaseSold($qty);
1673
+	}
1674
+
1675
+
1676
+	/**
1677
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1678
+	 *
1679
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1680
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1681
+	 * @throws EE_Error
1682
+	 * @throws InvalidArgumentException
1683
+	 * @throws InvalidDataTypeException
1684
+	 * @throws InvalidInterfaceException
1685
+	 * @throws ReflectionException
1686
+	 * @deprecated 4.9.80.p
1687
+	 */
1688
+	protected function _increase_sold_for_datetimes($qty)
1689
+	{
1690
+		EE_Error::doing_it_wrong(
1691
+			__FUNCTION__,
1692
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1693
+			'4.9.80.p',
1694
+			'5.0.0.p'
1695
+		);
1696
+		$this->increaseSoldForDatetimes($qty);
1697
+	}
1698
+
1699
+
1700
+	/**
1701
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1702
+	 * DB and then updates the model objects.
1703
+	 * Does not affect the reserved counts.
1704
+	 *
1705
+	 * @param int $qty
1706
+	 * @return void
1707
+	 * @throws EE_Error
1708
+	 * @throws InvalidArgumentException
1709
+	 * @throws InvalidDataTypeException
1710
+	 * @throws InvalidInterfaceException
1711
+	 * @throws ReflectionException
1712
+	 * @deprecated 4.9.80.p
1713
+	 */
1714
+	public function decrease_sold($qty = 1)
1715
+	{
1716
+		EE_Error::doing_it_wrong(
1717
+			__FUNCTION__,
1718
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1719
+			'4.9.80.p',
1720
+			'5.0.0.p'
1721
+		);
1722
+		$this->decreaseSold($qty);
1723
+	}
1724
+
1725
+
1726
+	/**
1727
+	 * Decreases sold on related datetimes
1728
+	 *
1729
+	 * @param int $qty
1730
+	 * @return void
1731
+	 * @throws EE_Error
1732
+	 * @throws InvalidArgumentException
1733
+	 * @throws InvalidDataTypeException
1734
+	 * @throws InvalidInterfaceException
1735
+	 * @throws ReflectionException
1736
+	 * @deprecated 4.9.80.p
1737
+	 */
1738
+	protected function _decrease_sold_for_datetimes($qty = 1)
1739
+	{
1740
+		EE_Error::doing_it_wrong(
1741
+			__FUNCTION__,
1742
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1743
+			'4.9.80.p',
1744
+			'5.0.0.p'
1745
+		);
1746
+		$this->decreaseSoldForDatetimes($qty);
1747
+	}
1748
+
1749
+
1750
+	/**
1751
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1752
+	 *
1753
+	 * @param int    $qty
1754
+	 * @param string $source
1755
+	 * @return bool whether we successfully reserved the ticket or not.
1756
+	 * @throws EE_Error
1757
+	 * @throws InvalidArgumentException
1758
+	 * @throws ReflectionException
1759
+	 * @throws InvalidDataTypeException
1760
+	 * @throws InvalidInterfaceException
1761
+	 * @deprecated 4.9.80.p
1762
+	 */
1763
+	public function increase_reserved($qty = 1, $source = 'unknown')
1764
+	{
1765
+		EE_Error::doing_it_wrong(
1766
+			__FUNCTION__,
1767
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1768
+			'4.9.80.p',
1769
+			'5.0.0.p'
1770
+		);
1771
+		return $this->increaseReserved($qty);
1772
+	}
1773
+
1774
+
1775
+	/**
1776
+	 * Increases sold on related datetimes
1777
+	 *
1778
+	 * @param int $qty
1779
+	 * @return boolean indicating success
1780
+	 * @throws EE_Error
1781
+	 * @throws InvalidArgumentException
1782
+	 * @throws InvalidDataTypeException
1783
+	 * @throws InvalidInterfaceException
1784
+	 * @throws ReflectionException
1785
+	 * @deprecated 4.9.80.p
1786
+	 */
1787
+	protected function _increase_reserved_for_datetimes($qty = 1)
1788
+	{
1789
+		EE_Error::doing_it_wrong(
1790
+			__FUNCTION__,
1791
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1792
+			'4.9.80.p',
1793
+			'5.0.0.p'
1794
+		);
1795
+		return $this->increaseReservedForDatetimes($qty);
1796
+	}
1797
+
1798
+
1799
+	/**
1800
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1801
+	 *
1802
+	 * @param int    $qty
1803
+	 * @param bool   $adjust_datetimes
1804
+	 * @param string $source
1805
+	 * @return void
1806
+	 * @throws EE_Error
1807
+	 * @throws InvalidArgumentException
1808
+	 * @throws ReflectionException
1809
+	 * @throws InvalidDataTypeException
1810
+	 * @throws InvalidInterfaceException
1811
+	 * @deprecated 4.9.80.p
1812
+	 */
1813
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1814
+	{
1815
+		EE_Error::doing_it_wrong(
1816
+			__FUNCTION__,
1817
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1818
+			'4.9.80.p',
1819
+			'5.0.0.p'
1820
+		);
1821
+		$this->decreaseReserved($qty);
1822
+	}
1823
+
1824
+
1825
+	/**
1826
+	 * Decreases reserved on related datetimes
1827
+	 *
1828
+	 * @param int $qty
1829
+	 * @return void
1830
+	 * @throws EE_Error
1831
+	 * @throws InvalidArgumentException
1832
+	 * @throws ReflectionException
1833
+	 * @throws InvalidDataTypeException
1834
+	 * @throws InvalidInterfaceException
1835
+	 * @deprecated 4.9.80.p
1836
+	 */
1837
+	protected function _decrease_reserved_for_datetimes($qty = 1)
1838
+	{
1839
+		EE_Error::doing_it_wrong(
1840
+			__FUNCTION__,
1841
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
1842
+			'4.9.80.p',
1843
+			'5.0.0.p'
1844
+		);
1845
+		$this->decreaseReservedForDatetimes($qty);
1846
+	}
1847 1847
 }
Please login to merge, or discard this patch.
Spacing   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
     public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
67 67
     {
68 68
         $ticket = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
69
-        if (! $ticket instanceof EE_Ticket) {
69
+        if ( ! $ticket instanceof EE_Ticket) {
70 70
             $ticket = new EE_Ticket($props_n_values, false, $timezone, $date_formats);
71 71
         }
72 72
         return $ticket;
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
     public function ticket_status($display = false, $remaining = null)
151 151
     {
152 152
         $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
153
-        if (! $remaining) {
153
+        if ( ! $remaining) {
154 154
             return $display
155 155
                 ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence')
156 156
                 : EE_Ticket::sold_out;
@@ -287,7 +287,7 @@  discard block
 block discarded – undo
287 287
             ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
288 288
             : '';
289 289
 
290
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
290
+        return $first_date && $last_date ? $first_date.$conjunction.$last_date : '';
291 291
     }
292 292
 
293 293
 
@@ -315,7 +315,7 @@  discard block
 block discarded – undo
315 315
      */
316 316
     public function datetimes($query_params = [])
317 317
     {
318
-        if (! isset($query_params['order_by'])) {
318
+        if ( ! isset($query_params['order_by'])) {
319 319
             $query_params['order_by']['DTT_order'] = 'ASC';
320 320
         }
321 321
         return $this->get_many_related('Datetime', $query_params);
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
             return $total;
362 362
         }
363 363
 
364
-        if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
364
+        if ( ! empty($dtt_id) && ! isset($tickets_sold['datetime'][$dtt_id])) {
365 365
             EE_Error::add_error(
366 366
                 esc_html__(
367 367
                     'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
@@ -373,7 +373,7 @@  discard block
 block discarded – undo
373 373
             );
374 374
             return $total;
375 375
         }
376
-        return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
376
+        return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][$dtt_id];
377 377
     }
378 378
 
379 379
 
@@ -388,9 +388,9 @@  discard block
 block discarded – undo
388 388
     {
389 389
         $datetimes    = $this->get_many_related('Datetime');
390 390
         $tickets_sold = [];
391
-        if (! empty($datetimes)) {
391
+        if ( ! empty($datetimes)) {
392 392
             foreach ($datetimes as $datetime) {
393
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
393
+                $tickets_sold['datetime'][$datetime->ID()] = $datetime->get('DTT_sold');
394 394
             }
395 395
         }
396 396
         // Tickets sold
@@ -994,7 +994,7 @@  discard block
 block discarded – undo
994 994
                 'TKT_qty',
995 995
                 $qty
996 996
             );
997
-            if (! $success) {
997
+            if ( ! $success) {
998 998
                 // The datetimes were successfully bumped, but not the
999 999
                 // ticket. So we need to manually rollback the datetimes.
1000 1000
                 $this->decreaseReservedForDatetimes($qty);
@@ -1463,7 +1463,7 @@  discard block
 block discarded – undo
1463 1463
         foreach ($this->datetimes() as $datetime) {
1464 1464
             $times[] = $datetime->start_date_and_time();
1465 1465
         }
1466
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1466
+        return $this->name().' @ '.implode(', ', $times).' for '.$this->pretty_price();
1467 1467
     }
1468 1468
 
1469 1469
 
@@ -1571,7 +1571,7 @@  discard block
 block discarded – undo
1571 1571
     {
1572 1572
         // get one datetime to use for getting the event
1573 1573
         $datetime = $this->first_datetime();
1574
-        if (! $datetime instanceof EE_Datetime) {
1574
+        if ( ! $datetime instanceof EE_Datetime) {
1575 1575
             throw new UnexpectedEntityException(
1576 1576
                 $datetime,
1577 1577
                 'EE_Datetime',
@@ -1582,7 +1582,7 @@  discard block
 block discarded – undo
1582 1582
             );
1583 1583
         }
1584 1584
         $event = $datetime->event();
1585
-        if (! $event instanceof EE_Event) {
1585
+        if ( ! $event instanceof EE_Event) {
1586 1586
             throw new UnexpectedEntityException(
1587 1587
                 $event,
1588 1588
                 'EE_Event',
Please login to merge, or discard this patch.
core/db_classes/EE_CSV.class.php 1 patch
Indentation   +564 added lines, -564 removed lines patch added patch discarded remove patch
@@ -13,568 +13,568 @@
 block discarded – undo
13 13
  */
14 14
 class EE_CSV
15 15
 {
16
-    /**
17
-     * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
18
-     */
19
-    const metadata_header = 'Event Espresso Export Meta Data';
20
-
21
-    /**
22
-     * @var EE_CSV
23
-     */
24
-    private static $_instance = null;
25
-
26
-
27
-    /**
28
-     * @return void
29
-     */
30
-    private function __construct()
31
-    {
32
-    }
33
-
34
-
35
-    /**
36
-     * singleton method used to instantiate class object
37
-     *
38
-     * @return EE_CSV
39
-     */
40
-    public static function instance(): EE_CSV
41
-    {
42
-        // check if class object is instantiated
43
-        if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
44
-            self::$_instance = new self();
45
-        }
46
-        return self::$_instance;
47
-    }
48
-
49
-
50
-    /**
51
-     * Opens a unicode or utf file (normal file_get_contents has difficulty reading a unicode file. @param string
52
-     * $file_path
53
-     *
54
-     * @return string
55
-     * @throws EE_Error
56
-     * @see http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
57
-     *
58
-     */
59
-    private function read_unicode_file(string $file_path): string
60
-    {
61
-        $fc = "";
62
-        $fh = fopen($file_path, "rb");
63
-        if (! $fh) {
64
-            throw new EE_Error(
65
-                sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path)
66
-            );
67
-        }
68
-        $file_length = filesize($file_path);
69
-        $bc   = fread($fh, $file_length);
70
-        for ($i = 0; $i < $file_length; $i++) {
71
-            $c = substr($bc, $i, 1);
72
-            if ((ord($c) != 0) && (ord($c) != 13)) {
73
-                $fc = $fc . $c;
74
-            }
75
-        }
76
-        if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
77
-            $fc = substr($fc, 2);
78
-        }
79
-        return ($fc);
80
-    }
81
-
82
-
83
-    /**
84
-     * Generic CSV-functionality to turn an entire CSV file into a single array that's
85
-     * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
86
-     * representing each row in the CSV file, and the second-level arrays being each column in that row
87
-     *
88
-     * @param string $path_to_file
89
-     * @return array of arrays. Top-level array has rows, second-level array has each item
90
-     * @throws EE_Error
91
-     */
92
-    public function import_csv_to_multi_dimensional_array(string $path_to_file): array
93
-    {
94
-        // needed to deal with Mac line endings
95
-        ini_set('auto_detect_line_endings', true);
96
-        // because fgetcsv does not correctly deal with backslashed quotes such as \"
97
-        // we'll read the file into a string
98
-        $file_contents = $this->read_unicode_file($path_to_file);
99
-        // replace backslashed quotes with CSV enclosures
100
-        $file_contents = str_replace('\\"', '"""', $file_contents);
101
-        // HEY YOU! PUT THAT FILE BACK!!!
102
-        file_put_contents($path_to_file, $file_contents);
103
-
104
-        if (($file_handle = fopen($path_to_file, "r")) !== false) {
105
-            $csv_array = [];
106
-            // loop through each row of the file
107
-            while (($data = fgetcsv($file_handle, 0)) !== false) {
108
-                $csv_array[] = $data;
109
-            }
110
-            # Close the File.
111
-            fclose($file_handle);
112
-            return $csv_array;
113
-        }
114
-        EE_Error::add_error(
115
-            sprintf(
116
-                esc_html__('An error occurred - the file: %s could not opened.', 'event_espresso'),
117
-                $path_to_file
118
-            ),
119
-            __FILE__,
120
-            __FUNCTION__,
121
-            __LINE__
122
-        );
123
-        return [];
124
-    }
125
-
126
-
127
-    /**
128
-     * import contents of csv file and store values in an array to be manipulated by other functions
129
-     *
130
-     * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
131
-     *                                      If $model_name is provided, assumes that each row in the CSV represents a
132
-     *                                      model object for that model If $model_name ISN'T provided, assumes that
133
-     *                                      before model object data, there is a row where the first entry is simply
134
-     *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
135
-     *                                      then maybe a row of headers, and then the model data. Eg.
136
-     *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
137
-     *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
138
-     *                                      Forest
139
-     * @param string  $model_name           model name if we know what model we're importing
140
-     * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
141
-     *                                      data
142
-     * @return array|false                  - array on success - multi dimensional with headers as keys
143
-     *                                      (if headers exist) OR string on fail -
144
-     *                                      error message like the following array('Event'=>array(
145
-     *                                      array('EVT_ID'=>1,'EVT_name'=>'bob party',...),
146
-     *                                      array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
147
-     *                                      ...
148
-     *                                      )
149
-     *                                      'Venue'=>array(
150
-     *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
151
-     *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
152
-     *                                      ...
153
-     *                                      )
154
-     *                                      ...
155
-     *                                      )
156
-     * @throws EE_Error
157
-     */
158
-    public function import_csv_to_model_data_array(
159
-        string $path_to_file,
160
-        string $model_name = '',
161
-        bool $first_row_is_headers = true
162
-    ) {
163
-        $multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
164
-        if (empty($multi_dimensional_array)) {
165
-            return false;
166
-        }
167
-        // gotta start somewhere
168
-        $row = 1;
169
-        // array to store csv data in
170
-        $ee_formatted_data = [];
171
-        // array to store headers (column names)
172
-        $headers = [];
173
-        foreach ($multi_dimensional_array as $data) {
174
-            // if first cell is MODEL, then second cell is the MODEL name
175
-            if ($data[0] == 'MODEL') {
176
-                $model_name = $data[1];
177
-                // don't bother looking for model data in this row. The rest of this
178
-                // row should be blank
179
-                // AND pretend this is the first row again
180
-                $row = 1;
181
-                // reset headers
182
-                $headers = [];
183
-                continue;
184
-            }
185
-            if (strpos($data[0], EE_CSV::metadata_header) !== false) {
186
-                $model_name = EE_CSV::metadata_header;
187
-                // store like model data, we just won't try importing it etc.
188
-                $row = 1;
189
-                continue;
190
-            }
191
-
192
-
193
-            // how many columns are there?
194
-            $columns = count($data);
195
-
196
-            $model_entry = [];
197
-            // loop through each column
198
-            for ($i = 0; $i < $columns; $i++) {
199
-                // replace csv_enclosures with backslashed quotes
200
-                $data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
201
-                // do we need to grab the column names?
202
-                if ($row === 1) {
203
-                    if ($first_row_is_headers) {
204
-                        // store the column names to use for keys
205
-                        $column_name = $data[ $i ];
206
-                        // check it's not blank... sometimes CSV editing programs adda bunch of empty columns onto the end...
207
-                        if (! $column_name) {
208
-                            continue;
209
-                        }
210
-                        $matches = [];
211
-                        if ($model_name == EE_CSV::metadata_header) {
212
-                            $headers[ $i ] = $column_name;
213
-                        } else {
214
-                            // now get the db table name from it (the part between square brackets)
215
-                            $success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
216
-                            if (! $success) {
217
-                                EE_Error::add_error(
218
-                                    sprintf(
219
-                                        esc_html__(
220
-                                            "The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
221
-                                            "event_espresso"
222
-                                        ),
223
-                                        $column_name,
224
-                                        implode(",", $data)
225
-                                    ),
226
-                                    __FILE__,
227
-                                    __FUNCTION__,
228
-                                    __LINE__
229
-                                );
230
-                                return false;
231
-                            }
232
-                            $headers[ $i ] = $matches[2];
233
-                        }
234
-                    } else {
235
-                        // no column names means our final array will just use counters for keys
236
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
237
-                        $headers[ $i ]                 = $i;
238
-                    }
239
-                    // and we need to store csv data
240
-                } else {
241
-                    // this column isn' ta header, store it if there is a header for it
242
-                    if (isset($headers[ $i ])) {
243
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
244
-                    }
245
-                }
246
-            }
247
-            // save the row's data IF it's a non-header-row
248
-            if (! $first_row_is_headers || $row > 1) {
249
-                $ee_formatted_data[ $model_name ][] = $model_entry;
250
-            }
251
-            // advance to next row
252
-            $row++;
253
-        }
254
-
255
-        // delete the uploaded file
256
-        unlink($path_to_file);
257
-        // echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
258
-        // die();
259
-
260
-        // it's good to give back
261
-        return $ee_formatted_data;
262
-    }
263
-
264
-
265
-    /**
266
-     * @throws EE_Error
267
-     */
268
-    public function save_csv_to_db($csv_data_array, $model_name = false): bool
269
-    {
270
-        EE_Error::doing_it_wrong(
271
-            'save_csv_to_db',
272
-            esc_html__(
273
-                'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
274
-                'event_espresso'
275
-            ),
276
-            '4.6.7'
277
-        );
278
-        return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
279
-    }
280
-
281
-
282
-    /**
283
-     * Sends HTTP headers to indicate that the browser should download a file,
284
-     * and starts writing the file to PHP's output. Returns the file handle so other functions can
285
-     * also write to it
286
-     *
287
-     * @param string $filename the name of the file that the user will download
288
-     * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
289
-     */
290
-    public function begin_sending_csv(string $filename)
291
-    {
292
-        // grab file extension
293
-        $ext = substr(strrchr($filename, '.'), 1);
294
-        if ($ext == '.csv' or $ext == '.xls') {
295
-            str_replace($ext, '', $filename);
296
-        }
297
-        $filename .= '.csv';
298
-
299
-        // if somebody's been naughty and already started outputting stuff, trash it
300
-        // and start writing our stuff.
301
-        if (ob_get_length()) {
302
-            @ob_flush();
303
-            @flush();
304
-            @ob_end_flush();
305
-        }
306
-        @ob_start();
307
-        header("Pragma: public");
308
-        header("Expires: 0");
309
-        header("Pragma: no-cache");
310
-        header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
311
-        // header("Content-Type: application/force-download");
312
-        // header("Content-Type: application/octet-stream");
313
-        // header("Content-Type: application/download");
314
-        header('Content-disposition: attachment; filename=' . $filename);
315
-        header("Content-Type: text/csv; charset=utf-8");
316
-        do_action('AHEE__EE_CSV__begin_sending_csv__headers');
317
-        echo apply_filters(
318
-            'FHEE__EE_CSV__begin_sending_csv__start_writing',
319
-            "\xEF\xBB\xBF"
320
-        );
321
-        // makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
322
-        return fopen('php://output', 'w');
323
-    }
324
-
325
-
326
-    /**
327
-     * Writes some meta data to the CSV as a bunch of columns. Initially we're only
328
-     * mentioning the version and timezone
329
-     *
330
-     * @param resource $file_handle
331
-     * @throws EE_Error
332
-     * @throws EE_Error
333
-     */
334
-    public function write_metadata_to_csv($file_handle)
335
-    {
336
-        $data_row = [EE_CSV::metadata_header];// do NOT translate because this exact string is used when importing
337
-        $this->fputcsv2($file_handle, $data_row);
338
-        $meta_data = [
339
-            0 => [
340
-                'version'        => espresso_version(),
341
-                'timezone'       => EEH_DTT_Helper::get_timezone(),
342
-                'time_of_export' => current_time('mysql'),
343
-                'site_url'       => site_url(),
344
-            ],
345
-        ];
346
-        $this->write_data_array_to_csv($file_handle, $meta_data);
347
-    }
348
-
349
-
350
-    /**
351
-     * Writes $data to the csv file open in $file_handle. uses the array indices of $data for column headers
352
-     *
353
-     * @param resource   $file_handle
354
-     * @param array|null $data          2D array, first numerically-indexed,
355
-     *                                  and next-level-down preferably indexed by string
356
-     * @return boolean                  if we successfully wrote to the CSV or not.
357
-     *                                  If there's no $data, we consider that a success
358
-     *                                  (because we wrote everything there was...nothing)
359
-     * @throws EE_Error
360
-     */
361
-    public function write_data_array_to_csv($file_handle, ?array $data): bool
362
-    {
363
-        // determine if $data is actually a 2d array
364
-        if ($data && is_array(EEH_Array::get_one_item_from_array($data))) {
365
-            // make sure top level is numerically indexed,
366
-            if (EEH_Array::is_associative_array($data)) {
367
-                throw new EE_Error(
368
-                    sprintf(
369
-                        esc_html__(
370
-                            "top-level array must be numerically indexed. Does these look like numbers to you? %s",
371
-                            "event_espresso"
372
-                        ),
373
-                        implode(",", array_keys($data))
374
-                    )
375
-                );
376
-            }
377
-            $item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
378
-            // now, is the last item in the top-level array of $data an associative or numeric array?
379
-            if (EEH_Array::is_associative_array($item_in_top_level_array)) {
380
-                // its associative, so we want to output its keys as column headers
381
-                $keys = array_keys($item_in_top_level_array);
382
-                $this->fputcsv2($file_handle, $keys);
383
-            }
384
-            // start writing data
385
-            foreach ($data as $data_row) {
386
-                $this->fputcsv2($file_handle, $data_row);
387
-            }
388
-            return true;
389
-        }
390
-        // no data TO write... so we can assume that's a success
391
-        return true;
392
-    }
393
-
394
-
395
-    /**
396
-     * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
397
-     * Calls exit to prevent polluting the CSV file with other junk
398
-     *
399
-     * @param resource $fh file handle where we're writing the CSV to
400
-     */
401
-    public function end_sending_csv($fh)
402
-    {
403
-        fclose($fh);
404
-        exit(0);
405
-    }
406
-
407
-
408
-    /**
409
-     * Given an open file, writes all the model data to it in the format the importer expects.
410
-     * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($file_handle).
411
-     *
412
-     * @param resource $file_handle
413
-     * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
414
-     *                                   next layer is numerically indexed to represent each model object (eg, each
415
-     *                                   individual event), and the last layer has all the attributes of that model
416
-     *                                   object (eg, the event's id, name, etc)
417
-     * @return void
418
-     * @throws EE_Error
419
-     * @throws ReflectionException
420
-     */
421
-    public function write_model_data_to_csv($file_handle, array $model_data_array)
422
-    {
423
-        $this->write_metadata_to_csv($file_handle);
424
-        foreach ($model_data_array as $model_name => $model_instance_arrays) {
425
-            // first: output a special row stating the model
426
-            $this->fputcsv2($file_handle, ['MODEL', $model_name]);
427
-            // if we have items to put in the CSV, do it normally
428
-
429
-            if (! empty($model_instance_arrays)) {
430
-                $this->write_data_array_to_csv($file_handle, $model_instance_arrays);
431
-            } else {
432
-                // echo "no data to write... so just write the headers";
433
-                // so there's actually NO model objects for that model.
434
-                // probably still want to show the columns
435
-                $model        = EE_Registry::instance()->load_model($model_name);
436
-                $column_names = [];
437
-                foreach ($model->field_settings() as $field) {
438
-                    $column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
439
-                }
440
-                $this->write_data_array_to_csv($file_handle, [$column_names]);
441
-            }
442
-        }
443
-    }
444
-
445
-
446
-    /**
447
-     * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
448
-     * and dies (in order to avoid other plugins from messing up the csv output)
449
-     *
450
-     * @param string $filename         the filename you want to give the file
451
-     * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
452
-     * @return void
453
-     * @throws EE_Error
454
-     * @throws ReflectionException
455
-     */
456
-    public function export_multiple_model_data_to_csv(string $filename, array $model_data_array)
457
-    {
458
-        $file_handle = $this->begin_sending_csv($filename);
459
-        $this->write_model_data_to_csv($file_handle, $model_data_array);
460
-        $this->end_sending_csv($file_handle);
461
-    }
462
-
463
-
464
-    /**
465
-     * export contents of an array to csv file
466
-     *
467
-     * @param array|null $data     - the array of data to be converted to csv and exported
468
-     * @param string     $filename - name for newly created csv file
469
-     * @return void
470
-     */
471
-    public function export_array_to_csv(?array $data, string $filename = '')
472
-    {
473
-        // no data file or filename?? get outta here
474
-        if (empty($data) || ! $filename) {
475
-            return;
476
-        }
477
-        $fh = $this->begin_sending_csv($filename);
478
-        $this->end_sending_csv($fh);
479
-    }
480
-
481
-
482
-    /**
483
-     * @Determine the maximum upload file size based on php.ini settings
484
-     * @access    public
485
-     * @param int $percent_of_max - desired percentage of the max upload_mb
486
-     * @return int KB
487
-     */
488
-    public function get_max_upload_size(int $percent_of_max = 0)
489
-    {
490
-        $max_upload   = (int) (ini_get('upload_max_filesize'));
491
-        $max_post     = (int) (ini_get('post_max_size'));
492
-        $memory_limit = (int) (ini_get('memory_limit'));
493
-
494
-        // determine the smallest of the three values from above
495
-        $upload_mb = min($max_upload, $max_post, $memory_limit);
496
-
497
-        // convert MB to KB
498
-        $upload_mb = $upload_mb * 1024;
499
-
500
-        // don't want the full monty? then reduce the max upload size
501
-        if ($percent_of_max) {
502
-            // is percent_of_max like this -> 50 or like this -> 0.50 ?
503
-            if ($percent_of_max > 1) {
504
-                // changes 50 to 0.50
505
-                $percent_of_max = $percent_of_max / 100;
506
-            }
507
-            // make upload_mb a percentage of the max upload_mb
508
-            $upload_mb = $upload_mb * $percent_of_max;
509
-        }
510
-
511
-        return $upload_mb;
512
-    }
513
-
514
-
515
-    /**
516
-     * drop in replacement for PHP's fputcsv function - but this one works!!!
517
-     *
518
-     * @param resource $fh         - file handle - what we are writing to
519
-     * @param array    $row        - individual row of csv data
520
-     * @param string   $delimiter  - csv delimiter
521
-     * @param string   $enclosure  - csv enclosure
522
-     * @param bool     $mysql_null - allows php NULL to be overridden with MySQL's insertable NULL value
523
-     * @return void
524
-     */
525
-    private function fputcsv2(
526
-        $fh,
527
-        array $row,
528
-        string $delimiter = ',',
529
-        string $enclosure = '"',
530
-        bool $mysql_null = false
531
-    ) {
532
-        // Allow user to filter the csv delimiter and enclosure for other countries csv standards
533
-        $delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
534
-        $enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
535
-
536
-        $delimiter_esc = preg_quote($delimiter, '/');
537
-        $enclosure_esc = preg_quote($enclosure, '/');
538
-
539
-        $output = [];
540
-        foreach ($row as $field_value) {
541
-            if (is_object($field_value) || is_array($field_value)) {
542
-                $field_value = serialize($field_value);
543
-            }
544
-            if ($field_value === null && $mysql_null) {
545
-                $output[] = 'NULL';
546
-                continue;
547
-            }
548
-
549
-            $output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value)
550
-                ? ($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
551
-                : $field_value;
552
-        }
553
-
554
-        fwrite($fh, join($delimiter, $output) . PHP_EOL);
555
-    }
556
-
557
-
558
-    /**
559
-     * Gets the date format to use in teh csv. filterable
560
-     *
561
-     * @param string $current_format
562
-     * @return string
563
-     */
564
-    public function get_date_format_for_csv(string $current_format = ''): string
565
-    {
566
-        return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
567
-    }
568
-
569
-
570
-    /**
571
-     * Gets the time format we want to use in CSV reports. Filterable
572
-     *
573
-     * @param string $current_format
574
-     * @return string
575
-     */
576
-    public function get_time_format_for_csv(string $current_format = ''): string
577
-    {
578
-        return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
579
-    }
16
+	/**
17
+	 * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
18
+	 */
19
+	const metadata_header = 'Event Espresso Export Meta Data';
20
+
21
+	/**
22
+	 * @var EE_CSV
23
+	 */
24
+	private static $_instance = null;
25
+
26
+
27
+	/**
28
+	 * @return void
29
+	 */
30
+	private function __construct()
31
+	{
32
+	}
33
+
34
+
35
+	/**
36
+	 * singleton method used to instantiate class object
37
+	 *
38
+	 * @return EE_CSV
39
+	 */
40
+	public static function instance(): EE_CSV
41
+	{
42
+		// check if class object is instantiated
43
+		if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
44
+			self::$_instance = new self();
45
+		}
46
+		return self::$_instance;
47
+	}
48
+
49
+
50
+	/**
51
+	 * Opens a unicode or utf file (normal file_get_contents has difficulty reading a unicode file. @param string
52
+	 * $file_path
53
+	 *
54
+	 * @return string
55
+	 * @throws EE_Error
56
+	 * @see http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
57
+	 *
58
+	 */
59
+	private function read_unicode_file(string $file_path): string
60
+	{
61
+		$fc = "";
62
+		$fh = fopen($file_path, "rb");
63
+		if (! $fh) {
64
+			throw new EE_Error(
65
+				sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path)
66
+			);
67
+		}
68
+		$file_length = filesize($file_path);
69
+		$bc   = fread($fh, $file_length);
70
+		for ($i = 0; $i < $file_length; $i++) {
71
+			$c = substr($bc, $i, 1);
72
+			if ((ord($c) != 0) && (ord($c) != 13)) {
73
+				$fc = $fc . $c;
74
+			}
75
+		}
76
+		if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
77
+			$fc = substr($fc, 2);
78
+		}
79
+		return ($fc);
80
+	}
81
+
82
+
83
+	/**
84
+	 * Generic CSV-functionality to turn an entire CSV file into a single array that's
85
+	 * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
86
+	 * representing each row in the CSV file, and the second-level arrays being each column in that row
87
+	 *
88
+	 * @param string $path_to_file
89
+	 * @return array of arrays. Top-level array has rows, second-level array has each item
90
+	 * @throws EE_Error
91
+	 */
92
+	public function import_csv_to_multi_dimensional_array(string $path_to_file): array
93
+	{
94
+		// needed to deal with Mac line endings
95
+		ini_set('auto_detect_line_endings', true);
96
+		// because fgetcsv does not correctly deal with backslashed quotes such as \"
97
+		// we'll read the file into a string
98
+		$file_contents = $this->read_unicode_file($path_to_file);
99
+		// replace backslashed quotes with CSV enclosures
100
+		$file_contents = str_replace('\\"', '"""', $file_contents);
101
+		// HEY YOU! PUT THAT FILE BACK!!!
102
+		file_put_contents($path_to_file, $file_contents);
103
+
104
+		if (($file_handle = fopen($path_to_file, "r")) !== false) {
105
+			$csv_array = [];
106
+			// loop through each row of the file
107
+			while (($data = fgetcsv($file_handle, 0)) !== false) {
108
+				$csv_array[] = $data;
109
+			}
110
+			# Close the File.
111
+			fclose($file_handle);
112
+			return $csv_array;
113
+		}
114
+		EE_Error::add_error(
115
+			sprintf(
116
+				esc_html__('An error occurred - the file: %s could not opened.', 'event_espresso'),
117
+				$path_to_file
118
+			),
119
+			__FILE__,
120
+			__FUNCTION__,
121
+			__LINE__
122
+		);
123
+		return [];
124
+	}
125
+
126
+
127
+	/**
128
+	 * import contents of csv file and store values in an array to be manipulated by other functions
129
+	 *
130
+	 * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
131
+	 *                                      If $model_name is provided, assumes that each row in the CSV represents a
132
+	 *                                      model object for that model If $model_name ISN'T provided, assumes that
133
+	 *                                      before model object data, there is a row where the first entry is simply
134
+	 *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
135
+	 *                                      then maybe a row of headers, and then the model data. Eg.
136
+	 *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
137
+	 *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
138
+	 *                                      Forest
139
+	 * @param string  $model_name           model name if we know what model we're importing
140
+	 * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
141
+	 *                                      data
142
+	 * @return array|false                  - array on success - multi dimensional with headers as keys
143
+	 *                                      (if headers exist) OR string on fail -
144
+	 *                                      error message like the following array('Event'=>array(
145
+	 *                                      array('EVT_ID'=>1,'EVT_name'=>'bob party',...),
146
+	 *                                      array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
147
+	 *                                      ...
148
+	 *                                      )
149
+	 *                                      'Venue'=>array(
150
+	 *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
151
+	 *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
152
+	 *                                      ...
153
+	 *                                      )
154
+	 *                                      ...
155
+	 *                                      )
156
+	 * @throws EE_Error
157
+	 */
158
+	public function import_csv_to_model_data_array(
159
+		string $path_to_file,
160
+		string $model_name = '',
161
+		bool $first_row_is_headers = true
162
+	) {
163
+		$multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
164
+		if (empty($multi_dimensional_array)) {
165
+			return false;
166
+		}
167
+		// gotta start somewhere
168
+		$row = 1;
169
+		// array to store csv data in
170
+		$ee_formatted_data = [];
171
+		// array to store headers (column names)
172
+		$headers = [];
173
+		foreach ($multi_dimensional_array as $data) {
174
+			// if first cell is MODEL, then second cell is the MODEL name
175
+			if ($data[0] == 'MODEL') {
176
+				$model_name = $data[1];
177
+				// don't bother looking for model data in this row. The rest of this
178
+				// row should be blank
179
+				// AND pretend this is the first row again
180
+				$row = 1;
181
+				// reset headers
182
+				$headers = [];
183
+				continue;
184
+			}
185
+			if (strpos($data[0], EE_CSV::metadata_header) !== false) {
186
+				$model_name = EE_CSV::metadata_header;
187
+				// store like model data, we just won't try importing it etc.
188
+				$row = 1;
189
+				continue;
190
+			}
191
+
192
+
193
+			// how many columns are there?
194
+			$columns = count($data);
195
+
196
+			$model_entry = [];
197
+			// loop through each column
198
+			for ($i = 0; $i < $columns; $i++) {
199
+				// replace csv_enclosures with backslashed quotes
200
+				$data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
201
+				// do we need to grab the column names?
202
+				if ($row === 1) {
203
+					if ($first_row_is_headers) {
204
+						// store the column names to use for keys
205
+						$column_name = $data[ $i ];
206
+						// check it's not blank... sometimes CSV editing programs adda bunch of empty columns onto the end...
207
+						if (! $column_name) {
208
+							continue;
209
+						}
210
+						$matches = [];
211
+						if ($model_name == EE_CSV::metadata_header) {
212
+							$headers[ $i ] = $column_name;
213
+						} else {
214
+							// now get the db table name from it (the part between square brackets)
215
+							$success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
216
+							if (! $success) {
217
+								EE_Error::add_error(
218
+									sprintf(
219
+										esc_html__(
220
+											"The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
221
+											"event_espresso"
222
+										),
223
+										$column_name,
224
+										implode(",", $data)
225
+									),
226
+									__FILE__,
227
+									__FUNCTION__,
228
+									__LINE__
229
+								);
230
+								return false;
231
+							}
232
+							$headers[ $i ] = $matches[2];
233
+						}
234
+					} else {
235
+						// no column names means our final array will just use counters for keys
236
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
237
+						$headers[ $i ]                 = $i;
238
+					}
239
+					// and we need to store csv data
240
+				} else {
241
+					// this column isn' ta header, store it if there is a header for it
242
+					if (isset($headers[ $i ])) {
243
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
244
+					}
245
+				}
246
+			}
247
+			// save the row's data IF it's a non-header-row
248
+			if (! $first_row_is_headers || $row > 1) {
249
+				$ee_formatted_data[ $model_name ][] = $model_entry;
250
+			}
251
+			// advance to next row
252
+			$row++;
253
+		}
254
+
255
+		// delete the uploaded file
256
+		unlink($path_to_file);
257
+		// echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
258
+		// die();
259
+
260
+		// it's good to give back
261
+		return $ee_formatted_data;
262
+	}
263
+
264
+
265
+	/**
266
+	 * @throws EE_Error
267
+	 */
268
+	public function save_csv_to_db($csv_data_array, $model_name = false): bool
269
+	{
270
+		EE_Error::doing_it_wrong(
271
+			'save_csv_to_db',
272
+			esc_html__(
273
+				'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
274
+				'event_espresso'
275
+			),
276
+			'4.6.7'
277
+		);
278
+		return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
279
+	}
280
+
281
+
282
+	/**
283
+	 * Sends HTTP headers to indicate that the browser should download a file,
284
+	 * and starts writing the file to PHP's output. Returns the file handle so other functions can
285
+	 * also write to it
286
+	 *
287
+	 * @param string $filename the name of the file that the user will download
288
+	 * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
289
+	 */
290
+	public function begin_sending_csv(string $filename)
291
+	{
292
+		// grab file extension
293
+		$ext = substr(strrchr($filename, '.'), 1);
294
+		if ($ext == '.csv' or $ext == '.xls') {
295
+			str_replace($ext, '', $filename);
296
+		}
297
+		$filename .= '.csv';
298
+
299
+		// if somebody's been naughty and already started outputting stuff, trash it
300
+		// and start writing our stuff.
301
+		if (ob_get_length()) {
302
+			@ob_flush();
303
+			@flush();
304
+			@ob_end_flush();
305
+		}
306
+		@ob_start();
307
+		header("Pragma: public");
308
+		header("Expires: 0");
309
+		header("Pragma: no-cache");
310
+		header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
311
+		// header("Content-Type: application/force-download");
312
+		// header("Content-Type: application/octet-stream");
313
+		// header("Content-Type: application/download");
314
+		header('Content-disposition: attachment; filename=' . $filename);
315
+		header("Content-Type: text/csv; charset=utf-8");
316
+		do_action('AHEE__EE_CSV__begin_sending_csv__headers');
317
+		echo apply_filters(
318
+			'FHEE__EE_CSV__begin_sending_csv__start_writing',
319
+			"\xEF\xBB\xBF"
320
+		);
321
+		// makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
322
+		return fopen('php://output', 'w');
323
+	}
324
+
325
+
326
+	/**
327
+	 * Writes some meta data to the CSV as a bunch of columns. Initially we're only
328
+	 * mentioning the version and timezone
329
+	 *
330
+	 * @param resource $file_handle
331
+	 * @throws EE_Error
332
+	 * @throws EE_Error
333
+	 */
334
+	public function write_metadata_to_csv($file_handle)
335
+	{
336
+		$data_row = [EE_CSV::metadata_header];// do NOT translate because this exact string is used when importing
337
+		$this->fputcsv2($file_handle, $data_row);
338
+		$meta_data = [
339
+			0 => [
340
+				'version'        => espresso_version(),
341
+				'timezone'       => EEH_DTT_Helper::get_timezone(),
342
+				'time_of_export' => current_time('mysql'),
343
+				'site_url'       => site_url(),
344
+			],
345
+		];
346
+		$this->write_data_array_to_csv($file_handle, $meta_data);
347
+	}
348
+
349
+
350
+	/**
351
+	 * Writes $data to the csv file open in $file_handle. uses the array indices of $data for column headers
352
+	 *
353
+	 * @param resource   $file_handle
354
+	 * @param array|null $data          2D array, first numerically-indexed,
355
+	 *                                  and next-level-down preferably indexed by string
356
+	 * @return boolean                  if we successfully wrote to the CSV or not.
357
+	 *                                  If there's no $data, we consider that a success
358
+	 *                                  (because we wrote everything there was...nothing)
359
+	 * @throws EE_Error
360
+	 */
361
+	public function write_data_array_to_csv($file_handle, ?array $data): bool
362
+	{
363
+		// determine if $data is actually a 2d array
364
+		if ($data && is_array(EEH_Array::get_one_item_from_array($data))) {
365
+			// make sure top level is numerically indexed,
366
+			if (EEH_Array::is_associative_array($data)) {
367
+				throw new EE_Error(
368
+					sprintf(
369
+						esc_html__(
370
+							"top-level array must be numerically indexed. Does these look like numbers to you? %s",
371
+							"event_espresso"
372
+						),
373
+						implode(",", array_keys($data))
374
+					)
375
+				);
376
+			}
377
+			$item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
378
+			// now, is the last item in the top-level array of $data an associative or numeric array?
379
+			if (EEH_Array::is_associative_array($item_in_top_level_array)) {
380
+				// its associative, so we want to output its keys as column headers
381
+				$keys = array_keys($item_in_top_level_array);
382
+				$this->fputcsv2($file_handle, $keys);
383
+			}
384
+			// start writing data
385
+			foreach ($data as $data_row) {
386
+				$this->fputcsv2($file_handle, $data_row);
387
+			}
388
+			return true;
389
+		}
390
+		// no data TO write... so we can assume that's a success
391
+		return true;
392
+	}
393
+
394
+
395
+	/**
396
+	 * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
397
+	 * Calls exit to prevent polluting the CSV file with other junk
398
+	 *
399
+	 * @param resource $fh file handle where we're writing the CSV to
400
+	 */
401
+	public function end_sending_csv($fh)
402
+	{
403
+		fclose($fh);
404
+		exit(0);
405
+	}
406
+
407
+
408
+	/**
409
+	 * Given an open file, writes all the model data to it in the format the importer expects.
410
+	 * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($file_handle).
411
+	 *
412
+	 * @param resource $file_handle
413
+	 * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
414
+	 *                                   next layer is numerically indexed to represent each model object (eg, each
415
+	 *                                   individual event), and the last layer has all the attributes of that model
416
+	 *                                   object (eg, the event's id, name, etc)
417
+	 * @return void
418
+	 * @throws EE_Error
419
+	 * @throws ReflectionException
420
+	 */
421
+	public function write_model_data_to_csv($file_handle, array $model_data_array)
422
+	{
423
+		$this->write_metadata_to_csv($file_handle);
424
+		foreach ($model_data_array as $model_name => $model_instance_arrays) {
425
+			// first: output a special row stating the model
426
+			$this->fputcsv2($file_handle, ['MODEL', $model_name]);
427
+			// if we have items to put in the CSV, do it normally
428
+
429
+			if (! empty($model_instance_arrays)) {
430
+				$this->write_data_array_to_csv($file_handle, $model_instance_arrays);
431
+			} else {
432
+				// echo "no data to write... so just write the headers";
433
+				// so there's actually NO model objects for that model.
434
+				// probably still want to show the columns
435
+				$model        = EE_Registry::instance()->load_model($model_name);
436
+				$column_names = [];
437
+				foreach ($model->field_settings() as $field) {
438
+					$column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
439
+				}
440
+				$this->write_data_array_to_csv($file_handle, [$column_names]);
441
+			}
442
+		}
443
+	}
444
+
445
+
446
+	/**
447
+	 * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
448
+	 * and dies (in order to avoid other plugins from messing up the csv output)
449
+	 *
450
+	 * @param string $filename         the filename you want to give the file
451
+	 * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
452
+	 * @return void
453
+	 * @throws EE_Error
454
+	 * @throws ReflectionException
455
+	 */
456
+	public function export_multiple_model_data_to_csv(string $filename, array $model_data_array)
457
+	{
458
+		$file_handle = $this->begin_sending_csv($filename);
459
+		$this->write_model_data_to_csv($file_handle, $model_data_array);
460
+		$this->end_sending_csv($file_handle);
461
+	}
462
+
463
+
464
+	/**
465
+	 * export contents of an array to csv file
466
+	 *
467
+	 * @param array|null $data     - the array of data to be converted to csv and exported
468
+	 * @param string     $filename - name for newly created csv file
469
+	 * @return void
470
+	 */
471
+	public function export_array_to_csv(?array $data, string $filename = '')
472
+	{
473
+		// no data file or filename?? get outta here
474
+		if (empty($data) || ! $filename) {
475
+			return;
476
+		}
477
+		$fh = $this->begin_sending_csv($filename);
478
+		$this->end_sending_csv($fh);
479
+	}
480
+
481
+
482
+	/**
483
+	 * @Determine the maximum upload file size based on php.ini settings
484
+	 * @access    public
485
+	 * @param int $percent_of_max - desired percentage of the max upload_mb
486
+	 * @return int KB
487
+	 */
488
+	public function get_max_upload_size(int $percent_of_max = 0)
489
+	{
490
+		$max_upload   = (int) (ini_get('upload_max_filesize'));
491
+		$max_post     = (int) (ini_get('post_max_size'));
492
+		$memory_limit = (int) (ini_get('memory_limit'));
493
+
494
+		// determine the smallest of the three values from above
495
+		$upload_mb = min($max_upload, $max_post, $memory_limit);
496
+
497
+		// convert MB to KB
498
+		$upload_mb = $upload_mb * 1024;
499
+
500
+		// don't want the full monty? then reduce the max upload size
501
+		if ($percent_of_max) {
502
+			// is percent_of_max like this -> 50 or like this -> 0.50 ?
503
+			if ($percent_of_max > 1) {
504
+				// changes 50 to 0.50
505
+				$percent_of_max = $percent_of_max / 100;
506
+			}
507
+			// make upload_mb a percentage of the max upload_mb
508
+			$upload_mb = $upload_mb * $percent_of_max;
509
+		}
510
+
511
+		return $upload_mb;
512
+	}
513
+
514
+
515
+	/**
516
+	 * drop in replacement for PHP's fputcsv function - but this one works!!!
517
+	 *
518
+	 * @param resource $fh         - file handle - what we are writing to
519
+	 * @param array    $row        - individual row of csv data
520
+	 * @param string   $delimiter  - csv delimiter
521
+	 * @param string   $enclosure  - csv enclosure
522
+	 * @param bool     $mysql_null - allows php NULL to be overridden with MySQL's insertable NULL value
523
+	 * @return void
524
+	 */
525
+	private function fputcsv2(
526
+		$fh,
527
+		array $row,
528
+		string $delimiter = ',',
529
+		string $enclosure = '"',
530
+		bool $mysql_null = false
531
+	) {
532
+		// Allow user to filter the csv delimiter and enclosure for other countries csv standards
533
+		$delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
534
+		$enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
535
+
536
+		$delimiter_esc = preg_quote($delimiter, '/');
537
+		$enclosure_esc = preg_quote($enclosure, '/');
538
+
539
+		$output = [];
540
+		foreach ($row as $field_value) {
541
+			if (is_object($field_value) || is_array($field_value)) {
542
+				$field_value = serialize($field_value);
543
+			}
544
+			if ($field_value === null && $mysql_null) {
545
+				$output[] = 'NULL';
546
+				continue;
547
+			}
548
+
549
+			$output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value)
550
+				? ($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
551
+				: $field_value;
552
+		}
553
+
554
+		fwrite($fh, join($delimiter, $output) . PHP_EOL);
555
+	}
556
+
557
+
558
+	/**
559
+	 * Gets the date format to use in teh csv. filterable
560
+	 *
561
+	 * @param string $current_format
562
+	 * @return string
563
+	 */
564
+	public function get_date_format_for_csv(string $current_format = ''): string
565
+	{
566
+		return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
567
+	}
568
+
569
+
570
+	/**
571
+	 * Gets the time format we want to use in CSV reports. Filterable
572
+	 *
573
+	 * @param string $current_format
574
+	 * @return string
575
+	 */
576
+	public function get_time_format_for_csv(string $current_format = ''): string
577
+	{
578
+		return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
579
+	}
580 580
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Import.class.php 1 patch
Indentation   +985 added lines, -985 removed lines patch added patch discarded remove patch
@@ -13,94 +13,94 @@  discard block
 block discarded – undo
13 13
  */
14 14
 class EE_Import implements ResettableInterface
15 15
 {
16
-    const do_insert  = 'insert';
17
-
18
-    const do_update  = 'update';
19
-
20
-    const do_nothing = 'nothing';
21
-
22
-    /**
23
-     * @var EE_Import
24
-     */
25
-    private static $_instance;
26
-
27
-    /**
28
-     * @var int
29
-     */
30
-    protected $_total_inserts = 0;
31
-
32
-    /**
33
-     * @var int
34
-     */
35
-    protected $_total_updates = 0;
36
-
37
-    /**
38
-     * @var int
39
-     */
40
-    protected $_total_insert_errors = 0;
41
-
42
-    /**
43
-     * @var int
44
-     */
45
-    protected $_total_update_errors = 0;
46
-
47
-
48
-    /**
49
-     * @return void
50
-     */
51
-    private function __construct()
52
-    {
53
-        $this->_total_inserts       = 0;
54
-        $this->_total_updates       = 0;
55
-        $this->_total_insert_errors = 0;
56
-        $this->_total_update_errors = 0;
57
-    }
58
-
59
-
60
-    /**
61
-     *    @ singleton method used to instantiate class object
62
-     *    @ access public
63
-     *
64
-     * @return EE_Import
65
-     */
66
-    public static function instance(): EE_Import
67
-    {
68
-        // check if class object is instantiated
69
-        if (! self::$_instance instanceof EE_Import) {
70
-            self::$_instance = new self();
71
-        }
72
-        return self::$_instance;
73
-    }
74
-
75
-
76
-    /**
77
-     * Resets the importer
78
-     *
79
-     * @return EE_Import
80
-     */
81
-    public static function reset(): EE_Import
82
-    {
83
-        self::$_instance = null;
84
-        return self::instance();
85
-    }
86
-
87
-
88
-    /**
89
-     * generates HTML for a file upload input and form
90
-     *
91
-     * @param string $title    - heading for the form
92
-     * @param string $intro    - additional text explaining what to do
93
-     * @param string $form_url - EE Admin page to direct form to - in the form "espresso_{pageslug}"
94
-     * @param string $action   - EE Admin page route array "action" that form will direct to
95
-     * @param string $type     - type of file to import
96
-     * @return string
97
-     */
98
-    public function upload_form(string $title, string $intro, string $form_url, string $action, string $type): string
99
-    {
100
-        $form_url = EE_Admin_Page::add_query_args_and_nonce(['action' => $action], $form_url);
101
-
102
-        ob_start();
103
-        ?>
16
+	const do_insert  = 'insert';
17
+
18
+	const do_update  = 'update';
19
+
20
+	const do_nothing = 'nothing';
21
+
22
+	/**
23
+	 * @var EE_Import
24
+	 */
25
+	private static $_instance;
26
+
27
+	/**
28
+	 * @var int
29
+	 */
30
+	protected $_total_inserts = 0;
31
+
32
+	/**
33
+	 * @var int
34
+	 */
35
+	protected $_total_updates = 0;
36
+
37
+	/**
38
+	 * @var int
39
+	 */
40
+	protected $_total_insert_errors = 0;
41
+
42
+	/**
43
+	 * @var int
44
+	 */
45
+	protected $_total_update_errors = 0;
46
+
47
+
48
+	/**
49
+	 * @return void
50
+	 */
51
+	private function __construct()
52
+	{
53
+		$this->_total_inserts       = 0;
54
+		$this->_total_updates       = 0;
55
+		$this->_total_insert_errors = 0;
56
+		$this->_total_update_errors = 0;
57
+	}
58
+
59
+
60
+	/**
61
+	 *    @ singleton method used to instantiate class object
62
+	 *    @ access public
63
+	 *
64
+	 * @return EE_Import
65
+	 */
66
+	public static function instance(): EE_Import
67
+	{
68
+		// check if class object is instantiated
69
+		if (! self::$_instance instanceof EE_Import) {
70
+			self::$_instance = new self();
71
+		}
72
+		return self::$_instance;
73
+	}
74
+
75
+
76
+	/**
77
+	 * Resets the importer
78
+	 *
79
+	 * @return EE_Import
80
+	 */
81
+	public static function reset(): EE_Import
82
+	{
83
+		self::$_instance = null;
84
+		return self::instance();
85
+	}
86
+
87
+
88
+	/**
89
+	 * generates HTML for a file upload input and form
90
+	 *
91
+	 * @param string $title    - heading for the form
92
+	 * @param string $intro    - additional text explaining what to do
93
+	 * @param string $form_url - EE Admin page to direct form to - in the form "espresso_{pageslug}"
94
+	 * @param string $action   - EE Admin page route array "action" that form will direct to
95
+	 * @param string $type     - type of file to import
96
+	 * @return string
97
+	 */
98
+	public function upload_form(string $title, string $intro, string $form_url, string $action, string $type): string
99
+	{
100
+		$form_url = EE_Admin_Page::add_query_args_and_nonce(['action' => $action], $form_url);
101
+
102
+		ob_start();
103
+		?>
104 104
         <div class="ee-upload-form-dv">
105 105
             <h3><?php echo esc_html($title); ?></h3>
106 106
             <p><?php echo esc_html($intro); ?></p>
@@ -119,906 +119,906 @@  discard block
 block discarded – undo
119 119
                 <b><?php esc_html_e('Attention', 'event_espresso'); ?></b><br/>
120 120
                 <?php echo sprintf(esc_html__('Accepts .%s file types only.', 'event_espresso'), $type); ?>
121 121
                 <?php echo esc_html__(
122
-                    'Please only import CSV files exported from Event Espresso, or compatible 3rd-party software.',
123
-                    'event_espresso'
124
-                ); ?>
122
+					'Please only import CSV files exported from Event Espresso, or compatible 3rd-party software.',
123
+					'event_espresso'
124
+				); ?>
125 125
             </p>
126 126
 
127 127
         </div>
128 128
 
129 129
         <?php
130
-        return ob_get_clean();
131
-    }
132
-
133
-
134
-    /**
135
-     * @Import Event Espresso data - some code "borrowed" from event espresso csv_import.php
136
-     * @access public
137
-     * @return boolean success
138
-     * @throws EE_Error
139
-     * @throws ReflectionException
140
-     */
141
-    public function import(): bool
142
-    {
143
-        require_once(EE_CLASSES . 'EE_CSV.class.php');
144
-        $EE_CSV = EE_CSV::instance();
145
-        /** @var RequestInterface $request */
146
-        $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
147
-        $files   = $this->getFileParams($request);
148
-
149
-        $filename  = $files['file']['name'][0];
150
-        $file_ext  = substr(strrchr($filename, '.'), 1);
151
-        $temp_file = $files['file']['tmp_name'][0];
152
-        $filesize  = $files['file']['size'][0] / 1024;// convert from bytes to KB
153
-
154
-        if ($file_ext !== 'csv') {
155
-            EE_Error::add_error(
156
-                sprintf(
157
-                    esc_html__('%s  had an invalid file extension, not uploaded', 'event_espresso'),
158
-                    $filename
159
-                ),
160
-                __FILE__,
161
-                __FUNCTION__,
162
-                __LINE__
163
-            );
164
-            return false;
165
-        }
166
-
167
-        $ignore_max_file_size = (bool) apply_filters('FHEE__EE_Import__import__ignore_max_file_size', true, $files);
168
-        if (! $ignore_max_file_size) {
169
-            // max upload size in KB
170
-            $max_upload = $EE_CSV->get_max_upload_size();
171
-            if ($filesize > $max_upload) {
172
-                EE_Error::add_error(
173
-                    sprintf(
174
-                        esc_html__(
175
-                            "%s was too large of a file and could not be uploaded. The max filesize is %s' KB.",
176
-                            'event_espresso'
177
-                        ),
178
-                        $filename,
179
-                        $max_upload
180
-                    ),
181
-                    __FILE__,
182
-                    __FUNCTION__,
183
-                    __LINE__
184
-                );
185
-                return false;
186
-            }
187
-        }
188
-
189
-        $wp_upload_dir = str_replace(['\\', '/'], '/', wp_upload_dir());
190
-        $path_to_file  = $wp_upload_dir['basedir'] . '/espresso/' . $filename;
191
-
192
-        if (! move_uploaded_file($temp_file, $path_to_file)) {
193
-            EE_Error::add_error(
194
-                sprintf(
195
-                    esc_html__('%s was not successfully uploaded', 'event_espresso'),
196
-                    $filename
197
-                ),
198
-                __FILE__,
199
-                __FUNCTION__,
200
-                __LINE__
201
-            );
202
-            return false;
203
-        }
204
-
205
-        // convert csv to array
206
-        $csv_array = $EE_CSV->import_csv_to_model_data_array($path_to_file);
207
-
208
-        // was data successfully stored in an array?
209
-        if (! is_array($csv_array)) {
210
-            // no array? must be an error
211
-            EE_Error::add_error(
212
-                esc_html__('No file seems to have been uploaded', 'event_espresso'),
213
-                __FILE__,
214
-                __FUNCTION__,
215
-                __LINE__
216
-            );
217
-            return false;
218
-        }
219
-
220
-        // save processed codes to db
221
-        if ($this->save_csv_data_array_to_db($csv_array)) {
222
-            return true;
223
-        }
224
-        return false;
225
-    }
226
-
227
-
228
-    /**
229
-     * @param RequestInterface $request
230
-     * @return array|null
231
-     * @since $VID:$
232
-     */
233
-    private function getFileParams(RequestInterface $request): ?array
234
-    {
235
-        $error = null;
236
-        if (! ($request->requestParamIsSet('import') && $request->requestParamIsSet('csv_submitted'))) {
237
-            $error = esc_html__(
238
-                "Invalid request. Expected request params 'import' or 'csv_submitted' were missing.",
239
-                'event_espresso'
240
-            );
241
-        }
242
-        $files = $request->filesParams();
243
-
244
-        if (! $error) {
245
-            switch ($files['file']['error'][0]) {
246
-                case UPLOAD_ERR_OK:
247
-                    break;
248
-                case UPLOAD_ERR_INI_SIZE:
249
-                    $error = esc_html__(
250
-                        "'The uploaded file exceeds the upload_max_filesize directive in php.ini.'",
251
-                        'event_espresso'
252
-                    );
253
-                    break;
254
-                case UPLOAD_ERR_FORM_SIZE:
255
-                    $error = esc_html__(
256
-                        'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
257
-                        'event_espresso'
258
-                    );
259
-                    break;
260
-                case UPLOAD_ERR_PARTIAL:
261
-                    $error = esc_html__('The uploaded file was only partially uploaded.', 'event_espresso');
262
-                    break;
263
-                case UPLOAD_ERR_NO_FILE:
264
-                    $error = esc_html__('No file was uploaded.', 'event_espresso');
265
-                    break;
266
-                case UPLOAD_ERR_NO_TMP_DIR:
267
-                    $error = esc_html__('Missing a temporary folder.', 'event_espresso');
268
-                    break;
269
-                case UPLOAD_ERR_CANT_WRITE:
270
-                    $error = esc_html__('Failed to write file to disk.', 'event_espresso');
271
-                    break;
272
-                case UPLOAD_ERR_EXTENSION:
273
-                    $error = esc_html__('File upload stopped by extension.', 'event_espresso');
274
-                    break;
275
-                default:
276
-                    $error = esc_html__(
277
-                        'An unknown error occurred and the file could not be uploaded',
278
-                        'event_espresso'
279
-                    );
280
-            }
281
-        }
282
-
283
-        if ($error) {
284
-            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
285
-            return null;
286
-        }
287
-
288
-        return $files;
289
-    }
290
-
291
-
292
-    /**
293
-     *    Given an array of data (usually from a CSV import) attempts to save that data to the db.
294
-     *    If $model_name ISN'T provided, assumes that this is a 3d array, with toplevel keys being model names,
295
-     *    next level being numeric indexes adn each value representing a model object, and the last layer down
296
-     *    being keys of model fields and their proposed values.
297
-     *    If $model_name IS provided, assumes a 2d array of the bottom two layers previously mentioned.
298
-     *    If the CSV data says (in the metadata row) that it's from the SAME database,
299
-     *    we treat the IDs in the CSV as the normal IDs, and try to update those records. However, if those
300
-     *    IDs DON'T exist in the database, they're treated as temporary IDs,
301
-     *    which can used elsewhere to refer to the same object. Once an item
302
-     *    with a temporary ID gets inserted, we record its mapping from temporary
303
-     *    ID to real ID, and use the real ID in place of the temporary ID
304
-     *    when that temporary ID was used as a foreign key.
305
-     *    If the CSV data says (in the metadata again) that it's from a DIFFERENT database,
306
-     *    we treat all the IDs in the CSV as temporary ID- eg, if the CSV specifies an event with
307
-     *    ID 1, and the database already has an event with ID 1, we assume that's just a coincidence,
308
-     *    and insert a new event, and map it's temporary ID of 1 over to its new real ID.
309
-     *    An important exception are non-auto-increment primary keys. If one entry in the
310
-     *    CSV file has the same ID as one in the DB, we assume they are meant to be
311
-     *    the same item, and instead update the item in the DB with that same ID.
312
-     *    Also note, we remember the mappings permanently. So the 2nd, 3rd, and 10000th
313
-     *    time you import a CSV from a different site, we remember their mappings, and
314
-     * will try to update the item in the DB instead of inserting another item (eg
315
-     * if we previously imported an event with temporary ID 1, and then it got a
316
-     * real ID of 123, we remember that. So the next time we import an event with
317
-     * temporary ID, from the same site, we know that it's real ID is 123, and will
318
-     * update that event, instead of adding a new event).
319
-     *
320
-     * @param array  $csv_data_array    the array containing the csv data produced from
321
-     *                                  EE_CSV::import_csv_to_model_data_array()
322
-     * @param string $model_name
323
-     * @return bool                     TRUE on success, FALSE on fail
324
-     * @throws EE_Error
325
-     * @throws ReflectionException
326
-     */
327
-    public function save_csv_data_array_to_db(array $csv_data_array, string $model_name = ''): bool
328
-    {
329
-        $success = false;
330
-        // whether to treat this import as if it's data from a different database or not
331
-        // ie, if it IS from a different database, ignore foreign keys with
332
-        $export_from_site_a_to_b = true;
333
-        // first level of array is not table information but a table name was passed to the function
334
-        // array is only two levels deep, so let's fix that by adding a level, else the next steps will fail
335
-        if ($model_name) {
336
-            $csv_data_array = [$csv_data_array];
337
-        }
338
-        // begin looking through the $csv_data_array, expecting the toplevel key to be the model's name...
339
-        $old_site_url = 'none-specified';
340
-        // handle metadata
341
-        if (isset($csv_data_array[ EE_CSV::metadata_header ])) {
342
-            $csv_metadata = array_shift($csv_data_array[ EE_CSV::metadata_header ]);
343
-            // ok so its metadata, dont try to save it to the db obviously...
344
-            if (isset($csv_metadata['site_url']) && $csv_metadata['site_url'] == site_url()) {
345
-                EE_Error::add_attention(
346
-                    esc_html__(
347
-                        'CSV Data appears to be from the same database, so attempting to update data',
348
-                        'event_espresso'
349
-                    )
350
-                );
351
-                $export_from_site_a_to_b = false;
352
-            } else {
353
-                $old_site_url = $csv_metadata['site_url'] ?? $old_site_url;
354
-                EE_Error::add_attention(
355
-                    sprintf(
356
-                        esc_html__(
357
-                            "CSV Data appears to be from a different database (%s instead of %s), so we assume IDs in the CSV data DO NOT correspond to IDs in this database",
358
-                            "event_espresso"
359
-                        ),
360
-                        $old_site_url,
361
-                        site_url()
362
-                    )
363
-                );
364
-            }
365
-            unset($csv_data_array[ EE_CSV::metadata_header ]);
366
-        }
367
-
368
-        $id_mapping_option_name = 'ee_id_mapping_from' . sanitize_title($old_site_url);
369
-        /**
370
-         * @var $old_db_to_new_db_mapping   2d array: toplevel keys being model names,
371
-         *                                            bottom-level keys being the original key,
372
-         *                                            and the value will be the newly-inserted ID.
373
-         *                                  If we have already imported data from the same website via CSV,
374
-         *                                  it should be kept in this wp option
375
-         */
376
-        $old_db_to_new_db_mapping = (array) get_option($id_mapping_option_name, []);
377
-
378
-        if ($old_db_to_new_db_mapping) {
379
-            EE_Error::add_attention(
380
-                sprintf(
381
-                    esc_html__(
382
-                        "We noticed you have imported data via CSV from %s before. Because of this, IDs in your CSV have been mapped to their new IDs in %s",
383
-                        "event_espresso"
384
-                    ),
385
-                    $old_site_url,
386
-                    site_url()
387
-                )
388
-            );
389
-        }
390
-        $old_db_to_new_db_mapping = $this->save_data_rows_to_db(
391
-            $csv_data_array,
392
-            $export_from_site_a_to_b,
393
-            $old_db_to_new_db_mapping
394
-        );
395
-
396
-        // save the mapping from old db to new db in case they try re-importing the same data from the same website again
397
-        update_option($id_mapping_option_name, $old_db_to_new_db_mapping);
398
-
399
-        if ($this->_total_updates > 0) {
400
-            EE_Error::add_success(
401
-                sprintf(
402
-                    esc_html__("%s existing records in the database were updated.", "event_espresso"),
403
-                    $this->_total_updates
404
-                )
405
-            );
406
-            $success = true;
407
-        }
408
-        if ($this->_total_inserts > 0) {
409
-            EE_Error::add_success(
410
-                sprintf(
411
-                    esc_html__("%s new records were added to the database.", "event_espresso"),
412
-                    $this->_total_inserts
413
-                )
414
-            );
415
-            $success = true;
416
-        }
417
-
418
-        if ($this->_total_update_errors > 0) {
419
-            EE_Error::add_error(
420
-                sprintf(
421
-                    esc_html__(
422
-                        "'One or more errors occurred, and a total of %s existing records in the database were <strong>not</strong> updated.'",
423
-                        "event_espresso"
424
-                    ),
425
-                    $this->_total_update_errors
426
-                ),
427
-                __FILE__,
428
-                __FUNCTION__,
429
-                __LINE__
430
-            );
431
-            $success = false;
432
-        }
433
-        if ($this->_total_insert_errors > 0) {
434
-            EE_Error::add_error(
435
-                sprintf(
436
-                    esc_html__(
437
-                        "One or more errors occurred, and a total of %s new records were <strong>not</strong> added to the database.'",
438
-                        "event_espresso"
439
-                    ),
440
-                    $this->_total_insert_errors
441
-                ),
442
-                __FILE__,
443
-                __FUNCTION__,
444
-                __LINE__
445
-            );
446
-            $success = false;
447
-        }
448
-
449
-        // lastly, we need to update the datetime and ticket sold amounts
450
-        // as those may have been affected by this
451
-        EEM_Ticket::instance()->update_tickets_sold(EEM_Ticket::instance()->get_all());
452
-
453
-        // if there was at least one success and absolutely no errors
454
-        return $success;
455
-    }
456
-
457
-
458
-    /**
459
-     * Processes the array of data, given the knowledge that it's from the same database or a different one,
460
-     * and the mapping from temporary IDs to real IDs.
461
-     * If the data is from a different database, we treat the primary keys and their corresponding
462
-     * foreign keys as "temp Ids", basically identifiers that get mapped to real primary keys
463
-     * in the real target database. As items are inserted, their temporary primary keys
464
-     * are mapped to the real IDs in the target database. Also, before doing any update or
465
-     * insert, we replace all the temp ID which are foreign keys with their mapped real IDs.
466
-     * An exception: string primary keys are treated as real IDs, or else we'd need to
467
-     * dynamically generate new string primary keys which would be very awkward for the country table etc.
468
-     * Also, models with no primary key are strange too. We combine use their primary key INDEX (a
469
-     * combination of fields) to create a unique string identifying the row and store
470
-     * those in the mapping.
471
-     *
472
-     * If the data is from the same database, we usually treat primary keys as real IDs.
473
-     * An exception is if there is nothing in the database for that ID. If that's the case,
474
-     * we need to insert a new row for that ID, and then map from the non-existent ID
475
-     * to the newly-inserted real ID.
476
-     *
477
-     * @param array $csv_data_array
478
-     * @param bool  $export_from_site_a_to_b
479
-     * @param array $old_db_to_new_db_mapping
480
-     * @return array|bool updated $old_db_to_new_db_mapping
481
-     * @throws EE_Error
482
-     * @throws ReflectionException
483
-     */
484
-    public function save_data_rows_to_db(
485
-        array $csv_data_array,
486
-        bool $export_from_site_a_to_b,
487
-        array $old_db_to_new_db_mapping
488
-    ) {
489
-        foreach ($csv_data_array as $model_name => $model_data_from_import) {
490
-            // check that assumption was correct. If
491
-            if (! EE_Registry::instance()->is_model_name($model_name)) {
492
-                // no table info in the array and no table name passed to the function?? FAIL
493
-                EE_Error::add_error(
494
-                    esc_html__(
495
-                        'No table information was specified and/or found, therefore the import could not be completed',
496
-                        'event_espresso'
497
-                    ),
498
-                    __FILE__,
499
-                    __FUNCTION__,
500
-                    __LINE__
501
-                );
502
-                return false;
503
-            }
504
-
505
-            /* @var $model EEM_Base */
506
-            $model = EE_Registry::instance()->load_model($model_name);
507
-
508
-            // so without further ado, scanning all the data provided for primary keys and their initial values
509
-            foreach ($model_data_from_import as $model_object_data) {
510
-                // before we do ANYTHING, make sure the csv row wasn't just completely blank
511
-                $row_is_completely_empty = true;
512
-                foreach ($model_object_data as $field) {
513
-                    if ($field) {
514
-                        $row_is_completely_empty = false;
515
-                    }
516
-                }
517
-                if ($row_is_completely_empty) {
518
-                    continue;
519
-                }
520
-                // find the PK in the row of data (or a combined key if
521
-                // there is no primary key)
522
-                if ($model->has_primary_key_field()) {
523
-                    $id_in_csv = $model_object_data[ $model->primary_key_name() ];
524
-                } else {
525
-                    $id_in_csv = $model->get_index_primary_key_string($model_object_data);
526
-                }
527
-
528
-                $model_object_data = $this->_replace_temp_ids_with_mappings(
529
-                    $model_object_data,
530
-                    $model,
531
-                    $old_db_to_new_db_mapping,
532
-                    $export_from_site_a_to_b
533
-                );
534
-                // now we need to decide if we're going to
535
-                $what_to_do = $export_from_site_a_to_b
536
-                    // add a new model object given the $model_object_data
537
-                    ? $this->_decide_whether_to_insert_or_update_given_data_from_other_db(
538
-                        $id_in_csv,
539
-                        $model_name,
540
-                        $old_db_to_new_db_mapping
541
-                    )
542
-                    //  or just update cuz this is just a re-import
543
-                    : $this->_decide_whether_to_insert_or_update_given_data_from_same_db(
544
-                        $model_object_data,
545
-                        $model
546
-                    );
547
-
548
-                if ($what_to_do === EE_Import::do_nothing) {
549
-                    continue;
550
-                }
551
-
552
-                // double-check we actually want to insert, if that's what we're planning
553
-                // based on whether this item would be unique in the DB or not
554
-                if ($what_to_do == EE_Import::do_insert) {
555
-                    // we're supposed to be inserting. But wait, will this thing
556
-                    // be acceptable if inserted?
557
-                    $conflicting = $model->get_one_conflicting($model_object_data, false);
558
-                    if ($conflicting) {
559
-                        // ok, this item would conflict if inserted. Just update the item that it conflicts with.
560
-                        $what_to_do = EE_Import::do_update;
561
-                        // and if this model has a primary key, remember its mapping
562
-                        if ($model->has_primary_key_field()) {
563
-                            $old_db_to_new_db_mapping[ $model_name ][ $id_in_csv ] = $conflicting->ID();
564
-                            $model_object_data[ $model->primary_key_name() ]       = $conflicting->ID();
565
-                        } else {
566
-                            // we want to update this conflicting item, instead of inserting a conflicting item
567
-                            // so we need to make sure they match entirely
568
-                            // (it's possible that they only conflicted on one field,
569
-                            // but we need them to match on other fields for the WHERE conditions in the update).
570
-                            // At the time of this comment, there were no models like this
571
-                            foreach ($model->get_combined_primary_key_fields() as $key_field) {
572
-                                $model_object_data[ $key_field->get_name() ] = $conflicting->get(
573
-                                    $key_field->get_name()
574
-                                );
575
-                            }
576
-                        }
577
-                    }
578
-                }
579
-                if ($what_to_do == EE_Import::do_insert) {
580
-                    $old_db_to_new_db_mapping = $this->_insert_from_data_array(
581
-                        $id_in_csv,
582
-                        $model_object_data,
583
-                        $model,
584
-                        $old_db_to_new_db_mapping
585
-                    );
586
-                } elseif ($what_to_do == EE_Import::do_update) {
587
-                    $old_db_to_new_db_mapping = $this->_update_from_data_array(
588
-                        $id_in_csv,
589
-                        $model_object_data,
590
-                        $model,
591
-                        $old_db_to_new_db_mapping
592
-                    );
593
-                } else {
594
-                    throw new EE_Error(
595
-                        sprintf(
596
-                            esc_html__(
597
-                                'Programming error. We should be inserting or updating, but instead we are being told to "%s", whifh is invalid',
598
-                                'event_espresso'
599
-                            ),
600
-                            $what_to_do
601
-                        )
602
-                    );
603
-                }
604
-            }
605
-        }
606
-        return $old_db_to_new_db_mapping;
607
-    }
608
-
609
-
610
-    /**
611
-     * Decides whether or not to insert, given that this data is from another database.
612
-     * So, if the primary key of this $model_object_data already exists in the database,
613
-     * it's just a coincidence and we should still insert. The only time we should
614
-     * update is when we know what it maps to, or there's something that would
615
-     * conflict (and we should instead just update that conflicting thing)
616
-     *
617
-     * @param string $id_in_csv
618
-     * @param string $model_name
619
-     * @param array  $old_db_to_new_db_mapping by reference so it can be modified
620
-     * @return string one of the constants on this class that starts with do_*
621
-     */
622
-    protected function _decide_whether_to_insert_or_update_given_data_from_other_db(
623
-        string $id_in_csv,
624
-        string $model_name,
625
-        array $old_db_to_new_db_mapping
626
-    ): string {
627
-        // if it's a site-to-site export-and-import, see if this model object's id
628
-        // in the old data that we know of
629
-        if (isset($old_db_to_new_db_mapping[ $model_name ][ $id_in_csv ])) {
630
-            return EE_Import::do_update;
631
-        }
632
-        return EE_Import::do_insert;
633
-    }
634
-
635
-
636
-    /**
637
-     * If this thing basically already exists in the database, we want to update it;
638
-     * otherwise insert it (ie, someone tweaked the CSV file, or the item was
639
-     * deleted in the database so it should be re-inserted)
640
-     *
641
-     * @param array    $model_object_data
642
-     * @param EEM_Base $model
643
-     * @return string
644
-     * @throws EE_Error
645
-     */
646
-    protected function _decide_whether_to_insert_or_update_given_data_from_same_db(
647
-        array $model_object_data,
648
-        EEM_Base $model
649
-    ): string {
650
-        // in this case, check if this thing ACTUALLY exists in the database
651
-        if ($model->get_one_conflicting($model_object_data)) {
652
-            return EE_Import::do_update;
653
-        }
654
-        return EE_Import::do_insert;
655
-    }
656
-
657
-
658
-    /**
659
-     * Using the $old_db_to_new_db_mapping array, replaces all the temporary IDs
660
-     * with their mapped real IDs. Eg, if importing from site A to B, the mapping
661
-     * file may indicate that the ID "my_event_id" maps to an actual event ID of 123.
662
-     * So this function searches for any event temp Ids called "my_event_id" and
663
-     * replaces them with 123.
664
-     * Also, if there is no temp ID for the INT foreign keys from another database,
665
-     * replaces them with 0 or the field's default.
666
-     *
667
-     * @param array    $model_object_data
668
-     * @param EEM_Base $model
669
-     * @param array    $old_db_to_new_db_mapping
670
-     * @param bool     $export_from_site_a_to_b
671
-     * @return array updated model object data with temp IDs removed
672
-     * @throws EE_Error
673
-     */
674
-    protected function _replace_temp_ids_with_mappings(
675
-        array $model_object_data,
676
-        EEM_Base $model,
677
-        array $old_db_to_new_db_mapping,
678
-        bool $export_from_site_a_to_b
679
-    ): array {
680
-        // if this model object's primary key is in the mapping, replace it
681
-        if (
682
-            $model->has_primary_key_field()
683
-            && $model->get_primary_key_field()->is_auto_increment()
684
-            && isset($old_db_to_new_db_mapping[ $model->get_this_model_name() ])
685
-            && isset(
686
-                $old_db_to_new_db_mapping[ $model->get_this_model_name(
687
-                ) ][ $model_object_data[ $model->primary_key_name() ] ]
688
-            )
689
-        ) {
690
-            $model_object_data[ $model->primary_key_name() ] = $old_db_to_new_db_mapping[ $model->get_this_model_name(
691
-            ) ][ $model_object_data[ $model->primary_key_name() ] ];
692
-        }
693
-
694
-        try {
695
-            $model_name_field = $model->get_field_containing_related_model_name();
696
-        } catch (EE_Error $e) {
697
-            $model_name_field = null;
698
-        }
699
-
700
-        foreach ($model->field_settings(true) as $field_obj) {
701
-            if (! $field_obj instanceof EE_Foreign_Key_Int_Field) {
702
-                // not a foreign key, or it's a string foreign key
703
-                // which we leave alone, because those are things like country names,
704
-                // which we'd really rather not make 2 USAs etc (we'd actually prefer to just update one)
705
-                // or it's just a regular value that ought to be replaced
706
-                continue;
707
-            }
708
-            $models_pointed_to = $field_obj->get_model_names_pointed_to();
709
-            foreach ($models_pointed_to as $model_pointed_to_by_fk) {
710
-                if ($model_name_field) {
711
-                    $value_of_model_name_field = $model_object_data[ $model_name_field->get_name() ];
712
-                    if ($value_of_model_name_field == $model_pointed_to_by_fk) {
713
-                        $model_object_data[ $field_obj->get_name() ] = $this->_find_mapping_in(
714
-                            $model_object_data[ $field_obj->get_name() ],
715
-                            $model_pointed_to_by_fk,
716
-                            $old_db_to_new_db_mapping,
717
-                            $export_from_site_a_to_b
718
-                        );
719
-                        // once we've found a mapping for this field no need to continue
720
-                        // break;
721
-                    }
722
-                } else {
723
-                    $model_object_data[ $field_obj->get_name() ] = $this->_find_mapping_in(
724
-                        $model_object_data[ $field_obj->get_name() ],
725
-                        $model_pointed_to_by_fk,
726
-                        $old_db_to_new_db_mapping,
727
-                        $export_from_site_a_to_b
728
-                    );
729
-                    // once we've found a mapping for this field no need to continue
730
-                    // break;
731
-                }
732
-            }
733
-        }
734
-
735
-        if ($model instanceof EEM_Term_Taxonomy) {
736
-            $model_object_data = $this->_handle_split_term_ids($model_object_data);
737
-        }
738
-        return $model_object_data;
739
-    }
740
-
741
-
742
-    /**
743
-     * If the data was exported PRE-4.2, but then imported POST-4.2, then the term_id
744
-     * this term-taxonomy refers to may be out-of-date so we need to update it.
745
-     * see https://make.wordpress.org/core/2015/02/16/taxonomy-term-splitting-in-4-2-a-developer-guide/
746
-     *
747
-     * @param array $model_object_data
748
-     * @return array new model object data
749
-     */
750
-    protected function _handle_split_term_ids(array $model_object_data): array
751
-    {
752
-        if (
753
-            isset($model_object_data['term_id'])
754
-            && isset($model_object_data['taxonomy'])
755
-            && apply_filters(
756
-                'FHEE__EE_Import__handle_split_term_ids__function_exists',
757
-                function_exists('wp_get_split_term'),
758
-                $model_object_data
759
-            )
760
-        ) {
761
-            $new_term_id = wp_get_split_term($model_object_data['term_id'], $model_object_data['taxonomy']);
762
-            if ($new_term_id) {
763
-                $model_object_data['term_id'] = $new_term_id;
764
-            }
765
-        }
766
-        return $model_object_data;
767
-    }
768
-
769
-
770
-    /**
771
-     * Given the object's ID and its model's name, find it int he mapping data,
772
-     * bearing in mind where it came from
773
-     *
774
-     * @param int|string $object_id
775
-     * @param string     $model_name
776
-     * @param array      $old_db_to_new_db_mapping
777
-     * @param bool       $export_from_site_a_to_b
778
-     * @return int
779
-     */
780
-    protected function _find_mapping_in(
781
-        $object_id,
782
-        string $model_name,
783
-        array $old_db_to_new_db_mapping,
784
-        bool $export_from_site_a_to_b
785
-    ) {
786
-        if (isset($old_db_to_new_db_mapping[ $model_name ][ $object_id ])) {
787
-            return $old_db_to_new_db_mapping[ $model_name ][ $object_id ];
788
-        }
789
-        if ($object_id == '0' || $object_id == '') {
790
-            // leave as-is
791
-            return $object_id;
792
-        }
793
-        if ($export_from_site_a_to_b) {
794
-            // we couldn't find a mapping for this, and it's from a different site,
795
-            // so blank it out
796
-            return null;
797
-        }
798
-        // we couldn't find a mapping for this, but it's from this DB anyway
799
-        // so let's just leave it as-is
800
-        return $object_id;
801
-    }
802
-
803
-
804
-    /**
805
-     *
806
-     * @param int|string     $id_in_csv
807
-     * @param array  $model_object_data
808
-     * @param EEM_Base $model
809
-     * @param array  $old_db_to_new_db_mapping
810
-     * @return array updated $old_db_to_new_db_mapping
811
-     * @throws EE_Error
812
-     */
813
-    protected function _insert_from_data_array(
814
-        $id_in_csv,
815
-        array $model_object_data,
816
-        EEM_Base $model,
817
-        array $old_db_to_new_db_mapping
818
-    ): array {
819
-        // remove the primary key, if there is one (we don't want it for inserts OR updates)
820
-        // we'll put it back in if we need it
821
-        if ($model->has_primary_key_field() && $model->get_primary_key_field()->is_auto_increment()) {
822
-            $effective_id = $model_object_data[ $model->primary_key_name() ];
823
-            unset($model_object_data[ $model->primary_key_name() ]);
824
-        } else {
825
-            $effective_id = $model->get_index_primary_key_string($model_object_data);
826
-        }
827
-        // the model takes care of validating the CSV's input
828
-        try {
829
-            $new_id = $model->insert($model_object_data);
830
-            if ($new_id) {
831
-                $old_db_to_new_db_mapping[ $model->get_this_model_name() ][ $id_in_csv ] = $new_id;
832
-                $this->_total_inserts++;
833
-                EE_Error::add_success(
834
-                    sprintf(
835
-                        esc_html__("Successfully added new %s (with id %s) with csv data %s", "event_espresso"),
836
-                        $model->get_this_model_name(),
837
-                        $new_id,
838
-                        implode(",", $model_object_data)
839
-                    )
840
-                );
841
-            } else {
842
-                $this->_total_insert_errors++;
843
-                // put the ID used back in there for the error message
844
-                if ($model->has_primary_key_field()) {
845
-                    $model_object_data[ $model->primary_key_name() ] = $effective_id;
846
-                }
847
-                EE_Error::add_error(
848
-                    sprintf(
849
-                        esc_html__("Could not insert new %s with the csv data: %s", "event_espresso"),
850
-                        $model->get_this_model_name(),
851
-                        http_build_query($model_object_data)
852
-                    ),
853
-                    __FILE__,
854
-                    __FUNCTION__,
855
-                    __LINE__
856
-                );
857
-            }
858
-        } catch (EE_Error $e) {
859
-            $this->_total_insert_errors++;
860
-            if ($model->has_primary_key_field()) {
861
-                $model_object_data[ $model->primary_key_name() ] = $effective_id;
862
-            }
863
-            EE_Error::add_error(
864
-                sprintf(
865
-                    esc_html__("Could not insert new %s with the csv data: %s because %s", "event_espresso"),
866
-                    $model->get_this_model_name(),
867
-                    implode(",", $model_object_data),
868
-                    $e->getMessage()
869
-                ),
870
-                __FILE__,
871
-                __FUNCTION__,
872
-                __LINE__
873
-            );
874
-        }
875
-        return $old_db_to_new_db_mapping;
876
-    }
877
-
878
-
879
-    /**
880
-     * Given the model object data, finds the row to update and updates it
881
-     *
882
-     * @param string|int $id_in_csv
883
-     * @param array      $model_object_data
884
-     * @param EEM_Base   $model
885
-     * @param array      $old_db_to_new_db_mapping
886
-     * @return array updated $old_db_to_new_db_mapping
887
-     */
888
-    protected function _update_from_data_array(
889
-        $id_in_csv,
890
-        array $model_object_data,
891
-        EEM_Base $model,
892
-        array $old_db_to_new_db_mapping
893
-    ): array {
894
-        $conditions = null;
895
-        try {
896
-            // let's keep two copies of the model object data:
897
-            // one for performing an update, one for everything else
898
-            $model_object_data_for_update = $model_object_data;
899
-            if ($model->has_primary_key_field()) {
900
-                $conditions = [$model->primary_key_name() => $model_object_data[ $model->primary_key_name() ]];
901
-                // remove the primary key because we shouldn't use it for updating
902
-                unset($model_object_data_for_update[ $model->primary_key_name() ]);
903
-            } elseif ($model->get_combined_primary_key_fields() > 1) {
904
-                $conditions = [];
905
-                foreach ($model->get_combined_primary_key_fields() as $key_field) {
906
-                    $conditions[ $key_field->get_name() ] = $model_object_data[ $key_field->get_name() ];
907
-                }
908
-            } else {
909
-                // this should just throw an exception, explaining that we dont have a primary key (or a combined key)
910
-                $model->primary_key_name();
911
-            }
912
-
913
-            $success = $model->update($model_object_data_for_update, [$conditions]);
914
-            if ($success) {
915
-                $this->_total_updates++;
916
-                EE_Error::add_success(
917
-                    sprintf(
918
-                        esc_html__("Successfully updated %s with csv data %s", "event_espresso"),
919
-                        $model->get_this_model_name(),
920
-                        implode(",", $model_object_data_for_update)
921
-                    )
922
-                );
923
-                // we should still record the mapping even though it was an update
924
-                // because if we were going to insert something but it was going to conflict
925
-                // we would have last-minute decided to update. So we'd like to know what we updated
926
-                // and so we record what record ended up being updated using the mapping
927
-                if ($model->has_primary_key_field()) {
928
-                    $new_key_for_mapping = $model_object_data[ $model->primary_key_name() ];
929
-                } else {
930
-                    // no primary key just a combined key
931
-                    $new_key_for_mapping = $model->get_index_primary_key_string($model_object_data);
932
-                }
933
-                $old_db_to_new_db_mapping[ $model->get_this_model_name() ][ $id_in_csv ] = $new_key_for_mapping;
934
-            } else {
935
-                $matched_items = $model->get_all([$conditions]);
936
-                if (! $matched_items) {
937
-                    // no items were matched (so we shouldn't have updated)... but then we should have inserted? what the heck?
938
-                    $this->_total_update_errors++;
939
-                    EE_Error::add_error(
940
-                        sprintf(
941
-                            esc_html__(
942
-                                "Could not update %s with the csv data: '%s' for an unknown reason (using WHERE conditions %s)",
943
-                                "event_espresso"
944
-                            ),
945
-                            $model->get_this_model_name(),
946
-                            http_build_query($model_object_data),
947
-                            http_build_query($conditions)
948
-                        ),
949
-                        __FILE__,
950
-                        __FUNCTION__,
951
-                        __LINE__
952
-                    );
953
-                } else {
954
-                    $this->_total_updates++;
955
-                    EE_Error::add_success(
956
-                        sprintf(
957
-                            esc_html__(
958
-                                "%s with csv data '%s' was found in the database and didn't need updating because all the data is identical.",
959
-                                "event_espresso"
960
-                            ),
961
-                            $model->get_this_model_name(),
962
-                            implode(",", $model_object_data)
963
-                        )
964
-                    );
965
-                }
966
-            }
967
-        } catch (EE_Error $e) {
968
-            $this->_total_update_errors++;
969
-            $basic_message = sprintf(
970
-                esc_html__("Could not update %s with the csv data: %s because %s", "event_espresso"),
971
-                $model->get_this_model_name(),
972
-                implode(",", $model_object_data),
973
-                $e->getMessage()
974
-            );
975
-            $debug_message = $basic_message . ' Stack trace: ' . $e->getTraceAsString();
976
-            EE_Error::add_error("$basic_message | $debug_message", __FILE__, __FUNCTION__, __LINE__);
977
-        }
978
-        return $old_db_to_new_db_mapping;
979
-    }
980
-
981
-
982
-    /**
983
-     * Gets the number of inserts performed since importer was instantiated or reset
984
-     *
985
-     * @return int
986
-     */
987
-    public function get_total_inserts(): int
988
-    {
989
-        return $this->_total_inserts;
990
-    }
991
-
992
-
993
-    /**
994
-     *  Gets the number of insert errors since importer was instantiated or reset
995
-     *
996
-     * @return int
997
-     */
998
-    public function get_total_insert_errors(): int
999
-    {
1000
-        return $this->_total_insert_errors;
1001
-    }
1002
-
1003
-
1004
-    /**
1005
-     *  Gets the number of updates performed since importer was instantiated or reset
1006
-     *
1007
-     * @return int
1008
-     */
1009
-    public function get_total_updates(): int
1010
-    {
1011
-        return $this->_total_updates;
1012
-    }
1013
-
1014
-
1015
-    /**
1016
-     *  Gets the number of update errors since importer was instantiated or reset
1017
-     *
1018
-     * @return int
1019
-     */
1020
-    public function get_total_update_errors(): int
1021
-    {
1022
-        return $this->_total_update_errors;
1023
-    }
130
+		return ob_get_clean();
131
+	}
132
+
133
+
134
+	/**
135
+	 * @Import Event Espresso data - some code "borrowed" from event espresso csv_import.php
136
+	 * @access public
137
+	 * @return boolean success
138
+	 * @throws EE_Error
139
+	 * @throws ReflectionException
140
+	 */
141
+	public function import(): bool
142
+	{
143
+		require_once(EE_CLASSES . 'EE_CSV.class.php');
144
+		$EE_CSV = EE_CSV::instance();
145
+		/** @var RequestInterface $request */
146
+		$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
147
+		$files   = $this->getFileParams($request);
148
+
149
+		$filename  = $files['file']['name'][0];
150
+		$file_ext  = substr(strrchr($filename, '.'), 1);
151
+		$temp_file = $files['file']['tmp_name'][0];
152
+		$filesize  = $files['file']['size'][0] / 1024;// convert from bytes to KB
153
+
154
+		if ($file_ext !== 'csv') {
155
+			EE_Error::add_error(
156
+				sprintf(
157
+					esc_html__('%s  had an invalid file extension, not uploaded', 'event_espresso'),
158
+					$filename
159
+				),
160
+				__FILE__,
161
+				__FUNCTION__,
162
+				__LINE__
163
+			);
164
+			return false;
165
+		}
166
+
167
+		$ignore_max_file_size = (bool) apply_filters('FHEE__EE_Import__import__ignore_max_file_size', true, $files);
168
+		if (! $ignore_max_file_size) {
169
+			// max upload size in KB
170
+			$max_upload = $EE_CSV->get_max_upload_size();
171
+			if ($filesize > $max_upload) {
172
+				EE_Error::add_error(
173
+					sprintf(
174
+						esc_html__(
175
+							"%s was too large of a file and could not be uploaded. The max filesize is %s' KB.",
176
+							'event_espresso'
177
+						),
178
+						$filename,
179
+						$max_upload
180
+					),
181
+					__FILE__,
182
+					__FUNCTION__,
183
+					__LINE__
184
+				);
185
+				return false;
186
+			}
187
+		}
188
+
189
+		$wp_upload_dir = str_replace(['\\', '/'], '/', wp_upload_dir());
190
+		$path_to_file  = $wp_upload_dir['basedir'] . '/espresso/' . $filename;
191
+
192
+		if (! move_uploaded_file($temp_file, $path_to_file)) {
193
+			EE_Error::add_error(
194
+				sprintf(
195
+					esc_html__('%s was not successfully uploaded', 'event_espresso'),
196
+					$filename
197
+				),
198
+				__FILE__,
199
+				__FUNCTION__,
200
+				__LINE__
201
+			);
202
+			return false;
203
+		}
204
+
205
+		// convert csv to array
206
+		$csv_array = $EE_CSV->import_csv_to_model_data_array($path_to_file);
207
+
208
+		// was data successfully stored in an array?
209
+		if (! is_array($csv_array)) {
210
+			// no array? must be an error
211
+			EE_Error::add_error(
212
+				esc_html__('No file seems to have been uploaded', 'event_espresso'),
213
+				__FILE__,
214
+				__FUNCTION__,
215
+				__LINE__
216
+			);
217
+			return false;
218
+		}
219
+
220
+		// save processed codes to db
221
+		if ($this->save_csv_data_array_to_db($csv_array)) {
222
+			return true;
223
+		}
224
+		return false;
225
+	}
226
+
227
+
228
+	/**
229
+	 * @param RequestInterface $request
230
+	 * @return array|null
231
+	 * @since $VID:$
232
+	 */
233
+	private function getFileParams(RequestInterface $request): ?array
234
+	{
235
+		$error = null;
236
+		if (! ($request->requestParamIsSet('import') && $request->requestParamIsSet('csv_submitted'))) {
237
+			$error = esc_html__(
238
+				"Invalid request. Expected request params 'import' or 'csv_submitted' were missing.",
239
+				'event_espresso'
240
+			);
241
+		}
242
+		$files = $request->filesParams();
243
+
244
+		if (! $error) {
245
+			switch ($files['file']['error'][0]) {
246
+				case UPLOAD_ERR_OK:
247
+					break;
248
+				case UPLOAD_ERR_INI_SIZE:
249
+					$error = esc_html__(
250
+						"'The uploaded file exceeds the upload_max_filesize directive in php.ini.'",
251
+						'event_espresso'
252
+					);
253
+					break;
254
+				case UPLOAD_ERR_FORM_SIZE:
255
+					$error = esc_html__(
256
+						'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
257
+						'event_espresso'
258
+					);
259
+					break;
260
+				case UPLOAD_ERR_PARTIAL:
261
+					$error = esc_html__('The uploaded file was only partially uploaded.', 'event_espresso');
262
+					break;
263
+				case UPLOAD_ERR_NO_FILE:
264
+					$error = esc_html__('No file was uploaded.', 'event_espresso');
265
+					break;
266
+				case UPLOAD_ERR_NO_TMP_DIR:
267
+					$error = esc_html__('Missing a temporary folder.', 'event_espresso');
268
+					break;
269
+				case UPLOAD_ERR_CANT_WRITE:
270
+					$error = esc_html__('Failed to write file to disk.', 'event_espresso');
271
+					break;
272
+				case UPLOAD_ERR_EXTENSION:
273
+					$error = esc_html__('File upload stopped by extension.', 'event_espresso');
274
+					break;
275
+				default:
276
+					$error = esc_html__(
277
+						'An unknown error occurred and the file could not be uploaded',
278
+						'event_espresso'
279
+					);
280
+			}
281
+		}
282
+
283
+		if ($error) {
284
+			EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
285
+			return null;
286
+		}
287
+
288
+		return $files;
289
+	}
290
+
291
+
292
+	/**
293
+	 *    Given an array of data (usually from a CSV import) attempts to save that data to the db.
294
+	 *    If $model_name ISN'T provided, assumes that this is a 3d array, with toplevel keys being model names,
295
+	 *    next level being numeric indexes adn each value representing a model object, and the last layer down
296
+	 *    being keys of model fields and their proposed values.
297
+	 *    If $model_name IS provided, assumes a 2d array of the bottom two layers previously mentioned.
298
+	 *    If the CSV data says (in the metadata row) that it's from the SAME database,
299
+	 *    we treat the IDs in the CSV as the normal IDs, and try to update those records. However, if those
300
+	 *    IDs DON'T exist in the database, they're treated as temporary IDs,
301
+	 *    which can used elsewhere to refer to the same object. Once an item
302
+	 *    with a temporary ID gets inserted, we record its mapping from temporary
303
+	 *    ID to real ID, and use the real ID in place of the temporary ID
304
+	 *    when that temporary ID was used as a foreign key.
305
+	 *    If the CSV data says (in the metadata again) that it's from a DIFFERENT database,
306
+	 *    we treat all the IDs in the CSV as temporary ID- eg, if the CSV specifies an event with
307
+	 *    ID 1, and the database already has an event with ID 1, we assume that's just a coincidence,
308
+	 *    and insert a new event, and map it's temporary ID of 1 over to its new real ID.
309
+	 *    An important exception are non-auto-increment primary keys. If one entry in the
310
+	 *    CSV file has the same ID as one in the DB, we assume they are meant to be
311
+	 *    the same item, and instead update the item in the DB with that same ID.
312
+	 *    Also note, we remember the mappings permanently. So the 2nd, 3rd, and 10000th
313
+	 *    time you import a CSV from a different site, we remember their mappings, and
314
+	 * will try to update the item in the DB instead of inserting another item (eg
315
+	 * if we previously imported an event with temporary ID 1, and then it got a
316
+	 * real ID of 123, we remember that. So the next time we import an event with
317
+	 * temporary ID, from the same site, we know that it's real ID is 123, and will
318
+	 * update that event, instead of adding a new event).
319
+	 *
320
+	 * @param array  $csv_data_array    the array containing the csv data produced from
321
+	 *                                  EE_CSV::import_csv_to_model_data_array()
322
+	 * @param string $model_name
323
+	 * @return bool                     TRUE on success, FALSE on fail
324
+	 * @throws EE_Error
325
+	 * @throws ReflectionException
326
+	 */
327
+	public function save_csv_data_array_to_db(array $csv_data_array, string $model_name = ''): bool
328
+	{
329
+		$success = false;
330
+		// whether to treat this import as if it's data from a different database or not
331
+		// ie, if it IS from a different database, ignore foreign keys with
332
+		$export_from_site_a_to_b = true;
333
+		// first level of array is not table information but a table name was passed to the function
334
+		// array is only two levels deep, so let's fix that by adding a level, else the next steps will fail
335
+		if ($model_name) {
336
+			$csv_data_array = [$csv_data_array];
337
+		}
338
+		// begin looking through the $csv_data_array, expecting the toplevel key to be the model's name...
339
+		$old_site_url = 'none-specified';
340
+		// handle metadata
341
+		if (isset($csv_data_array[ EE_CSV::metadata_header ])) {
342
+			$csv_metadata = array_shift($csv_data_array[ EE_CSV::metadata_header ]);
343
+			// ok so its metadata, dont try to save it to the db obviously...
344
+			if (isset($csv_metadata['site_url']) && $csv_metadata['site_url'] == site_url()) {
345
+				EE_Error::add_attention(
346
+					esc_html__(
347
+						'CSV Data appears to be from the same database, so attempting to update data',
348
+						'event_espresso'
349
+					)
350
+				);
351
+				$export_from_site_a_to_b = false;
352
+			} else {
353
+				$old_site_url = $csv_metadata['site_url'] ?? $old_site_url;
354
+				EE_Error::add_attention(
355
+					sprintf(
356
+						esc_html__(
357
+							"CSV Data appears to be from a different database (%s instead of %s), so we assume IDs in the CSV data DO NOT correspond to IDs in this database",
358
+							"event_espresso"
359
+						),
360
+						$old_site_url,
361
+						site_url()
362
+					)
363
+				);
364
+			}
365
+			unset($csv_data_array[ EE_CSV::metadata_header ]);
366
+		}
367
+
368
+		$id_mapping_option_name = 'ee_id_mapping_from' . sanitize_title($old_site_url);
369
+		/**
370
+		 * @var $old_db_to_new_db_mapping   2d array: toplevel keys being model names,
371
+		 *                                            bottom-level keys being the original key,
372
+		 *                                            and the value will be the newly-inserted ID.
373
+		 *                                  If we have already imported data from the same website via CSV,
374
+		 *                                  it should be kept in this wp option
375
+		 */
376
+		$old_db_to_new_db_mapping = (array) get_option($id_mapping_option_name, []);
377
+
378
+		if ($old_db_to_new_db_mapping) {
379
+			EE_Error::add_attention(
380
+				sprintf(
381
+					esc_html__(
382
+						"We noticed you have imported data via CSV from %s before. Because of this, IDs in your CSV have been mapped to their new IDs in %s",
383
+						"event_espresso"
384
+					),
385
+					$old_site_url,
386
+					site_url()
387
+				)
388
+			);
389
+		}
390
+		$old_db_to_new_db_mapping = $this->save_data_rows_to_db(
391
+			$csv_data_array,
392
+			$export_from_site_a_to_b,
393
+			$old_db_to_new_db_mapping
394
+		);
395
+
396
+		// save the mapping from old db to new db in case they try re-importing the same data from the same website again
397
+		update_option($id_mapping_option_name, $old_db_to_new_db_mapping);
398
+
399
+		if ($this->_total_updates > 0) {
400
+			EE_Error::add_success(
401
+				sprintf(
402
+					esc_html__("%s existing records in the database were updated.", "event_espresso"),
403
+					$this->_total_updates
404
+				)
405
+			);
406
+			$success = true;
407
+		}
408
+		if ($this->_total_inserts > 0) {
409
+			EE_Error::add_success(
410
+				sprintf(
411
+					esc_html__("%s new records were added to the database.", "event_espresso"),
412
+					$this->_total_inserts
413
+				)
414
+			);
415
+			$success = true;
416
+		}
417
+
418
+		if ($this->_total_update_errors > 0) {
419
+			EE_Error::add_error(
420
+				sprintf(
421
+					esc_html__(
422
+						"'One or more errors occurred, and a total of %s existing records in the database were <strong>not</strong> updated.'",
423
+						"event_espresso"
424
+					),
425
+					$this->_total_update_errors
426
+				),
427
+				__FILE__,
428
+				__FUNCTION__,
429
+				__LINE__
430
+			);
431
+			$success = false;
432
+		}
433
+		if ($this->_total_insert_errors > 0) {
434
+			EE_Error::add_error(
435
+				sprintf(
436
+					esc_html__(
437
+						"One or more errors occurred, and a total of %s new records were <strong>not</strong> added to the database.'",
438
+						"event_espresso"
439
+					),
440
+					$this->_total_insert_errors
441
+				),
442
+				__FILE__,
443
+				__FUNCTION__,
444
+				__LINE__
445
+			);
446
+			$success = false;
447
+		}
448
+
449
+		// lastly, we need to update the datetime and ticket sold amounts
450
+		// as those may have been affected by this
451
+		EEM_Ticket::instance()->update_tickets_sold(EEM_Ticket::instance()->get_all());
452
+
453
+		// if there was at least one success and absolutely no errors
454
+		return $success;
455
+	}
456
+
457
+
458
+	/**
459
+	 * Processes the array of data, given the knowledge that it's from the same database or a different one,
460
+	 * and the mapping from temporary IDs to real IDs.
461
+	 * If the data is from a different database, we treat the primary keys and their corresponding
462
+	 * foreign keys as "temp Ids", basically identifiers that get mapped to real primary keys
463
+	 * in the real target database. As items are inserted, their temporary primary keys
464
+	 * are mapped to the real IDs in the target database. Also, before doing any update or
465
+	 * insert, we replace all the temp ID which are foreign keys with their mapped real IDs.
466
+	 * An exception: string primary keys are treated as real IDs, or else we'd need to
467
+	 * dynamically generate new string primary keys which would be very awkward for the country table etc.
468
+	 * Also, models with no primary key are strange too. We combine use their primary key INDEX (a
469
+	 * combination of fields) to create a unique string identifying the row and store
470
+	 * those in the mapping.
471
+	 *
472
+	 * If the data is from the same database, we usually treat primary keys as real IDs.
473
+	 * An exception is if there is nothing in the database for that ID. If that's the case,
474
+	 * we need to insert a new row for that ID, and then map from the non-existent ID
475
+	 * to the newly-inserted real ID.
476
+	 *
477
+	 * @param array $csv_data_array
478
+	 * @param bool  $export_from_site_a_to_b
479
+	 * @param array $old_db_to_new_db_mapping
480
+	 * @return array|bool updated $old_db_to_new_db_mapping
481
+	 * @throws EE_Error
482
+	 * @throws ReflectionException
483
+	 */
484
+	public function save_data_rows_to_db(
485
+		array $csv_data_array,
486
+		bool $export_from_site_a_to_b,
487
+		array $old_db_to_new_db_mapping
488
+	) {
489
+		foreach ($csv_data_array as $model_name => $model_data_from_import) {
490
+			// check that assumption was correct. If
491
+			if (! EE_Registry::instance()->is_model_name($model_name)) {
492
+				// no table info in the array and no table name passed to the function?? FAIL
493
+				EE_Error::add_error(
494
+					esc_html__(
495
+						'No table information was specified and/or found, therefore the import could not be completed',
496
+						'event_espresso'
497
+					),
498
+					__FILE__,
499
+					__FUNCTION__,
500
+					__LINE__
501
+				);
502
+				return false;
503
+			}
504
+
505
+			/* @var $model EEM_Base */
506
+			$model = EE_Registry::instance()->load_model($model_name);
507
+
508
+			// so without further ado, scanning all the data provided for primary keys and their initial values
509
+			foreach ($model_data_from_import as $model_object_data) {
510
+				// before we do ANYTHING, make sure the csv row wasn't just completely blank
511
+				$row_is_completely_empty = true;
512
+				foreach ($model_object_data as $field) {
513
+					if ($field) {
514
+						$row_is_completely_empty = false;
515
+					}
516
+				}
517
+				if ($row_is_completely_empty) {
518
+					continue;
519
+				}
520
+				// find the PK in the row of data (or a combined key if
521
+				// there is no primary key)
522
+				if ($model->has_primary_key_field()) {
523
+					$id_in_csv = $model_object_data[ $model->primary_key_name() ];
524
+				} else {
525
+					$id_in_csv = $model->get_index_primary_key_string($model_object_data);
526
+				}
527
+
528
+				$model_object_data = $this->_replace_temp_ids_with_mappings(
529
+					$model_object_data,
530
+					$model,
531
+					$old_db_to_new_db_mapping,
532
+					$export_from_site_a_to_b
533
+				);
534
+				// now we need to decide if we're going to
535
+				$what_to_do = $export_from_site_a_to_b
536
+					// add a new model object given the $model_object_data
537
+					? $this->_decide_whether_to_insert_or_update_given_data_from_other_db(
538
+						$id_in_csv,
539
+						$model_name,
540
+						$old_db_to_new_db_mapping
541
+					)
542
+					//  or just update cuz this is just a re-import
543
+					: $this->_decide_whether_to_insert_or_update_given_data_from_same_db(
544
+						$model_object_data,
545
+						$model
546
+					);
547
+
548
+				if ($what_to_do === EE_Import::do_nothing) {
549
+					continue;
550
+				}
551
+
552
+				// double-check we actually want to insert, if that's what we're planning
553
+				// based on whether this item would be unique in the DB or not
554
+				if ($what_to_do == EE_Import::do_insert) {
555
+					// we're supposed to be inserting. But wait, will this thing
556
+					// be acceptable if inserted?
557
+					$conflicting = $model->get_one_conflicting($model_object_data, false);
558
+					if ($conflicting) {
559
+						// ok, this item would conflict if inserted. Just update the item that it conflicts with.
560
+						$what_to_do = EE_Import::do_update;
561
+						// and if this model has a primary key, remember its mapping
562
+						if ($model->has_primary_key_field()) {
563
+							$old_db_to_new_db_mapping[ $model_name ][ $id_in_csv ] = $conflicting->ID();
564
+							$model_object_data[ $model->primary_key_name() ]       = $conflicting->ID();
565
+						} else {
566
+							// we want to update this conflicting item, instead of inserting a conflicting item
567
+							// so we need to make sure they match entirely
568
+							// (it's possible that they only conflicted on one field,
569
+							// but we need them to match on other fields for the WHERE conditions in the update).
570
+							// At the time of this comment, there were no models like this
571
+							foreach ($model->get_combined_primary_key_fields() as $key_field) {
572
+								$model_object_data[ $key_field->get_name() ] = $conflicting->get(
573
+									$key_field->get_name()
574
+								);
575
+							}
576
+						}
577
+					}
578
+				}
579
+				if ($what_to_do == EE_Import::do_insert) {
580
+					$old_db_to_new_db_mapping = $this->_insert_from_data_array(
581
+						$id_in_csv,
582
+						$model_object_data,
583
+						$model,
584
+						$old_db_to_new_db_mapping
585
+					);
586
+				} elseif ($what_to_do == EE_Import::do_update) {
587
+					$old_db_to_new_db_mapping = $this->_update_from_data_array(
588
+						$id_in_csv,
589
+						$model_object_data,
590
+						$model,
591
+						$old_db_to_new_db_mapping
592
+					);
593
+				} else {
594
+					throw new EE_Error(
595
+						sprintf(
596
+							esc_html__(
597
+								'Programming error. We should be inserting or updating, but instead we are being told to "%s", whifh is invalid',
598
+								'event_espresso'
599
+							),
600
+							$what_to_do
601
+						)
602
+					);
603
+				}
604
+			}
605
+		}
606
+		return $old_db_to_new_db_mapping;
607
+	}
608
+
609
+
610
+	/**
611
+	 * Decides whether or not to insert, given that this data is from another database.
612
+	 * So, if the primary key of this $model_object_data already exists in the database,
613
+	 * it's just a coincidence and we should still insert. The only time we should
614
+	 * update is when we know what it maps to, or there's something that would
615
+	 * conflict (and we should instead just update that conflicting thing)
616
+	 *
617
+	 * @param string $id_in_csv
618
+	 * @param string $model_name
619
+	 * @param array  $old_db_to_new_db_mapping by reference so it can be modified
620
+	 * @return string one of the constants on this class that starts with do_*
621
+	 */
622
+	protected function _decide_whether_to_insert_or_update_given_data_from_other_db(
623
+		string $id_in_csv,
624
+		string $model_name,
625
+		array $old_db_to_new_db_mapping
626
+	): string {
627
+		// if it's a site-to-site export-and-import, see if this model object's id
628
+		// in the old data that we know of
629
+		if (isset($old_db_to_new_db_mapping[ $model_name ][ $id_in_csv ])) {
630
+			return EE_Import::do_update;
631
+		}
632
+		return EE_Import::do_insert;
633
+	}
634
+
635
+
636
+	/**
637
+	 * If this thing basically already exists in the database, we want to update it;
638
+	 * otherwise insert it (ie, someone tweaked the CSV file, or the item was
639
+	 * deleted in the database so it should be re-inserted)
640
+	 *
641
+	 * @param array    $model_object_data
642
+	 * @param EEM_Base $model
643
+	 * @return string
644
+	 * @throws EE_Error
645
+	 */
646
+	protected function _decide_whether_to_insert_or_update_given_data_from_same_db(
647
+		array $model_object_data,
648
+		EEM_Base $model
649
+	): string {
650
+		// in this case, check if this thing ACTUALLY exists in the database
651
+		if ($model->get_one_conflicting($model_object_data)) {
652
+			return EE_Import::do_update;
653
+		}
654
+		return EE_Import::do_insert;
655
+	}
656
+
657
+
658
+	/**
659
+	 * Using the $old_db_to_new_db_mapping array, replaces all the temporary IDs
660
+	 * with their mapped real IDs. Eg, if importing from site A to B, the mapping
661
+	 * file may indicate that the ID "my_event_id" maps to an actual event ID of 123.
662
+	 * So this function searches for any event temp Ids called "my_event_id" and
663
+	 * replaces them with 123.
664
+	 * Also, if there is no temp ID for the INT foreign keys from another database,
665
+	 * replaces them with 0 or the field's default.
666
+	 *
667
+	 * @param array    $model_object_data
668
+	 * @param EEM_Base $model
669
+	 * @param array    $old_db_to_new_db_mapping
670
+	 * @param bool     $export_from_site_a_to_b
671
+	 * @return array updated model object data with temp IDs removed
672
+	 * @throws EE_Error
673
+	 */
674
+	protected function _replace_temp_ids_with_mappings(
675
+		array $model_object_data,
676
+		EEM_Base $model,
677
+		array $old_db_to_new_db_mapping,
678
+		bool $export_from_site_a_to_b
679
+	): array {
680
+		// if this model object's primary key is in the mapping, replace it
681
+		if (
682
+			$model->has_primary_key_field()
683
+			&& $model->get_primary_key_field()->is_auto_increment()
684
+			&& isset($old_db_to_new_db_mapping[ $model->get_this_model_name() ])
685
+			&& isset(
686
+				$old_db_to_new_db_mapping[ $model->get_this_model_name(
687
+				) ][ $model_object_data[ $model->primary_key_name() ] ]
688
+			)
689
+		) {
690
+			$model_object_data[ $model->primary_key_name() ] = $old_db_to_new_db_mapping[ $model->get_this_model_name(
691
+			) ][ $model_object_data[ $model->primary_key_name() ] ];
692
+		}
693
+
694
+		try {
695
+			$model_name_field = $model->get_field_containing_related_model_name();
696
+		} catch (EE_Error $e) {
697
+			$model_name_field = null;
698
+		}
699
+
700
+		foreach ($model->field_settings(true) as $field_obj) {
701
+			if (! $field_obj instanceof EE_Foreign_Key_Int_Field) {
702
+				// not a foreign key, or it's a string foreign key
703
+				// which we leave alone, because those are things like country names,
704
+				// which we'd really rather not make 2 USAs etc (we'd actually prefer to just update one)
705
+				// or it's just a regular value that ought to be replaced
706
+				continue;
707
+			}
708
+			$models_pointed_to = $field_obj->get_model_names_pointed_to();
709
+			foreach ($models_pointed_to as $model_pointed_to_by_fk) {
710
+				if ($model_name_field) {
711
+					$value_of_model_name_field = $model_object_data[ $model_name_field->get_name() ];
712
+					if ($value_of_model_name_field == $model_pointed_to_by_fk) {
713
+						$model_object_data[ $field_obj->get_name() ] = $this->_find_mapping_in(
714
+							$model_object_data[ $field_obj->get_name() ],
715
+							$model_pointed_to_by_fk,
716
+							$old_db_to_new_db_mapping,
717
+							$export_from_site_a_to_b
718
+						);
719
+						// once we've found a mapping for this field no need to continue
720
+						// break;
721
+					}
722
+				} else {
723
+					$model_object_data[ $field_obj->get_name() ] = $this->_find_mapping_in(
724
+						$model_object_data[ $field_obj->get_name() ],
725
+						$model_pointed_to_by_fk,
726
+						$old_db_to_new_db_mapping,
727
+						$export_from_site_a_to_b
728
+					);
729
+					// once we've found a mapping for this field no need to continue
730
+					// break;
731
+				}
732
+			}
733
+		}
734
+
735
+		if ($model instanceof EEM_Term_Taxonomy) {
736
+			$model_object_data = $this->_handle_split_term_ids($model_object_data);
737
+		}
738
+		return $model_object_data;
739
+	}
740
+
741
+
742
+	/**
743
+	 * If the data was exported PRE-4.2, but then imported POST-4.2, then the term_id
744
+	 * this term-taxonomy refers to may be out-of-date so we need to update it.
745
+	 * see https://make.wordpress.org/core/2015/02/16/taxonomy-term-splitting-in-4-2-a-developer-guide/
746
+	 *
747
+	 * @param array $model_object_data
748
+	 * @return array new model object data
749
+	 */
750
+	protected function _handle_split_term_ids(array $model_object_data): array
751
+	{
752
+		if (
753
+			isset($model_object_data['term_id'])
754
+			&& isset($model_object_data['taxonomy'])
755
+			&& apply_filters(
756
+				'FHEE__EE_Import__handle_split_term_ids__function_exists',
757
+				function_exists('wp_get_split_term'),
758
+				$model_object_data
759
+			)
760
+		) {
761
+			$new_term_id = wp_get_split_term($model_object_data['term_id'], $model_object_data['taxonomy']);
762
+			if ($new_term_id) {
763
+				$model_object_data['term_id'] = $new_term_id;
764
+			}
765
+		}
766
+		return $model_object_data;
767
+	}
768
+
769
+
770
+	/**
771
+	 * Given the object's ID and its model's name, find it int he mapping data,
772
+	 * bearing in mind where it came from
773
+	 *
774
+	 * @param int|string $object_id
775
+	 * @param string     $model_name
776
+	 * @param array      $old_db_to_new_db_mapping
777
+	 * @param bool       $export_from_site_a_to_b
778
+	 * @return int
779
+	 */
780
+	protected function _find_mapping_in(
781
+		$object_id,
782
+		string $model_name,
783
+		array $old_db_to_new_db_mapping,
784
+		bool $export_from_site_a_to_b
785
+	) {
786
+		if (isset($old_db_to_new_db_mapping[ $model_name ][ $object_id ])) {
787
+			return $old_db_to_new_db_mapping[ $model_name ][ $object_id ];
788
+		}
789
+		if ($object_id == '0' || $object_id == '') {
790
+			// leave as-is
791
+			return $object_id;
792
+		}
793
+		if ($export_from_site_a_to_b) {
794
+			// we couldn't find a mapping for this, and it's from a different site,
795
+			// so blank it out
796
+			return null;
797
+		}
798
+		// we couldn't find a mapping for this, but it's from this DB anyway
799
+		// so let's just leave it as-is
800
+		return $object_id;
801
+	}
802
+
803
+
804
+	/**
805
+	 *
806
+	 * @param int|string     $id_in_csv
807
+	 * @param array  $model_object_data
808
+	 * @param EEM_Base $model
809
+	 * @param array  $old_db_to_new_db_mapping
810
+	 * @return array updated $old_db_to_new_db_mapping
811
+	 * @throws EE_Error
812
+	 */
813
+	protected function _insert_from_data_array(
814
+		$id_in_csv,
815
+		array $model_object_data,
816
+		EEM_Base $model,
817
+		array $old_db_to_new_db_mapping
818
+	): array {
819
+		// remove the primary key, if there is one (we don't want it for inserts OR updates)
820
+		// we'll put it back in if we need it
821
+		if ($model->has_primary_key_field() && $model->get_primary_key_field()->is_auto_increment()) {
822
+			$effective_id = $model_object_data[ $model->primary_key_name() ];
823
+			unset($model_object_data[ $model->primary_key_name() ]);
824
+		} else {
825
+			$effective_id = $model->get_index_primary_key_string($model_object_data);
826
+		}
827
+		// the model takes care of validating the CSV's input
828
+		try {
829
+			$new_id = $model->insert($model_object_data);
830
+			if ($new_id) {
831
+				$old_db_to_new_db_mapping[ $model->get_this_model_name() ][ $id_in_csv ] = $new_id;
832
+				$this->_total_inserts++;
833
+				EE_Error::add_success(
834
+					sprintf(
835
+						esc_html__("Successfully added new %s (with id %s) with csv data %s", "event_espresso"),
836
+						$model->get_this_model_name(),
837
+						$new_id,
838
+						implode(",", $model_object_data)
839
+					)
840
+				);
841
+			} else {
842
+				$this->_total_insert_errors++;
843
+				// put the ID used back in there for the error message
844
+				if ($model->has_primary_key_field()) {
845
+					$model_object_data[ $model->primary_key_name() ] = $effective_id;
846
+				}
847
+				EE_Error::add_error(
848
+					sprintf(
849
+						esc_html__("Could not insert new %s with the csv data: %s", "event_espresso"),
850
+						$model->get_this_model_name(),
851
+						http_build_query($model_object_data)
852
+					),
853
+					__FILE__,
854
+					__FUNCTION__,
855
+					__LINE__
856
+				);
857
+			}
858
+		} catch (EE_Error $e) {
859
+			$this->_total_insert_errors++;
860
+			if ($model->has_primary_key_field()) {
861
+				$model_object_data[ $model->primary_key_name() ] = $effective_id;
862
+			}
863
+			EE_Error::add_error(
864
+				sprintf(
865
+					esc_html__("Could not insert new %s with the csv data: %s because %s", "event_espresso"),
866
+					$model->get_this_model_name(),
867
+					implode(",", $model_object_data),
868
+					$e->getMessage()
869
+				),
870
+				__FILE__,
871
+				__FUNCTION__,
872
+				__LINE__
873
+			);
874
+		}
875
+		return $old_db_to_new_db_mapping;
876
+	}
877
+
878
+
879
+	/**
880
+	 * Given the model object data, finds the row to update and updates it
881
+	 *
882
+	 * @param string|int $id_in_csv
883
+	 * @param array      $model_object_data
884
+	 * @param EEM_Base   $model
885
+	 * @param array      $old_db_to_new_db_mapping
886
+	 * @return array updated $old_db_to_new_db_mapping
887
+	 */
888
+	protected function _update_from_data_array(
889
+		$id_in_csv,
890
+		array $model_object_data,
891
+		EEM_Base $model,
892
+		array $old_db_to_new_db_mapping
893
+	): array {
894
+		$conditions = null;
895
+		try {
896
+			// let's keep two copies of the model object data:
897
+			// one for performing an update, one for everything else
898
+			$model_object_data_for_update = $model_object_data;
899
+			if ($model->has_primary_key_field()) {
900
+				$conditions = [$model->primary_key_name() => $model_object_data[ $model->primary_key_name() ]];
901
+				// remove the primary key because we shouldn't use it for updating
902
+				unset($model_object_data_for_update[ $model->primary_key_name() ]);
903
+			} elseif ($model->get_combined_primary_key_fields() > 1) {
904
+				$conditions = [];
905
+				foreach ($model->get_combined_primary_key_fields() as $key_field) {
906
+					$conditions[ $key_field->get_name() ] = $model_object_data[ $key_field->get_name() ];
907
+				}
908
+			} else {
909
+				// this should just throw an exception, explaining that we dont have a primary key (or a combined key)
910
+				$model->primary_key_name();
911
+			}
912
+
913
+			$success = $model->update($model_object_data_for_update, [$conditions]);
914
+			if ($success) {
915
+				$this->_total_updates++;
916
+				EE_Error::add_success(
917
+					sprintf(
918
+						esc_html__("Successfully updated %s with csv data %s", "event_espresso"),
919
+						$model->get_this_model_name(),
920
+						implode(",", $model_object_data_for_update)
921
+					)
922
+				);
923
+				// we should still record the mapping even though it was an update
924
+				// because if we were going to insert something but it was going to conflict
925
+				// we would have last-minute decided to update. So we'd like to know what we updated
926
+				// and so we record what record ended up being updated using the mapping
927
+				if ($model->has_primary_key_field()) {
928
+					$new_key_for_mapping = $model_object_data[ $model->primary_key_name() ];
929
+				} else {
930
+					// no primary key just a combined key
931
+					$new_key_for_mapping = $model->get_index_primary_key_string($model_object_data);
932
+				}
933
+				$old_db_to_new_db_mapping[ $model->get_this_model_name() ][ $id_in_csv ] = $new_key_for_mapping;
934
+			} else {
935
+				$matched_items = $model->get_all([$conditions]);
936
+				if (! $matched_items) {
937
+					// no items were matched (so we shouldn't have updated)... but then we should have inserted? what the heck?
938
+					$this->_total_update_errors++;
939
+					EE_Error::add_error(
940
+						sprintf(
941
+							esc_html__(
942
+								"Could not update %s with the csv data: '%s' for an unknown reason (using WHERE conditions %s)",
943
+								"event_espresso"
944
+							),
945
+							$model->get_this_model_name(),
946
+							http_build_query($model_object_data),
947
+							http_build_query($conditions)
948
+						),
949
+						__FILE__,
950
+						__FUNCTION__,
951
+						__LINE__
952
+					);
953
+				} else {
954
+					$this->_total_updates++;
955
+					EE_Error::add_success(
956
+						sprintf(
957
+							esc_html__(
958
+								"%s with csv data '%s' was found in the database and didn't need updating because all the data is identical.",
959
+								"event_espresso"
960
+							),
961
+							$model->get_this_model_name(),
962
+							implode(",", $model_object_data)
963
+						)
964
+					);
965
+				}
966
+			}
967
+		} catch (EE_Error $e) {
968
+			$this->_total_update_errors++;
969
+			$basic_message = sprintf(
970
+				esc_html__("Could not update %s with the csv data: %s because %s", "event_espresso"),
971
+				$model->get_this_model_name(),
972
+				implode(",", $model_object_data),
973
+				$e->getMessage()
974
+			);
975
+			$debug_message = $basic_message . ' Stack trace: ' . $e->getTraceAsString();
976
+			EE_Error::add_error("$basic_message | $debug_message", __FILE__, __FUNCTION__, __LINE__);
977
+		}
978
+		return $old_db_to_new_db_mapping;
979
+	}
980
+
981
+
982
+	/**
983
+	 * Gets the number of inserts performed since importer was instantiated or reset
984
+	 *
985
+	 * @return int
986
+	 */
987
+	public function get_total_inserts(): int
988
+	{
989
+		return $this->_total_inserts;
990
+	}
991
+
992
+
993
+	/**
994
+	 *  Gets the number of insert errors since importer was instantiated or reset
995
+	 *
996
+	 * @return int
997
+	 */
998
+	public function get_total_insert_errors(): int
999
+	{
1000
+		return $this->_total_insert_errors;
1001
+	}
1002
+
1003
+
1004
+	/**
1005
+	 *  Gets the number of updates performed since importer was instantiated or reset
1006
+	 *
1007
+	 * @return int
1008
+	 */
1009
+	public function get_total_updates(): int
1010
+	{
1011
+		return $this->_total_updates;
1012
+	}
1013
+
1014
+
1015
+	/**
1016
+	 *  Gets the number of update errors since importer was instantiated or reset
1017
+	 *
1018
+	 * @return int
1019
+	 */
1020
+	public function get_total_update_errors(): int
1021
+	{
1022
+		return $this->_total_update_errors;
1023
+	}
1024 1024
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Price.class.php 1 patch
Indentation   +342 added lines, -342 removed lines patch added patch discarded remove patch
@@ -9,346 +9,346 @@
 block discarded – undo
9 9
  */
10 10
 class EE_Price extends EE_Soft_Delete_Base_Class
11 11
 {
12
-    /**
13
-     * @param array  $props_n_values    incoming values
14
-     * @param string $timezone          incoming timezone (if not set the timezone set for the website will be used.)
15
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the
16
-     *                                  date_format and the second value is the time format
17
-     * @return EE_Price
18
-     * @throws EE_Error|ReflectionException
19
-     */
20
-    public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
21
-    {
22
-        $price = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
23
-        if (! $price instanceof EE_Price) {
24
-            $price = new EE_Price($props_n_values, false, $timezone, $date_formats);
25
-        }
26
-        return $price;
27
-    }
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values  incoming values from the database
32
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
33
-     *                                the website will be used.
34
-     * @return EE_Price
35
-     * @throws EE_Error|ReflectionException
36
-     */
37
-    public static function new_instance_from_db($props_n_values = [], $timezone = null)
38
-    {
39
-        return new self($props_n_values, true, $timezone);
40
-    }
41
-
42
-
43
-    /**
44
-     * Adds some defaults if they're not specified
45
-     *
46
-     * @param array  $props_n_values
47
-     * @param bool   $bydb
48
-     * @param string $timezone
49
-     * @param array  $date_formats  incoming date_formats in an array where the first value is the
50
-     *                              date_format and the second value is the time format
51
-     * @throws EE_Error
52
-     * @throws ReflectionException
53
-     */
54
-    protected function __construct($props_n_values = [], $bydb = false, $timezone = '', $date_formats = [])
55
-    {
56
-        parent::__construct($props_n_values, $bydb, $timezone, $date_formats);
57
-    }
58
-
59
-
60
-    /**
61
-     * Set Price type ID
62
-     *
63
-     * @param int $PRT_ID
64
-     * @throws EE_Error
65
-     * @throws ReflectionException
66
-     */
67
-    public function set_type($PRT_ID = 0)
68
-    {
69
-        $this->set('PRT_ID', $PRT_ID);
70
-    }
71
-
72
-
73
-    /**
74
-     * Set Price Amount
75
-     *
76
-     * @param float $amount
77
-     * @throws EE_Error
78
-     * @throws ReflectionException
79
-     */
80
-    public function set_amount($amount = 0.00)
81
-    {
82
-        $this->set('PRC_amount', $amount);
83
-    }
84
-
85
-
86
-    /**
87
-     * Set Price Name
88
-     *
89
-     * @param string $PRC_name
90
-     * @throws EE_Error
91
-     * @throws ReflectionException
92
-     */
93
-    public function set_name($PRC_name = '')
94
-    {
95
-        $this->set('PRC_name', $PRC_name);
96
-    }
97
-
98
-
99
-    /**
100
-     * Set Price Description
101
-     *
102
-     * @param string $PRC_desc
103
-     * @throws EE_Error
104
-     * @throws ReflectionException
105
-     */
106
-    public function set_description($PRC_desc = '')
107
-    {
108
-        $this->Set('PRC_desc', $PRC_desc);
109
-    }
110
-
111
-
112
-    /**
113
-     * set is_default
114
-     *
115
-     * @param bool $PRC_is_default
116
-     * @throws EE_Error
117
-     * @throws ReflectionException
118
-     */
119
-    public function set_is_default($PRC_is_default = false)
120
-    {
121
-        $this->set('PRC_is_default', $PRC_is_default);
122
-    }
123
-
124
-
125
-    /**
126
-     * set deleted
127
-     *
128
-     * @param bool $PRC_deleted
129
-     * @throws EE_Error
130
-     * @throws ReflectionException
131
-     */
132
-    public function set_deleted($PRC_deleted = null)
133
-    {
134
-        $this->set('PRC_deleted', $PRC_deleted);
135
-    }
136
-
137
-
138
-    /**
139
-     * get Price type
140
-     *
141
-     * @return        int
142
-     * @throws EE_Error
143
-     * @throws ReflectionException
144
-     */
145
-    public function type()
146
-    {
147
-        return $this->get('PRT_ID');
148
-    }
149
-
150
-
151
-    /**
152
-     * get Price Amount
153
-     *
154
-     * @return float
155
-     * @throws EE_Error
156
-     * @throws ReflectionException
157
-     */
158
-    public function amount()
159
-    {
160
-        return $this->get('PRC_amount');
161
-    }
162
-
163
-
164
-    /**
165
-     * get Price Name
166
-     *
167
-     * @return        string
168
-     * @throws EE_Error
169
-     * @throws ReflectionException
170
-     */
171
-    public function name()
172
-    {
173
-        return $this->get('PRC_name');
174
-    }
175
-
176
-
177
-    /**
178
-     * get Price description
179
-     *
180
-     * @return        string
181
-     * @throws EE_Error
182
-     * @throws ReflectionException
183
-     */
184
-    public function desc()
185
-    {
186
-        return $this->get('PRC_desc');
187
-    }
188
-
189
-
190
-    /**
191
-     * get overrides
192
-     *
193
-     * @return        int
194
-     * @throws EE_Error
195
-     * @throws ReflectionException
196
-     */
197
-    public function overrides()
198
-    {
199
-        return $this->get('PRC_overrides');
200
-    }
201
-
202
-
203
-    /**
204
-     * get order
205
-     *
206
-     * @return        int
207
-     * @throws EE_Error
208
-     * @throws ReflectionException
209
-     */
210
-    public function order()
211
-    {
212
-        return $this->get('PRC_order');
213
-    }
214
-
215
-
216
-    /**
217
-     * get the author of the price
218
-     *
219
-     * @return int
220
-     * @throws EE_Error
221
-     * @throws ReflectionException
222
-     * @since 4.5.0
223
-     *
224
-     */
225
-    public function wp_user()
226
-    {
227
-        return $this->get('PRC_wp_user');
228
-    }
229
-
230
-
231
-    /**
232
-     * get is_default
233
-     *
234
-     * @return        bool
235
-     * @throws EE_Error
236
-     * @throws ReflectionException
237
-     */
238
-    public function is_default()
239
-    {
240
-        return $this->get('PRC_is_default');
241
-    }
242
-
243
-
244
-    /**
245
-     * get deleted
246
-     *
247
-     * @return        bool
248
-     * @throws EE_Error
249
-     * @throws ReflectionException
250
-     */
251
-    public function deleted()
252
-    {
253
-        return $this->get('PRC_deleted');
254
-    }
255
-
256
-
257
-    /**
258
-     * @return bool
259
-     * @throws EE_Error
260
-     * @throws ReflectionException
261
-     */
262
-    public function parent()
263
-    {
264
-        return $this->get('PRC_parent');
265
-    }
266
-
267
-
268
-    // some helper methods for getting info on the price_type for this price
269
-
270
-
271
-    /**
272
-     * return whether the price is a base price or not
273
-     *
274
-     * @return boolean
275
-     * @throws EE_Error
276
-     * @throws ReflectionException
277
-     */
278
-    public function is_base_price()
279
-    {
280
-        $price_type = $this->type_obj();
281
-        return $price_type->base_type() === 1;
282
-    }
283
-
284
-
285
-    /**
286
-     *
287
-     * @return EE_Base_Class|EE_Price_Type
288
-     * @throws EE_Error
289
-     * @throws ReflectionException
290
-     */
291
-    public function type_obj()
292
-    {
293
-        return $this->get_first_related('Price_Type');
294
-    }
295
-
296
-
297
-    /**
298
-     * Simply indicates whether this price increases or decreases the total
299
-     *
300
-     * @return boolean true = discount, otherwise adds to the total
301
-     * @throws EE_Error
302
-     * @throws ReflectionException
303
-     */
304
-    public function is_discount()
305
-    {
306
-        $price_type = $this->type_obj();
307
-        return $price_type->is_discount();
308
-    }
309
-
310
-
311
-    /**
312
-     * whether the price is a percentage or not
313
-     *
314
-     * @return boolean
315
-     * @throws EE_Error
316
-     * @throws ReflectionException
317
-     */
318
-    public function is_percent()
319
-    {
320
-        $price_type = $this->type_obj();
321
-        return $price_type->get('PRT_is_percent');
322
-    }
323
-
324
-
325
-    /**
326
-     * return pretty price dependant on whether its a dollar or percent.
327
-     *
328
-     * @return string
329
-     * @throws EE_Error
330
-     * @throws ReflectionException
331
-     * @since 4.4.0
332
-     *
333
-     */
334
-    public function pretty_price()
335
-    {
336
-        return $this->is_percent()
337
-            ? apply_filters(
338
-                'FHEE__format_percentage_value',
339
-                $this->get_pretty('PRC_amount', 'localized_float')
340
-            )
341
-            : $this->get_pretty('PRC_amount', 'localized_currency');
342
-    }
343
-
344
-
345
-    /**
346
-     * @return mixed
347
-     * @throws EE_Error
348
-     * @throws ReflectionException
349
-     */
350
-    public function get_price_without_currency_symbol()
351
-    {
352
-        return $this->get_pretty('PRC_amount', 'localized_float');
353
-    }
12
+	/**
13
+	 * @param array  $props_n_values    incoming values
14
+	 * @param string $timezone          incoming timezone (if not set the timezone set for the website will be used.)
15
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the
16
+	 *                                  date_format and the second value is the time format
17
+	 * @return EE_Price
18
+	 * @throws EE_Error|ReflectionException
19
+	 */
20
+	public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
21
+	{
22
+		$price = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
23
+		if (! $price instanceof EE_Price) {
24
+			$price = new EE_Price($props_n_values, false, $timezone, $date_formats);
25
+		}
26
+		return $price;
27
+	}
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values  incoming values from the database
32
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
33
+	 *                                the website will be used.
34
+	 * @return EE_Price
35
+	 * @throws EE_Error|ReflectionException
36
+	 */
37
+	public static function new_instance_from_db($props_n_values = [], $timezone = null)
38
+	{
39
+		return new self($props_n_values, true, $timezone);
40
+	}
41
+
42
+
43
+	/**
44
+	 * Adds some defaults if they're not specified
45
+	 *
46
+	 * @param array  $props_n_values
47
+	 * @param bool   $bydb
48
+	 * @param string $timezone
49
+	 * @param array  $date_formats  incoming date_formats in an array where the first value is the
50
+	 *                              date_format and the second value is the time format
51
+	 * @throws EE_Error
52
+	 * @throws ReflectionException
53
+	 */
54
+	protected function __construct($props_n_values = [], $bydb = false, $timezone = '', $date_formats = [])
55
+	{
56
+		parent::__construct($props_n_values, $bydb, $timezone, $date_formats);
57
+	}
58
+
59
+
60
+	/**
61
+	 * Set Price type ID
62
+	 *
63
+	 * @param int $PRT_ID
64
+	 * @throws EE_Error
65
+	 * @throws ReflectionException
66
+	 */
67
+	public function set_type($PRT_ID = 0)
68
+	{
69
+		$this->set('PRT_ID', $PRT_ID);
70
+	}
71
+
72
+
73
+	/**
74
+	 * Set Price Amount
75
+	 *
76
+	 * @param float $amount
77
+	 * @throws EE_Error
78
+	 * @throws ReflectionException
79
+	 */
80
+	public function set_amount($amount = 0.00)
81
+	{
82
+		$this->set('PRC_amount', $amount);
83
+	}
84
+
85
+
86
+	/**
87
+	 * Set Price Name
88
+	 *
89
+	 * @param string $PRC_name
90
+	 * @throws EE_Error
91
+	 * @throws ReflectionException
92
+	 */
93
+	public function set_name($PRC_name = '')
94
+	{
95
+		$this->set('PRC_name', $PRC_name);
96
+	}
97
+
98
+
99
+	/**
100
+	 * Set Price Description
101
+	 *
102
+	 * @param string $PRC_desc
103
+	 * @throws EE_Error
104
+	 * @throws ReflectionException
105
+	 */
106
+	public function set_description($PRC_desc = '')
107
+	{
108
+		$this->Set('PRC_desc', $PRC_desc);
109
+	}
110
+
111
+
112
+	/**
113
+	 * set is_default
114
+	 *
115
+	 * @param bool $PRC_is_default
116
+	 * @throws EE_Error
117
+	 * @throws ReflectionException
118
+	 */
119
+	public function set_is_default($PRC_is_default = false)
120
+	{
121
+		$this->set('PRC_is_default', $PRC_is_default);
122
+	}
123
+
124
+
125
+	/**
126
+	 * set deleted
127
+	 *
128
+	 * @param bool $PRC_deleted
129
+	 * @throws EE_Error
130
+	 * @throws ReflectionException
131
+	 */
132
+	public function set_deleted($PRC_deleted = null)
133
+	{
134
+		$this->set('PRC_deleted', $PRC_deleted);
135
+	}
136
+
137
+
138
+	/**
139
+	 * get Price type
140
+	 *
141
+	 * @return        int
142
+	 * @throws EE_Error
143
+	 * @throws ReflectionException
144
+	 */
145
+	public function type()
146
+	{
147
+		return $this->get('PRT_ID');
148
+	}
149
+
150
+
151
+	/**
152
+	 * get Price Amount
153
+	 *
154
+	 * @return float
155
+	 * @throws EE_Error
156
+	 * @throws ReflectionException
157
+	 */
158
+	public function amount()
159
+	{
160
+		return $this->get('PRC_amount');
161
+	}
162
+
163
+
164
+	/**
165
+	 * get Price Name
166
+	 *
167
+	 * @return        string
168
+	 * @throws EE_Error
169
+	 * @throws ReflectionException
170
+	 */
171
+	public function name()
172
+	{
173
+		return $this->get('PRC_name');
174
+	}
175
+
176
+
177
+	/**
178
+	 * get Price description
179
+	 *
180
+	 * @return        string
181
+	 * @throws EE_Error
182
+	 * @throws ReflectionException
183
+	 */
184
+	public function desc()
185
+	{
186
+		return $this->get('PRC_desc');
187
+	}
188
+
189
+
190
+	/**
191
+	 * get overrides
192
+	 *
193
+	 * @return        int
194
+	 * @throws EE_Error
195
+	 * @throws ReflectionException
196
+	 */
197
+	public function overrides()
198
+	{
199
+		return $this->get('PRC_overrides');
200
+	}
201
+
202
+
203
+	/**
204
+	 * get order
205
+	 *
206
+	 * @return        int
207
+	 * @throws EE_Error
208
+	 * @throws ReflectionException
209
+	 */
210
+	public function order()
211
+	{
212
+		return $this->get('PRC_order');
213
+	}
214
+
215
+
216
+	/**
217
+	 * get the author of the price
218
+	 *
219
+	 * @return int
220
+	 * @throws EE_Error
221
+	 * @throws ReflectionException
222
+	 * @since 4.5.0
223
+	 *
224
+	 */
225
+	public function wp_user()
226
+	{
227
+		return $this->get('PRC_wp_user');
228
+	}
229
+
230
+
231
+	/**
232
+	 * get is_default
233
+	 *
234
+	 * @return        bool
235
+	 * @throws EE_Error
236
+	 * @throws ReflectionException
237
+	 */
238
+	public function is_default()
239
+	{
240
+		return $this->get('PRC_is_default');
241
+	}
242
+
243
+
244
+	/**
245
+	 * get deleted
246
+	 *
247
+	 * @return        bool
248
+	 * @throws EE_Error
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function deleted()
252
+	{
253
+		return $this->get('PRC_deleted');
254
+	}
255
+
256
+
257
+	/**
258
+	 * @return bool
259
+	 * @throws EE_Error
260
+	 * @throws ReflectionException
261
+	 */
262
+	public function parent()
263
+	{
264
+		return $this->get('PRC_parent');
265
+	}
266
+
267
+
268
+	// some helper methods for getting info on the price_type for this price
269
+
270
+
271
+	/**
272
+	 * return whether the price is a base price or not
273
+	 *
274
+	 * @return boolean
275
+	 * @throws EE_Error
276
+	 * @throws ReflectionException
277
+	 */
278
+	public function is_base_price()
279
+	{
280
+		$price_type = $this->type_obj();
281
+		return $price_type->base_type() === 1;
282
+	}
283
+
284
+
285
+	/**
286
+	 *
287
+	 * @return EE_Base_Class|EE_Price_Type
288
+	 * @throws EE_Error
289
+	 * @throws ReflectionException
290
+	 */
291
+	public function type_obj()
292
+	{
293
+		return $this->get_first_related('Price_Type');
294
+	}
295
+
296
+
297
+	/**
298
+	 * Simply indicates whether this price increases or decreases the total
299
+	 *
300
+	 * @return boolean true = discount, otherwise adds to the total
301
+	 * @throws EE_Error
302
+	 * @throws ReflectionException
303
+	 */
304
+	public function is_discount()
305
+	{
306
+		$price_type = $this->type_obj();
307
+		return $price_type->is_discount();
308
+	}
309
+
310
+
311
+	/**
312
+	 * whether the price is a percentage or not
313
+	 *
314
+	 * @return boolean
315
+	 * @throws EE_Error
316
+	 * @throws ReflectionException
317
+	 */
318
+	public function is_percent()
319
+	{
320
+		$price_type = $this->type_obj();
321
+		return $price_type->get('PRT_is_percent');
322
+	}
323
+
324
+
325
+	/**
326
+	 * return pretty price dependant on whether its a dollar or percent.
327
+	 *
328
+	 * @return string
329
+	 * @throws EE_Error
330
+	 * @throws ReflectionException
331
+	 * @since 4.4.0
332
+	 *
333
+	 */
334
+	public function pretty_price()
335
+	{
336
+		return $this->is_percent()
337
+			? apply_filters(
338
+				'FHEE__format_percentage_value',
339
+				$this->get_pretty('PRC_amount', 'localized_float')
340
+			)
341
+			: $this->get_pretty('PRC_amount', 'localized_currency');
342
+	}
343
+
344
+
345
+	/**
346
+	 * @return mixed
347
+	 * @throws EE_Error
348
+	 * @throws ReflectionException
349
+	 */
350
+	public function get_price_without_currency_symbol()
351
+	{
352
+		return $this->get_pretty('PRC_amount', 'localized_float');
353
+	}
354 354
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Template.helper.php 2 patches
Indentation   +1037 added lines, -1037 removed lines patch added patch discarded remove patch
@@ -9,36 +9,36 @@  discard block
 block discarded – undo
9 9
 use EventEspresso\core\services\request\sanitizers\AllowedTags;
10 10
 
11 11
 if (! function_exists('espresso_get_template_part')) {
12
-    /**
13
-     * espresso_get_template_part
14
-     * basically a copy of the WordPress get_template_part() function but uses EEH_Template::locate_template() instead, and doesn't add base versions of files
15
-     * so not a very useful function at all except that it adds familiarity PLUS filtering based off of the entire template part name
16
-     *
17
-     * @param string $slug The slug name for the generic template.
18
-     * @param string $name The name of the specialised template.
19
-     */
20
-    function espresso_get_template_part($slug = null, $name = null)
21
-    {
22
-        EEH_Template::get_template_part($slug, $name);
23
-    }
12
+	/**
13
+	 * espresso_get_template_part
14
+	 * basically a copy of the WordPress get_template_part() function but uses EEH_Template::locate_template() instead, and doesn't add base versions of files
15
+	 * so not a very useful function at all except that it adds familiarity PLUS filtering based off of the entire template part name
16
+	 *
17
+	 * @param string $slug The slug name for the generic template.
18
+	 * @param string $name The name of the specialised template.
19
+	 */
20
+	function espresso_get_template_part($slug = null, $name = null)
21
+	{
22
+		EEH_Template::get_template_part($slug, $name);
23
+	}
24 24
 }
25 25
 
26 26
 
27 27
 if (! function_exists('espresso_get_object_css_class')) {
28
-    /**
29
-     * espresso_get_object_css_class - attempts to generate a css class based on the type of EE object passed
30
-     *
31
-     * @param EE_Base_Class $object the EE object the css class is being generated for
32
-     * @param string        $prefix added to the beginning of the generated class
33
-     * @param string        $suffix added to the end of the generated class
34
-     * @return string
35
-     * @throws EE_Error
36
-     * @throws ReflectionException
37
-     */
38
-    function espresso_get_object_css_class($object = null, $prefix = '', $suffix = '')
39
-    {
40
-        return EEH_Template::get_object_css_class($object, $prefix, $suffix);
41
-    }
28
+	/**
29
+	 * espresso_get_object_css_class - attempts to generate a css class based on the type of EE object passed
30
+	 *
31
+	 * @param EE_Base_Class $object the EE object the css class is being generated for
32
+	 * @param string        $prefix added to the beginning of the generated class
33
+	 * @param string        $suffix added to the end of the generated class
34
+	 * @return string
35
+	 * @throws EE_Error
36
+	 * @throws ReflectionException
37
+	 */
38
+	function espresso_get_object_css_class($object = null, $prefix = '', $suffix = '')
39
+	{
40
+		return EEH_Template::get_object_css_class($object, $prefix, $suffix);
41
+	}
42 42
 }
43 43
 
44 44
 /**
@@ -51,750 +51,750 @@  discard block
 block discarded – undo
51 51
  */
52 52
 class EEH_Template
53 53
 {
54
-    /**
55
-     * @var EE_Currency_Config[]
56
-     */
57
-    private static $currency_config;
58
-
59
-    /**
60
-     * @var CurrencyFormatter
61
-     */
62
-    private static $currency_formatter;
63
-
64
-    /**
65
-     * @var array
66
-     */
67
-    private static $_espresso_themes = [];
68
-
69
-
70
-    /**
71
-     * get legacy currency config object for locale
72
-     *
73
-     * @param Locale $currency_locale
74
-     * @return EE_Currency_Config
75
-     * @throws EE_Error
76
-     * @throws ReflectionException
77
-     * @since $VID:$
78
-     */
79
-    private static function getCurrencyConfigForCountryISO(Locale $currency_locale)
80
-    {
81
-        $currencyISO = $currency_locale->currencyIsoCode();
82
-        if (
83
-            ! isset(EEH_Template::$currency_config[ $currencyISO ])
84
-            || ! EEH_Template::$currency_config[ $currencyISO ] instanceof EE_Currency_Config
85
-        ) {
86
-            $CNT_ISO = EEM_Country::instance()->getCountryISOForCurrencyISO($currencyISO);
87
-            $currency_config = new EE_Currency_Config($CNT_ISO);
88
-            EEH_Template::$currency_config[ $currencyISO ] = $currency_config;
89
-        }
90
-        return EEH_Template::$currency_config[ $currencyISO ];
91
-    }
92
-
93
-
94
-    /**
95
-     * @return CurrencyFormatter
96
-     * @since   $VID:$
97
-     */
98
-    private static function getCurrencyFormatter()
99
-    {
100
-        if (! EEH_Template::$currency_formatter instanceof CurrencyFormatter) {
101
-            EEH_Template::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
102
-        }
103
-        return EEH_Template::$currency_formatter;
104
-    }
105
-
106
-
107
-    /**
108
-     *    is_espresso_theme - returns TRUE or FALSE on whether the currently active WP theme is an espresso theme
109
-     *
110
-     * @return boolean
111
-     */
112
-    public static function is_espresso_theme()
113
-    {
114
-        return wp_get_theme()->get('TextDomain') === 'event_espresso';
115
-    }
116
-
117
-
118
-    /**
119
-     *    load_espresso_theme_functions - if current theme is an espresso theme, or uses ee theme template parts, then
120
-     *    load its functions.php file ( if not already loaded )
121
-     *
122
-     * @return void
123
-     */
124
-    public static function load_espresso_theme_functions()
125
-    {
126
-        if (
127
-            ! defined('EE_THEME_FUNCTIONS_LOADED')
128
-            && is_readable(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php')
129
-        ) {
130
-            require_once(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php');
131
-        }
132
-    }
133
-
134
-
135
-    /**
136
-     *    get_espresso_themes - returns an array of Espresso Child themes located in the /templates/ directory
137
-     *
138
-     * @return array
139
-     */
140
-    public static function get_espresso_themes()
141
-    {
142
-        if (empty(EEH_Template::$_espresso_themes)) {
143
-            $espresso_themes = glob(EE_PUBLIC . '*', GLOB_ONLYDIR);
144
-            if (empty($espresso_themes)) {
145
-                return [];
146
-            }
147
-            if (($key = array_search('global_assets', $espresso_themes, true)) !== false) {
148
-                unset($espresso_themes[ $key ]);
149
-            }
150
-            EEH_Template::$_espresso_themes = [];
151
-            foreach ($espresso_themes as $espresso_theme) {
152
-                EEH_Template::$_espresso_themes[ basename($espresso_theme) ] = $espresso_theme;
153
-            }
154
-        }
155
-        return EEH_Template::$_espresso_themes;
156
-    }
157
-
158
-
159
-    /**
160
-     * EEH_Template::get_template_part
161
-     * basically a copy of the WordPress get_template_part() function but uses EEH_Template::locate_template() instead,
162
-     * and doesn't add base versions of files so not a very useful function at all except that it adds familiarity PLUS
163
-     * filtering based off of the entire template part name
164
-     *
165
-     * @param string $slug The slug name for the generic template.
166
-     * @param string $name The name of the specialised template.
167
-     * @param array  $template_args
168
-     * @param bool   $return_string
169
-     * @return void  echos the html output for the template
170
-     */
171
-    public static function get_template_part(
172
-        $slug = null,
173
-        $name = null,
174
-        $template_args = [],
175
-        $return_string = false
176
-    ) {
177
-        do_action("get_template_part_{$slug}-{$name}", $slug, $name);
178
-        $templates = [];
179
-        $name      = (string) $name;
180
-        if ($name !== '') {
181
-            $templates[] = "{$slug}-{$name}.php";
182
-        }
183
-        // allow template parts to be turned off via something like:
184
-        // add_filter( 'FHEE__content_espresso_events_tickets_template__display_datetimes', '__return_false' );
185
-        if (apply_filters("FHEE__EEH_Template__get_template_part__display__{$slug}_{$name}", true)) {
186
-            return EEH_Template::locate_template($templates, $template_args, true, $return_string);
187
-        }
188
-        return '';
189
-    }
190
-
191
-
192
-    /**
193
-     *    locate_template
194
-     *    locate a template file by looking in the following places, in the following order:
195
-     *        <server path up to>/wp-content/themes/<current active WordPress theme>/
196
-     *        <assumed full absolute server path>
197
-     *        <server path up to>/wp-content/uploads/espresso/templates/<current EE theme>/
198
-     *        <server path up to>/wp-content/uploads/espresso/templates/
199
-     *        <server path up to>/wp-content/plugins/<EE4 folder>/public/<current EE theme>/
200
-     *        <server path up to>/wp-content/plugins/<EE4 folder>/core/templates/<current EE theme>/
201
-     *        <server path up to>/wp-content/plugins/<EE4 folder>/
202
-     *    as soon as the template is found in one of these locations, it will be returned or loaded
203
-     *        Example:
204
-     *          You are using the WordPress Twenty Sixteen theme,
205
-     *        and you want to customize the "some-event.template.php" template,
206
-     *          which is located in the "/relative/path/to/" folder relative to the main EE plugin folder.
207
-     *          Assuming WP is installed on your server in the "/home/public_html/" folder,
208
-     *        EEH_Template::locate_template() will look at the following paths in order until the template is found:
209
-     *        /home/public_html/wp-content/themes/twentysixteen/some-event.template.php
210
-     *        /relative/path/to/some-event.template.php
211
-     *        /home/public_html/wp-content/uploads/espresso/templates/Espresso_Arabica_2014/relative/path/to/some-event.template.php
212
-     *        /home/public_html/wp-content/uploads/espresso/templates/relative/path/to/some-event.template.php
213
-     *        /home/public_html/wp-content/plugins/event-espresso-core-reg/public/Espresso_Arabica_2014/relative/path/to/some-event.template.php
214
-     *        /home/public_html/wp-content/plugins/event-espresso-core-reg/core/templates/Espresso_Arabica_2014/relative/path/to/some-event.template.php
215
-     *        /home/public_html/wp-content/plugins/event-espresso-core-reg/relative/path/to/some-event.template.php
216
-     *          Had you passed an absolute path to your template that was in some other location,
217
-     *        ie: "/absolute/path/to/some-event.template.php"
218
-     *          then the search would have been :
219
-     *        /home/public_html/wp-content/themes/twentysixteen/some-event.template.php
220
-     *        /absolute/path/to/some-event.template.php
221
-     *          and stopped there upon finding it in the second location
222
-     *
223
-     * @param array|string $templates       array of template file names including extension (or just a single string)
224
-     * @param array        $template_args   an array of arguments to be extracted for use in the template
225
-     * @param boolean      $load            whether to pass the located template path on to the
226
-     *                                      EEH_Template::display_template() method or simply return it
227
-     * @param boolean      $return_string   whether to send output immediately to screen, or capture and return as a
228
-     *                                      string
229
-     * @param boolean      $check_if_custom If TRUE, this flags this method to return boolean for whether this will
230
-     *                                      generate a custom template or not. Used in places where you don't actually
231
-     *                                      load the template, you just want to know if there's a custom version of it.
232
-     * @return mixed
233
-     * @throws DomainException
234
-     * @throws InvalidArgumentException
235
-     * @throws InvalidDataTypeException
236
-     * @throws InvalidInterfaceException
237
-     */
238
-    public static function locate_template(
239
-        $templates = [],
240
-        $template_args = [],
241
-        $load = true,
242
-        $return_string = true,
243
-        $check_if_custom = false
244
-    ) {
245
-        // first use WP locate_template to check for template in the current theme folder
246
-        $template_path = locate_template($templates);
247
-
248
-        if ($check_if_custom && ! empty($template_path)) {
249
-            return true;
250
-        }
251
-
252
-        // not in the theme
253
-        if (empty($template_path)) {
254
-            // not even a template to look for ?
255
-            if (empty($templates)) {
256
-                $loader = LoaderFactory::getLoader();
257
-                /** @var RequestInterface $request */
258
-                $request = $loader->getShared(RequestInterface::class);
259
-                // get post_type
260
-                $post_type = $request->getRequestParam('post_type');
261
-                /** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
262
-                $custom_post_types = $loader->getShared(
263
-                    'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
264
-                );
265
-                // get array of EE Custom Post Types
266
-                $EE_CPTs = $custom_post_types->getDefinitions();
267
-                // build template name based on request
268
-                if (isset($EE_CPTs[ $post_type ])) {
269
-                    $archive_or_single = is_archive() ? 'archive' : '';
270
-                    $archive_or_single = is_single() ? 'single' : $archive_or_single;
271
-                    $templates         = $archive_or_single . '-' . $post_type . '.php';
272
-                }
273
-            }
274
-            // currently active EE template theme
275
-            $current_theme = EE_Config::get_current_theme();
276
-
277
-            // array of paths to folders that may contain templates
278
-            $template_folder_paths = [
279
-                // first check the /wp-content/uploads/espresso/templates/(current EE theme)/  folder for an EE theme template file
280
-                EVENT_ESPRESSO_TEMPLATE_DIR . $current_theme,
281
-                // then in the root of the /wp-content/uploads/espresso/templates/ folder
282
-                EVENT_ESPRESSO_TEMPLATE_DIR,
283
-            ];
284
-
285
-            // add core plugin folders for checking only if we're not $check_if_custom
286
-            if (! $check_if_custom) {
287
-                $core_paths            = [
288
-                    // in the  /wp-content/plugins/(EE4 folder)/public/(current EE theme)/ folder within the plugin
289
-                    EE_PUBLIC . $current_theme,
290
-                    // in the  /wp-content/plugins/(EE4 folder)/core/templates/(current EE theme)/ folder within the plugin
291
-                    EE_TEMPLATES . $current_theme,
292
-                    // or maybe relative from the plugin root: /wp-content/plugins/(EE4 folder)/
293
-                    EE_PLUGIN_DIR_PATH,
294
-                ];
295
-                $template_folder_paths = array_merge($template_folder_paths, $core_paths);
296
-            }
297
-
298
-            // now filter that array
299
-            $template_folder_paths = apply_filters(
300
-                'FHEE__EEH_Template__locate_template__template_folder_paths',
301
-                $template_folder_paths
302
-            );
303
-            $templates = is_array($templates) ? $templates : [$templates];
304
-            $template_folder_paths = is_array($template_folder_paths)
305
-                ? $template_folder_paths
306
-                : [$template_folder_paths];
307
-            // array to hold all possible template paths
308
-            $full_template_paths = [];
309
-            $file_name           = '';
310
-            // loop through $templates
311
-            foreach ($templates as $template) {
312
-                // normalize directory separators
313
-                $template                      = EEH_File::standardise_directory_separators($template);
314
-                $file_name                     = basename($template);
315
-                $template_path_minus_file_name = substr($template, 0, (strlen($file_name) * -1));
316
-                // while looping through all template folder paths
317
-                foreach ($template_folder_paths as $template_folder_path) {
318
-                    // normalize directory separators
319
-                    $template_folder_path = EEH_File::standardise_directory_separators($template_folder_path);
320
-                    // determine if any common base path exists between the two paths
321
-                    $common_base_path = EEH_Template::_find_common_base_path(
322
-                        [$template_folder_path, $template_path_minus_file_name]
323
-                    );
324
-                    if ($common_base_path !== '') {
325
-                        // both paths have a common base, so just tack the filename onto our search path
326
-                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $file_name;
327
-                    } else {
328
-                        // no common base path, so let's just concatenate
329
-                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $template;
330
-                    }
331
-                    // build up our template locations array by adding our resolved paths
332
-                    $full_template_paths[] = $resolved_path;
333
-                }
334
-                // if $template is an absolute path, then we'll tack it onto the start of our array so that it gets searched first
335
-                array_unshift($full_template_paths, $template);
336
-                // path to the directory of the current theme: /wp-content/themes/(current WP theme)/
337
-                array_unshift($full_template_paths, get_stylesheet_directory() . '/' . $file_name);
338
-            }
339
-            // filter final array of full template paths
340
-            $full_template_paths = apply_filters(
341
-                'FHEE__EEH_Template__locate_template__full_template_paths',
342
-                $full_template_paths,
343
-                $file_name
344
-            );
345
-            // now loop through our final array of template location paths and check each location
346
-            foreach ((array) $full_template_paths as $full_template_path) {
347
-                if (is_readable($full_template_path)) {
348
-                    $template_path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $full_template_path);
349
-                    break;
350
-                }
351
-            }
352
-        }
353
-
354
-        // hook that can be used to display the full template path that will be used
355
-        do_action('AHEE__EEH_Template__locate_template__full_template_path', $template_path);
356
-
357
-        // if we got it and you want to see it...
358
-        if ($template_path && $load && ! $check_if_custom) {
359
-            if ($return_string) {
360
-                return EEH_Template::display_template($template_path, $template_args, true);
361
-            }
362
-            EEH_Template::display_template($template_path, $template_args);
363
-        }
364
-        return $check_if_custom && ! empty($template_path) ? true : $template_path;
365
-    }
366
-
367
-
368
-    /**
369
-     * _find_common_base_path
370
-     * given two paths, this determines if there is a common base path between the two
371
-     *
372
-     * @param array $paths
373
-     * @return string
374
-     */
375
-    protected static function _find_common_base_path($paths)
376
-    {
377
-        $last_offset      = 0;
378
-        $common_base_path = '';
379
-        while (($index = strpos($paths[0], '/', $last_offset)) !== false) {
380
-            $dir_length = $index - $last_offset + 1;
381
-            $directory  = substr($paths[0], $last_offset, $dir_length);
382
-            foreach ($paths as $path) {
383
-                if (substr($path, $last_offset, $dir_length) !== $directory) {
384
-                    return $common_base_path;
385
-                }
386
-            }
387
-            $common_base_path .= $directory;
388
-            $last_offset      = $index + 1;
389
-        }
390
-        return substr($common_base_path, 0, -1);
391
-    }
392
-
393
-
394
-    /**
395
-     * load and display a template
396
-     *
397
-     * @param bool|string $template_path    server path to the file to be loaded, including file name and extension
398
-     * @param array       $template_args    an array of arguments to be extracted for use in the template
399
-     * @param boolean     $return_string    whether to send output immediately to screen, or capture and return as a
400
-     *                                      string
401
-     * @param bool        $throw_exceptions if set to true, will throw an exception if the template is either
402
-     *                                      not found or is not readable
403
-     * @return string
404
-     * @throws DomainException
405
-     */
406
-    public static function display_template(
407
-        $template_path = false,
408
-        $template_args = [],
409
-        $return_string = false,
410
-        $throw_exceptions = false
411
-    ) {
412
-        /**
413
-         * These two filters are intended for last minute changes to templates being loaded and/or template arg
414
-         * modifications.  NOTE... modifying these things can cause breakage as most templates running through
415
-         * the display_template method are templates we DON'T want modified (usually because of js
416
-         * dependencies etc).  So unless you know what you are doing, do NOT filter templates or template args
417
-         * using this.
418
-         *
419
-         * @since 4.6.0
420
-         */
421
-        $template_path = (string) apply_filters('FHEE__EEH_Template__display_template__template_path', $template_path);
422
-        $template_args = (array) apply_filters('FHEE__EEH_Template__display_template__template_args', $template_args);
423
-
424
-        // you gimme nuttin - YOU GET NUTTIN !!
425
-        if (! $template_path || ! is_readable($template_path)) {
426
-            // ignore whether template is accessible ?
427
-            if ($throw_exceptions) {
428
-                throw new DomainException(
429
-                    esc_html__('Invalid, unreadable, or missing file.', 'event_espresso')
430
-                );
431
-            }
432
-            return '';
433
-        }
434
-        // if $template_args are not in an array, then make it so
435
-        if (! is_array($template_args) && ! is_object($template_args)) {
436
-            $template_args = [$template_args];
437
-        }
438
-        extract($template_args, EXTR_SKIP);
439
-        // ignore whether template is accessible ?
440
-        if ($throw_exceptions && ! is_readable($template_path)) {
441
-            throw new DomainException(
442
-                esc_html__(
443
-                    'Invalid, unreadable, or missing file.',
444
-                    'event_espresso'
445
-                )
446
-            );
447
-        }
448
-
449
-
450
-        if ($return_string) {
451
-            // because we want to return a string, we are going to capture the output
452
-            ob_start();
453
-            include($template_path);
454
-            return ob_get_clean();
455
-        }
456
-        include($template_path);
457
-        return '';
458
-    }
459
-
460
-
461
-    /**
462
-     * get_object_css_class - attempts to generate a css class based on the type of EE object passed
463
-     *
464
-     * @param EE_Base_Class $object the EE object the css class is being generated for
465
-     * @param string        $prefix added to the beginning of the generated class
466
-     * @param string        $suffix added to the end of the generated class
467
-     * @return string
468
-     * @throws EE_Error
469
-     * @throws ReflectionException
470
-     */
471
-    public static function get_object_css_class($object = null, $prefix = '', $suffix = '')
472
-    {
473
-        // in the beginning...
474
-        $prefix = ! empty($prefix) ? rtrim($prefix, '-') . '-' : '';
475
-        // da muddle
476
-        $class = '';
477
-        // the end
478
-        $suffix = ! empty($suffix) ? '-' . ltrim($suffix, '-') : '';
479
-        // is the passed object an EE object ?
480
-        if ($object instanceof EE_Base_Class) {
481
-            // grab the exact type of object
482
-            $obj_class = get_class($object);
483
-            // depending on the type of object...
484
-            $class = strtolower(str_replace('_', '-', $obj_class));
485
-            $class .= method_exists($obj_class, 'name') ? '-' . sanitize_title($object->name()) : '';
486
-        }
487
-        return $prefix . $class . $suffix;
488
-    }
489
-
490
-
491
-    /**
492
-     * EEH_Template::format_currency
493
-     * This helper takes a raw float value and formats it according to the default config country currency settings, or
494
-     * the country currency settings from the supplied country ISO code
495
-     *
496
-     * @param float   $amount                    raw money value
497
-     * @param boolean $return_raw                whether to return the formatted float value only with no currency sign
498
-     *                                           or code
499
-     * @param boolean $display_code              whether to display the country code (USD). Default = TRUE
500
-     * @param string  $CNT_ISO                   2 letter ISO code for a country
501
-     * @param string  $cur_code_span_class
502
-     * @param boolean $allow_fractional_subunits whether to allow displaying partial penny amounts
503
-     * @return string        the html output for the formatted money value
504
-     * @throws EE_Error
505
-     * @throws ReflectionException
506
-     * @deprecated $VID:$ plz use CurrencyFormatter::formatForLocale()
507
-     */
508
-    public static function format_currency(
509
-        $amount = null,
510
-        $return_raw = false,
511
-        $display_code = true,
512
-        $CNT_ISO = '',
513
-        $cur_code_span_class = 'currency-code', // not used anywhere
514
-        $allow_fractional_subunits = false
515
-    ) {
516
-        // ensure amount was received
517
-        if ($amount === null) {
518
-            $msg = esc_html__('In order to format currency, an amount needs to be passed.', 'event_espresso');
519
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
520
-            return '';
521
-        }
522
-        // ensure amount is float
523
-        $amount  = (float) apply_filters('FHEE__EEH_Template__format_currency__raw_amount', (float) $amount);
524
-        $CNT_ISO = apply_filters('FHEE__EEH_Template__format_currency__CNT_ISO', $CNT_ISO, $amount);
525
-
526
-        // filter raw amount (allows 0.00 to be changed to "free" for example)
527
-        $amount_formatted = apply_filters('FHEE__EEH_Template__format_currency__amount', $amount, $return_raw);
528
-        // still a number or was amount converted to a string like "free" ?
529
-        if (! is_float($amount_formatted)) {
530
-            return esc_html($amount_formatted);
531
-        }
532
-
533
-        try {
534
-            $currency_formatter = EEH_Template::getCurrencyFormatter();
535
-            $currency_ISO = '';
536
-            if ($CNT_ISO !== '') {
537
-                $currency_config = EEH_Money::get_currency_config($CNT_ISO);
538
-                $currency_ISO = $currency_config->code;
539
-            }
540
-            $currency_locale = $currency_formatter->getLocaleForCurrencyISO($currency_ISO, true);
541
-
542
-            // filter to allow global setting of display_code
543
-            $display_code = apply_filters('FHEE__EEH_Template__format_currency__display_code', $display_code);
544
-
545
-            $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY;
546
-            if ($return_raw) {
547
-                $format = CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
548
-            } elseif ($display_code) {
549
-                $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE;
550
-            }
551
-            // don't like this but it maintains backwards compatibility with how things were done before
552
-            $precision = $allow_fractional_subunits
553
-                ? $currency_locale->decimalPrecision() + 2
554
-                : $currency_locale->decimalPrecision();
555
-
556
-            $amount_formatted = $currency_formatter->formatForLocale(
557
-                $amount_formatted,
558
-                $format,
559
-                $currency_locale->name(),
560
-                $precision
561
-            );
562
-
563
-            // filter results
564
-            $amount_formatted = apply_filters(
565
-                'FHEE__EEH_Template__format_currency__amount_formatted',
566
-                $amount_formatted,
567
-                EEH_Template::getCurrencyConfigForCountryISO($currency_locale),
568
-                $return_raw,
569
-                $display_code
570
-            );
571
-        } catch (Exception $e) {
572
-            // eat exception
573
-        }
574
-        // return formatted currency amount
575
-        return esc_html($amount_formatted);
576
-    }
577
-
578
-
579
-    /**
580
-     * This function is used for outputting the localized label for a given status id in the schema requested (and
581
-     * possibly plural).  The intended use of this function is only for cases where wanting a label outside of a
582
-     * related status model or model object (i.e. in documentation etc.)
583
-     *
584
-     * @param string  $status_id  Status ID matching a registered status in the esp_status table.  If there is no
585
-     *                            match, then 'Unknown' will be returned.
586
-     * @param boolean $plural     Whether to return plural or not
587
-     * @param string  $schema     'UPPER', 'lower', or 'Sentence'
588
-     * @return string             The localized label for the status id.
589
-     * @throws EE_Error
590
-     */
591
-    public static function pretty_status($status_id, $plural = false, $schema = 'upper')
592
-    {
593
-        $status = EEM_Status::instance()->localized_status(
594
-            [$status_id => esc_html__('unknown', 'event_espresso')],
595
-            $plural,
596
-            $schema
597
-        );
598
-        return $status[ $status_id ];
599
-    }
600
-
601
-
602
-    /**
603
-     * This helper just returns a button or link for the given parameters
604
-     *
605
-     * @param string $url   the url for the link, note that `esc_url` will be called on it
606
-     * @param string $label What is the label you want displayed for the button
607
-     * @param string $class what class is used for the button (defaults to 'button-primary')
608
-     * @param string $icon
609
-     * @param string $title
610
-     * @return string the html output for the button
611
-     */
612
-    public static function get_button_or_link($url, $label, $class = 'button button--primary', $icon = '', $title = '')
613
-    {
614
-        $icon_html = '';
615
-        if (! empty($icon)) {
616
-            $dashicons = preg_split("(ee-icon |dashicons )", $icon);
617
-            $dashicons = array_filter($dashicons);
618
-            $count     = count($dashicons);
619
-            $icon_html .= $count > 1 ? '<span class="ee-composite-dashicon">' : '';
620
-            foreach ($dashicons as $dashicon) {
621
-                $type      = strpos($dashicon, 'ee-icon') !== false ? 'ee-icon ' : 'dashicons ';
622
-                $icon_html .= '<span class="' . $type . $dashicon . '"></span>';
623
-            }
624
-            $icon_html .= $count > 1 ? '</span>' : '';
625
-        }
626
-        // sanitize & escape
627
-        $id    = sanitize_title_with_dashes($label);
628
-        $url   = esc_url_raw($url);
629
-        $class = esc_attr($class);
630
-        $title = esc_attr($title);
631
-        $label = esc_html($label);
632
-        return "<a id='{$id}' href='{$url}' class='{$class}' title='{$title}'>{$icon_html}{$label}</a>";
633
-    }
634
-
635
-
636
-    /**
637
-     * This returns a generated link that will load the related help tab on admin pages.
638
-     *
639
-     * @param string      $help_tab_id the id for the connected help tab
640
-     * @param bool|string $page        The page identifier for the page the help tab is on
641
-     * @param bool|string $action      The action (route) for the admin page the help tab is on.
642
-     * @param bool|string $icon_style  (optional) include css class for the style you want to use for the help icon.
643
-     * @param bool|string $help_text   (optional) send help text you want to use for the link if default not to be used
644
-     * @return string              generated link
645
-     */
646
-    public static function get_help_tab_link(
647
-        $help_tab_id,
648
-        $page = false,
649
-        $action = false,
650
-        $icon_style = false,
651
-        $help_text = false
652
-    ) {
653
-        $allowedtags = AllowedTags::getAllowedTags();
654
-        /** @var RequestInterface $request */
655
-        $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
656
-        $page    = $page ?: $request->getRequestParam('page', '', 'key');
657
-        $action  = $action ?: $request->getRequestParam('action', 'default', 'key');
658
-
659
-
660
-        $help_tab_lnk = $page . '-' . $action . '-' . $help_tab_id;
661
-        $icon = $icon_style ? $icon_style : ' dashicons-editor-help';
662
-        $help_text = $help_text ? $help_text : '';
663
-        $link = '<a id="' . $help_tab_lnk . '"';
664
-        $link .= ' class="ee-clickable espresso-help-tab-lnk dashicons' . $icon . '"';
665
-        $link .= ' title="' .  esc_attr__(
666
-            'Click to open the \'Help\' tab for more information about this feature.',
667
-            'event_espresso'
668
-        ) . '"';
669
-        $link .= ' > ' . $help_text . ' </a>';
670
-        return $link;
671
-    }
672
-
673
-
674
-    /**
675
-     * This helper generates the html structure for the jquery joyride plugin with the given params.
676
-     *
677
-     * @link http://zurb.com/playground/jquery-joyride-feature-tour-plugin
678
-     * @see  EE_Admin_Page->_stop_callback() for the construct expected for the $stops param.
679
-     * @param EE_Help_Tour
680
-     * @return string         html
681
-     * @throws EE_Error
682
-     */
683
-    public static function help_tour_stops_generator(EE_Help_Tour $tour)
684
-    {
685
-        $id    = $tour->get_slug();
686
-        $stops = $tour->get_stops();
687
-
688
-        $content = '<ol style="display:none" id="' . $id . '">';
689
-
690
-        foreach ($stops as $stop) {
691
-            $data_id    = ! empty($stop['id']) ? ' data-id="' . $stop['id'] . '"' : '';
692
-            $data_class = empty($data_id) && ! empty($stop['class']) ? ' data-class="' . $stop['class'] . '"' : '';
693
-
694
-            // if container is set to modal then let's make sure we set the options accordingly
695
-            if (empty($data_id) && empty($data_class)) {
696
-                $stop['options']['modal']  = true;
697
-                $stop['options']['expose'] = true;
698
-            }
699
-
700
-            $custom_class  = ! empty($stop['custom_class']) ? ' class="' . $stop['custom_class'] . '"' : '';
701
-            $button_text   = ! empty($stop['button_text']) ? ' data-button="' . $stop['button_text'] . '"' : '';
702
-            $inner_content = isset($stop['content']) ? $stop['content'] : '';
703
-
704
-            // options
705
-            if (isset($stop['options']) && is_array($stop['options'])) {
706
-                $options = ' data-options="';
707
-                foreach ($stop['options'] as $option => $value) {
708
-                    $options .= $option . ':' . $value . ';';
709
-                }
710
-                $options .= '"';
711
-            } else {
712
-                $options = '';
713
-            }
714
-
715
-            // let's put all together
716
-            $content .= '<li'
717
-                        . $data_id
718
-                        . $data_class
719
-                        . $custom_class
720
-                        . $button_text
721
-                        . $options
722
-                        . '>'
723
-                        . $inner_content
724
-                        . '</li>';
725
-        }
726
-
727
-        $content .= '</ol>';
728
-        return $content;
729
-    }
730
-
731
-
732
-    /**
733
-     * This is a helper method to generate a status legend for a given status array.
734
-     * Note this will only work if the incoming statuses have a key in the EEM_Status->localized_status() methods
735
-     * status_array.
736
-     *
737
-     * @param array  $status_array   array of statuses that will make up the legend. In format:
738
-     *                               array(
739
-     *                               'status_item' => 'status_name'
740
-     *                               )
741
-     * @param string $active_status  This is used to indicate what the active status is IF that is to be highlighted in
742
-     *                               the legend.
743
-     * @return string               html structure for status.
744
-     * @throws EE_Error
745
-     */
746
-    public static function status_legend($status_array, $active_status = '')
747
-    {
748
-        if (! is_array($status_array)) {
749
-            throw new EE_Error(
750
-                esc_html__(
751
-                    'The EEH_Template::status_legend helper required the incoming status_array argument to be an array!',
752
-                    'event_espresso'
753
-                )
754
-            );
755
-        }
756
-
757
-        $content = '
54
+	/**
55
+	 * @var EE_Currency_Config[]
56
+	 */
57
+	private static $currency_config;
58
+
59
+	/**
60
+	 * @var CurrencyFormatter
61
+	 */
62
+	private static $currency_formatter;
63
+
64
+	/**
65
+	 * @var array
66
+	 */
67
+	private static $_espresso_themes = [];
68
+
69
+
70
+	/**
71
+	 * get legacy currency config object for locale
72
+	 *
73
+	 * @param Locale $currency_locale
74
+	 * @return EE_Currency_Config
75
+	 * @throws EE_Error
76
+	 * @throws ReflectionException
77
+	 * @since $VID:$
78
+	 */
79
+	private static function getCurrencyConfigForCountryISO(Locale $currency_locale)
80
+	{
81
+		$currencyISO = $currency_locale->currencyIsoCode();
82
+		if (
83
+			! isset(EEH_Template::$currency_config[ $currencyISO ])
84
+			|| ! EEH_Template::$currency_config[ $currencyISO ] instanceof EE_Currency_Config
85
+		) {
86
+			$CNT_ISO = EEM_Country::instance()->getCountryISOForCurrencyISO($currencyISO);
87
+			$currency_config = new EE_Currency_Config($CNT_ISO);
88
+			EEH_Template::$currency_config[ $currencyISO ] = $currency_config;
89
+		}
90
+		return EEH_Template::$currency_config[ $currencyISO ];
91
+	}
92
+
93
+
94
+	/**
95
+	 * @return CurrencyFormatter
96
+	 * @since   $VID:$
97
+	 */
98
+	private static function getCurrencyFormatter()
99
+	{
100
+		if (! EEH_Template::$currency_formatter instanceof CurrencyFormatter) {
101
+			EEH_Template::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
102
+		}
103
+		return EEH_Template::$currency_formatter;
104
+	}
105
+
106
+
107
+	/**
108
+	 *    is_espresso_theme - returns TRUE or FALSE on whether the currently active WP theme is an espresso theme
109
+	 *
110
+	 * @return boolean
111
+	 */
112
+	public static function is_espresso_theme()
113
+	{
114
+		return wp_get_theme()->get('TextDomain') === 'event_espresso';
115
+	}
116
+
117
+
118
+	/**
119
+	 *    load_espresso_theme_functions - if current theme is an espresso theme, or uses ee theme template parts, then
120
+	 *    load its functions.php file ( if not already loaded )
121
+	 *
122
+	 * @return void
123
+	 */
124
+	public static function load_espresso_theme_functions()
125
+	{
126
+		if (
127
+			! defined('EE_THEME_FUNCTIONS_LOADED')
128
+			&& is_readable(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php')
129
+		) {
130
+			require_once(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php');
131
+		}
132
+	}
133
+
134
+
135
+	/**
136
+	 *    get_espresso_themes - returns an array of Espresso Child themes located in the /templates/ directory
137
+	 *
138
+	 * @return array
139
+	 */
140
+	public static function get_espresso_themes()
141
+	{
142
+		if (empty(EEH_Template::$_espresso_themes)) {
143
+			$espresso_themes = glob(EE_PUBLIC . '*', GLOB_ONLYDIR);
144
+			if (empty($espresso_themes)) {
145
+				return [];
146
+			}
147
+			if (($key = array_search('global_assets', $espresso_themes, true)) !== false) {
148
+				unset($espresso_themes[ $key ]);
149
+			}
150
+			EEH_Template::$_espresso_themes = [];
151
+			foreach ($espresso_themes as $espresso_theme) {
152
+				EEH_Template::$_espresso_themes[ basename($espresso_theme) ] = $espresso_theme;
153
+			}
154
+		}
155
+		return EEH_Template::$_espresso_themes;
156
+	}
157
+
158
+
159
+	/**
160
+	 * EEH_Template::get_template_part
161
+	 * basically a copy of the WordPress get_template_part() function but uses EEH_Template::locate_template() instead,
162
+	 * and doesn't add base versions of files so not a very useful function at all except that it adds familiarity PLUS
163
+	 * filtering based off of the entire template part name
164
+	 *
165
+	 * @param string $slug The slug name for the generic template.
166
+	 * @param string $name The name of the specialised template.
167
+	 * @param array  $template_args
168
+	 * @param bool   $return_string
169
+	 * @return void  echos the html output for the template
170
+	 */
171
+	public static function get_template_part(
172
+		$slug = null,
173
+		$name = null,
174
+		$template_args = [],
175
+		$return_string = false
176
+	) {
177
+		do_action("get_template_part_{$slug}-{$name}", $slug, $name);
178
+		$templates = [];
179
+		$name      = (string) $name;
180
+		if ($name !== '') {
181
+			$templates[] = "{$slug}-{$name}.php";
182
+		}
183
+		// allow template parts to be turned off via something like:
184
+		// add_filter( 'FHEE__content_espresso_events_tickets_template__display_datetimes', '__return_false' );
185
+		if (apply_filters("FHEE__EEH_Template__get_template_part__display__{$slug}_{$name}", true)) {
186
+			return EEH_Template::locate_template($templates, $template_args, true, $return_string);
187
+		}
188
+		return '';
189
+	}
190
+
191
+
192
+	/**
193
+	 *    locate_template
194
+	 *    locate a template file by looking in the following places, in the following order:
195
+	 *        <server path up to>/wp-content/themes/<current active WordPress theme>/
196
+	 *        <assumed full absolute server path>
197
+	 *        <server path up to>/wp-content/uploads/espresso/templates/<current EE theme>/
198
+	 *        <server path up to>/wp-content/uploads/espresso/templates/
199
+	 *        <server path up to>/wp-content/plugins/<EE4 folder>/public/<current EE theme>/
200
+	 *        <server path up to>/wp-content/plugins/<EE4 folder>/core/templates/<current EE theme>/
201
+	 *        <server path up to>/wp-content/plugins/<EE4 folder>/
202
+	 *    as soon as the template is found in one of these locations, it will be returned or loaded
203
+	 *        Example:
204
+	 *          You are using the WordPress Twenty Sixteen theme,
205
+	 *        and you want to customize the "some-event.template.php" template,
206
+	 *          which is located in the "/relative/path/to/" folder relative to the main EE plugin folder.
207
+	 *          Assuming WP is installed on your server in the "/home/public_html/" folder,
208
+	 *        EEH_Template::locate_template() will look at the following paths in order until the template is found:
209
+	 *        /home/public_html/wp-content/themes/twentysixteen/some-event.template.php
210
+	 *        /relative/path/to/some-event.template.php
211
+	 *        /home/public_html/wp-content/uploads/espresso/templates/Espresso_Arabica_2014/relative/path/to/some-event.template.php
212
+	 *        /home/public_html/wp-content/uploads/espresso/templates/relative/path/to/some-event.template.php
213
+	 *        /home/public_html/wp-content/plugins/event-espresso-core-reg/public/Espresso_Arabica_2014/relative/path/to/some-event.template.php
214
+	 *        /home/public_html/wp-content/plugins/event-espresso-core-reg/core/templates/Espresso_Arabica_2014/relative/path/to/some-event.template.php
215
+	 *        /home/public_html/wp-content/plugins/event-espresso-core-reg/relative/path/to/some-event.template.php
216
+	 *          Had you passed an absolute path to your template that was in some other location,
217
+	 *        ie: "/absolute/path/to/some-event.template.php"
218
+	 *          then the search would have been :
219
+	 *        /home/public_html/wp-content/themes/twentysixteen/some-event.template.php
220
+	 *        /absolute/path/to/some-event.template.php
221
+	 *          and stopped there upon finding it in the second location
222
+	 *
223
+	 * @param array|string $templates       array of template file names including extension (or just a single string)
224
+	 * @param array        $template_args   an array of arguments to be extracted for use in the template
225
+	 * @param boolean      $load            whether to pass the located template path on to the
226
+	 *                                      EEH_Template::display_template() method or simply return it
227
+	 * @param boolean      $return_string   whether to send output immediately to screen, or capture and return as a
228
+	 *                                      string
229
+	 * @param boolean      $check_if_custom If TRUE, this flags this method to return boolean for whether this will
230
+	 *                                      generate a custom template or not. Used in places where you don't actually
231
+	 *                                      load the template, you just want to know if there's a custom version of it.
232
+	 * @return mixed
233
+	 * @throws DomainException
234
+	 * @throws InvalidArgumentException
235
+	 * @throws InvalidDataTypeException
236
+	 * @throws InvalidInterfaceException
237
+	 */
238
+	public static function locate_template(
239
+		$templates = [],
240
+		$template_args = [],
241
+		$load = true,
242
+		$return_string = true,
243
+		$check_if_custom = false
244
+	) {
245
+		// first use WP locate_template to check for template in the current theme folder
246
+		$template_path = locate_template($templates);
247
+
248
+		if ($check_if_custom && ! empty($template_path)) {
249
+			return true;
250
+		}
251
+
252
+		// not in the theme
253
+		if (empty($template_path)) {
254
+			// not even a template to look for ?
255
+			if (empty($templates)) {
256
+				$loader = LoaderFactory::getLoader();
257
+				/** @var RequestInterface $request */
258
+				$request = $loader->getShared(RequestInterface::class);
259
+				// get post_type
260
+				$post_type = $request->getRequestParam('post_type');
261
+				/** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
262
+				$custom_post_types = $loader->getShared(
263
+					'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
264
+				);
265
+				// get array of EE Custom Post Types
266
+				$EE_CPTs = $custom_post_types->getDefinitions();
267
+				// build template name based on request
268
+				if (isset($EE_CPTs[ $post_type ])) {
269
+					$archive_or_single = is_archive() ? 'archive' : '';
270
+					$archive_or_single = is_single() ? 'single' : $archive_or_single;
271
+					$templates         = $archive_or_single . '-' . $post_type . '.php';
272
+				}
273
+			}
274
+			// currently active EE template theme
275
+			$current_theme = EE_Config::get_current_theme();
276
+
277
+			// array of paths to folders that may contain templates
278
+			$template_folder_paths = [
279
+				// first check the /wp-content/uploads/espresso/templates/(current EE theme)/  folder for an EE theme template file
280
+				EVENT_ESPRESSO_TEMPLATE_DIR . $current_theme,
281
+				// then in the root of the /wp-content/uploads/espresso/templates/ folder
282
+				EVENT_ESPRESSO_TEMPLATE_DIR,
283
+			];
284
+
285
+			// add core plugin folders for checking only if we're not $check_if_custom
286
+			if (! $check_if_custom) {
287
+				$core_paths            = [
288
+					// in the  /wp-content/plugins/(EE4 folder)/public/(current EE theme)/ folder within the plugin
289
+					EE_PUBLIC . $current_theme,
290
+					// in the  /wp-content/plugins/(EE4 folder)/core/templates/(current EE theme)/ folder within the plugin
291
+					EE_TEMPLATES . $current_theme,
292
+					// or maybe relative from the plugin root: /wp-content/plugins/(EE4 folder)/
293
+					EE_PLUGIN_DIR_PATH,
294
+				];
295
+				$template_folder_paths = array_merge($template_folder_paths, $core_paths);
296
+			}
297
+
298
+			// now filter that array
299
+			$template_folder_paths = apply_filters(
300
+				'FHEE__EEH_Template__locate_template__template_folder_paths',
301
+				$template_folder_paths
302
+			);
303
+			$templates = is_array($templates) ? $templates : [$templates];
304
+			$template_folder_paths = is_array($template_folder_paths)
305
+				? $template_folder_paths
306
+				: [$template_folder_paths];
307
+			// array to hold all possible template paths
308
+			$full_template_paths = [];
309
+			$file_name           = '';
310
+			// loop through $templates
311
+			foreach ($templates as $template) {
312
+				// normalize directory separators
313
+				$template                      = EEH_File::standardise_directory_separators($template);
314
+				$file_name                     = basename($template);
315
+				$template_path_minus_file_name = substr($template, 0, (strlen($file_name) * -1));
316
+				// while looping through all template folder paths
317
+				foreach ($template_folder_paths as $template_folder_path) {
318
+					// normalize directory separators
319
+					$template_folder_path = EEH_File::standardise_directory_separators($template_folder_path);
320
+					// determine if any common base path exists between the two paths
321
+					$common_base_path = EEH_Template::_find_common_base_path(
322
+						[$template_folder_path, $template_path_minus_file_name]
323
+					);
324
+					if ($common_base_path !== '') {
325
+						// both paths have a common base, so just tack the filename onto our search path
326
+						$resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $file_name;
327
+					} else {
328
+						// no common base path, so let's just concatenate
329
+						$resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $template;
330
+					}
331
+					// build up our template locations array by adding our resolved paths
332
+					$full_template_paths[] = $resolved_path;
333
+				}
334
+				// if $template is an absolute path, then we'll tack it onto the start of our array so that it gets searched first
335
+				array_unshift($full_template_paths, $template);
336
+				// path to the directory of the current theme: /wp-content/themes/(current WP theme)/
337
+				array_unshift($full_template_paths, get_stylesheet_directory() . '/' . $file_name);
338
+			}
339
+			// filter final array of full template paths
340
+			$full_template_paths = apply_filters(
341
+				'FHEE__EEH_Template__locate_template__full_template_paths',
342
+				$full_template_paths,
343
+				$file_name
344
+			);
345
+			// now loop through our final array of template location paths and check each location
346
+			foreach ((array) $full_template_paths as $full_template_path) {
347
+				if (is_readable($full_template_path)) {
348
+					$template_path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $full_template_path);
349
+					break;
350
+				}
351
+			}
352
+		}
353
+
354
+		// hook that can be used to display the full template path that will be used
355
+		do_action('AHEE__EEH_Template__locate_template__full_template_path', $template_path);
356
+
357
+		// if we got it and you want to see it...
358
+		if ($template_path && $load && ! $check_if_custom) {
359
+			if ($return_string) {
360
+				return EEH_Template::display_template($template_path, $template_args, true);
361
+			}
362
+			EEH_Template::display_template($template_path, $template_args);
363
+		}
364
+		return $check_if_custom && ! empty($template_path) ? true : $template_path;
365
+	}
366
+
367
+
368
+	/**
369
+	 * _find_common_base_path
370
+	 * given two paths, this determines if there is a common base path between the two
371
+	 *
372
+	 * @param array $paths
373
+	 * @return string
374
+	 */
375
+	protected static function _find_common_base_path($paths)
376
+	{
377
+		$last_offset      = 0;
378
+		$common_base_path = '';
379
+		while (($index = strpos($paths[0], '/', $last_offset)) !== false) {
380
+			$dir_length = $index - $last_offset + 1;
381
+			$directory  = substr($paths[0], $last_offset, $dir_length);
382
+			foreach ($paths as $path) {
383
+				if (substr($path, $last_offset, $dir_length) !== $directory) {
384
+					return $common_base_path;
385
+				}
386
+			}
387
+			$common_base_path .= $directory;
388
+			$last_offset      = $index + 1;
389
+		}
390
+		return substr($common_base_path, 0, -1);
391
+	}
392
+
393
+
394
+	/**
395
+	 * load and display a template
396
+	 *
397
+	 * @param bool|string $template_path    server path to the file to be loaded, including file name and extension
398
+	 * @param array       $template_args    an array of arguments to be extracted for use in the template
399
+	 * @param boolean     $return_string    whether to send output immediately to screen, or capture and return as a
400
+	 *                                      string
401
+	 * @param bool        $throw_exceptions if set to true, will throw an exception if the template is either
402
+	 *                                      not found or is not readable
403
+	 * @return string
404
+	 * @throws DomainException
405
+	 */
406
+	public static function display_template(
407
+		$template_path = false,
408
+		$template_args = [],
409
+		$return_string = false,
410
+		$throw_exceptions = false
411
+	) {
412
+		/**
413
+		 * These two filters are intended for last minute changes to templates being loaded and/or template arg
414
+		 * modifications.  NOTE... modifying these things can cause breakage as most templates running through
415
+		 * the display_template method are templates we DON'T want modified (usually because of js
416
+		 * dependencies etc).  So unless you know what you are doing, do NOT filter templates or template args
417
+		 * using this.
418
+		 *
419
+		 * @since 4.6.0
420
+		 */
421
+		$template_path = (string) apply_filters('FHEE__EEH_Template__display_template__template_path', $template_path);
422
+		$template_args = (array) apply_filters('FHEE__EEH_Template__display_template__template_args', $template_args);
423
+
424
+		// you gimme nuttin - YOU GET NUTTIN !!
425
+		if (! $template_path || ! is_readable($template_path)) {
426
+			// ignore whether template is accessible ?
427
+			if ($throw_exceptions) {
428
+				throw new DomainException(
429
+					esc_html__('Invalid, unreadable, or missing file.', 'event_espresso')
430
+				);
431
+			}
432
+			return '';
433
+		}
434
+		// if $template_args are not in an array, then make it so
435
+		if (! is_array($template_args) && ! is_object($template_args)) {
436
+			$template_args = [$template_args];
437
+		}
438
+		extract($template_args, EXTR_SKIP);
439
+		// ignore whether template is accessible ?
440
+		if ($throw_exceptions && ! is_readable($template_path)) {
441
+			throw new DomainException(
442
+				esc_html__(
443
+					'Invalid, unreadable, or missing file.',
444
+					'event_espresso'
445
+				)
446
+			);
447
+		}
448
+
449
+
450
+		if ($return_string) {
451
+			// because we want to return a string, we are going to capture the output
452
+			ob_start();
453
+			include($template_path);
454
+			return ob_get_clean();
455
+		}
456
+		include($template_path);
457
+		return '';
458
+	}
459
+
460
+
461
+	/**
462
+	 * get_object_css_class - attempts to generate a css class based on the type of EE object passed
463
+	 *
464
+	 * @param EE_Base_Class $object the EE object the css class is being generated for
465
+	 * @param string        $prefix added to the beginning of the generated class
466
+	 * @param string        $suffix added to the end of the generated class
467
+	 * @return string
468
+	 * @throws EE_Error
469
+	 * @throws ReflectionException
470
+	 */
471
+	public static function get_object_css_class($object = null, $prefix = '', $suffix = '')
472
+	{
473
+		// in the beginning...
474
+		$prefix = ! empty($prefix) ? rtrim($prefix, '-') . '-' : '';
475
+		// da muddle
476
+		$class = '';
477
+		// the end
478
+		$suffix = ! empty($suffix) ? '-' . ltrim($suffix, '-') : '';
479
+		// is the passed object an EE object ?
480
+		if ($object instanceof EE_Base_Class) {
481
+			// grab the exact type of object
482
+			$obj_class = get_class($object);
483
+			// depending on the type of object...
484
+			$class = strtolower(str_replace('_', '-', $obj_class));
485
+			$class .= method_exists($obj_class, 'name') ? '-' . sanitize_title($object->name()) : '';
486
+		}
487
+		return $prefix . $class . $suffix;
488
+	}
489
+
490
+
491
+	/**
492
+	 * EEH_Template::format_currency
493
+	 * This helper takes a raw float value and formats it according to the default config country currency settings, or
494
+	 * the country currency settings from the supplied country ISO code
495
+	 *
496
+	 * @param float   $amount                    raw money value
497
+	 * @param boolean $return_raw                whether to return the formatted float value only with no currency sign
498
+	 *                                           or code
499
+	 * @param boolean $display_code              whether to display the country code (USD). Default = TRUE
500
+	 * @param string  $CNT_ISO                   2 letter ISO code for a country
501
+	 * @param string  $cur_code_span_class
502
+	 * @param boolean $allow_fractional_subunits whether to allow displaying partial penny amounts
503
+	 * @return string        the html output for the formatted money value
504
+	 * @throws EE_Error
505
+	 * @throws ReflectionException
506
+	 * @deprecated $VID:$ plz use CurrencyFormatter::formatForLocale()
507
+	 */
508
+	public static function format_currency(
509
+		$amount = null,
510
+		$return_raw = false,
511
+		$display_code = true,
512
+		$CNT_ISO = '',
513
+		$cur_code_span_class = 'currency-code', // not used anywhere
514
+		$allow_fractional_subunits = false
515
+	) {
516
+		// ensure amount was received
517
+		if ($amount === null) {
518
+			$msg = esc_html__('In order to format currency, an amount needs to be passed.', 'event_espresso');
519
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
520
+			return '';
521
+		}
522
+		// ensure amount is float
523
+		$amount  = (float) apply_filters('FHEE__EEH_Template__format_currency__raw_amount', (float) $amount);
524
+		$CNT_ISO = apply_filters('FHEE__EEH_Template__format_currency__CNT_ISO', $CNT_ISO, $amount);
525
+
526
+		// filter raw amount (allows 0.00 to be changed to "free" for example)
527
+		$amount_formatted = apply_filters('FHEE__EEH_Template__format_currency__amount', $amount, $return_raw);
528
+		// still a number or was amount converted to a string like "free" ?
529
+		if (! is_float($amount_formatted)) {
530
+			return esc_html($amount_formatted);
531
+		}
532
+
533
+		try {
534
+			$currency_formatter = EEH_Template::getCurrencyFormatter();
535
+			$currency_ISO = '';
536
+			if ($CNT_ISO !== '') {
537
+				$currency_config = EEH_Money::get_currency_config($CNT_ISO);
538
+				$currency_ISO = $currency_config->code;
539
+			}
540
+			$currency_locale = $currency_formatter->getLocaleForCurrencyISO($currency_ISO, true);
541
+
542
+			// filter to allow global setting of display_code
543
+			$display_code = apply_filters('FHEE__EEH_Template__format_currency__display_code', $display_code);
544
+
545
+			$format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY;
546
+			if ($return_raw) {
547
+				$format = CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
548
+			} elseif ($display_code) {
549
+				$format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE;
550
+			}
551
+			// don't like this but it maintains backwards compatibility with how things were done before
552
+			$precision = $allow_fractional_subunits
553
+				? $currency_locale->decimalPrecision() + 2
554
+				: $currency_locale->decimalPrecision();
555
+
556
+			$amount_formatted = $currency_formatter->formatForLocale(
557
+				$amount_formatted,
558
+				$format,
559
+				$currency_locale->name(),
560
+				$precision
561
+			);
562
+
563
+			// filter results
564
+			$amount_formatted = apply_filters(
565
+				'FHEE__EEH_Template__format_currency__amount_formatted',
566
+				$amount_formatted,
567
+				EEH_Template::getCurrencyConfigForCountryISO($currency_locale),
568
+				$return_raw,
569
+				$display_code
570
+			);
571
+		} catch (Exception $e) {
572
+			// eat exception
573
+		}
574
+		// return formatted currency amount
575
+		return esc_html($amount_formatted);
576
+	}
577
+
578
+
579
+	/**
580
+	 * This function is used for outputting the localized label for a given status id in the schema requested (and
581
+	 * possibly plural).  The intended use of this function is only for cases where wanting a label outside of a
582
+	 * related status model or model object (i.e. in documentation etc.)
583
+	 *
584
+	 * @param string  $status_id  Status ID matching a registered status in the esp_status table.  If there is no
585
+	 *                            match, then 'Unknown' will be returned.
586
+	 * @param boolean $plural     Whether to return plural or not
587
+	 * @param string  $schema     'UPPER', 'lower', or 'Sentence'
588
+	 * @return string             The localized label for the status id.
589
+	 * @throws EE_Error
590
+	 */
591
+	public static function pretty_status($status_id, $plural = false, $schema = 'upper')
592
+	{
593
+		$status = EEM_Status::instance()->localized_status(
594
+			[$status_id => esc_html__('unknown', 'event_espresso')],
595
+			$plural,
596
+			$schema
597
+		);
598
+		return $status[ $status_id ];
599
+	}
600
+
601
+
602
+	/**
603
+	 * This helper just returns a button or link for the given parameters
604
+	 *
605
+	 * @param string $url   the url for the link, note that `esc_url` will be called on it
606
+	 * @param string $label What is the label you want displayed for the button
607
+	 * @param string $class what class is used for the button (defaults to 'button-primary')
608
+	 * @param string $icon
609
+	 * @param string $title
610
+	 * @return string the html output for the button
611
+	 */
612
+	public static function get_button_or_link($url, $label, $class = 'button button--primary', $icon = '', $title = '')
613
+	{
614
+		$icon_html = '';
615
+		if (! empty($icon)) {
616
+			$dashicons = preg_split("(ee-icon |dashicons )", $icon);
617
+			$dashicons = array_filter($dashicons);
618
+			$count     = count($dashicons);
619
+			$icon_html .= $count > 1 ? '<span class="ee-composite-dashicon">' : '';
620
+			foreach ($dashicons as $dashicon) {
621
+				$type      = strpos($dashicon, 'ee-icon') !== false ? 'ee-icon ' : 'dashicons ';
622
+				$icon_html .= '<span class="' . $type . $dashicon . '"></span>';
623
+			}
624
+			$icon_html .= $count > 1 ? '</span>' : '';
625
+		}
626
+		// sanitize & escape
627
+		$id    = sanitize_title_with_dashes($label);
628
+		$url   = esc_url_raw($url);
629
+		$class = esc_attr($class);
630
+		$title = esc_attr($title);
631
+		$label = esc_html($label);
632
+		return "<a id='{$id}' href='{$url}' class='{$class}' title='{$title}'>{$icon_html}{$label}</a>";
633
+	}
634
+
635
+
636
+	/**
637
+	 * This returns a generated link that will load the related help tab on admin pages.
638
+	 *
639
+	 * @param string      $help_tab_id the id for the connected help tab
640
+	 * @param bool|string $page        The page identifier for the page the help tab is on
641
+	 * @param bool|string $action      The action (route) for the admin page the help tab is on.
642
+	 * @param bool|string $icon_style  (optional) include css class for the style you want to use for the help icon.
643
+	 * @param bool|string $help_text   (optional) send help text you want to use for the link if default not to be used
644
+	 * @return string              generated link
645
+	 */
646
+	public static function get_help_tab_link(
647
+		$help_tab_id,
648
+		$page = false,
649
+		$action = false,
650
+		$icon_style = false,
651
+		$help_text = false
652
+	) {
653
+		$allowedtags = AllowedTags::getAllowedTags();
654
+		/** @var RequestInterface $request */
655
+		$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
656
+		$page    = $page ?: $request->getRequestParam('page', '', 'key');
657
+		$action  = $action ?: $request->getRequestParam('action', 'default', 'key');
658
+
659
+
660
+		$help_tab_lnk = $page . '-' . $action . '-' . $help_tab_id;
661
+		$icon = $icon_style ? $icon_style : ' dashicons-editor-help';
662
+		$help_text = $help_text ? $help_text : '';
663
+		$link = '<a id="' . $help_tab_lnk . '"';
664
+		$link .= ' class="ee-clickable espresso-help-tab-lnk dashicons' . $icon . '"';
665
+		$link .= ' title="' .  esc_attr__(
666
+			'Click to open the \'Help\' tab for more information about this feature.',
667
+			'event_espresso'
668
+		) . '"';
669
+		$link .= ' > ' . $help_text . ' </a>';
670
+		return $link;
671
+	}
672
+
673
+
674
+	/**
675
+	 * This helper generates the html structure for the jquery joyride plugin with the given params.
676
+	 *
677
+	 * @link http://zurb.com/playground/jquery-joyride-feature-tour-plugin
678
+	 * @see  EE_Admin_Page->_stop_callback() for the construct expected for the $stops param.
679
+	 * @param EE_Help_Tour
680
+	 * @return string         html
681
+	 * @throws EE_Error
682
+	 */
683
+	public static function help_tour_stops_generator(EE_Help_Tour $tour)
684
+	{
685
+		$id    = $tour->get_slug();
686
+		$stops = $tour->get_stops();
687
+
688
+		$content = '<ol style="display:none" id="' . $id . '">';
689
+
690
+		foreach ($stops as $stop) {
691
+			$data_id    = ! empty($stop['id']) ? ' data-id="' . $stop['id'] . '"' : '';
692
+			$data_class = empty($data_id) && ! empty($stop['class']) ? ' data-class="' . $stop['class'] . '"' : '';
693
+
694
+			// if container is set to modal then let's make sure we set the options accordingly
695
+			if (empty($data_id) && empty($data_class)) {
696
+				$stop['options']['modal']  = true;
697
+				$stop['options']['expose'] = true;
698
+			}
699
+
700
+			$custom_class  = ! empty($stop['custom_class']) ? ' class="' . $stop['custom_class'] . '"' : '';
701
+			$button_text   = ! empty($stop['button_text']) ? ' data-button="' . $stop['button_text'] . '"' : '';
702
+			$inner_content = isset($stop['content']) ? $stop['content'] : '';
703
+
704
+			// options
705
+			if (isset($stop['options']) && is_array($stop['options'])) {
706
+				$options = ' data-options="';
707
+				foreach ($stop['options'] as $option => $value) {
708
+					$options .= $option . ':' . $value . ';';
709
+				}
710
+				$options .= '"';
711
+			} else {
712
+				$options = '';
713
+			}
714
+
715
+			// let's put all together
716
+			$content .= '<li'
717
+						. $data_id
718
+						. $data_class
719
+						. $custom_class
720
+						. $button_text
721
+						. $options
722
+						. '>'
723
+						. $inner_content
724
+						. '</li>';
725
+		}
726
+
727
+		$content .= '</ol>';
728
+		return $content;
729
+	}
730
+
731
+
732
+	/**
733
+	 * This is a helper method to generate a status legend for a given status array.
734
+	 * Note this will only work if the incoming statuses have a key in the EEM_Status->localized_status() methods
735
+	 * status_array.
736
+	 *
737
+	 * @param array  $status_array   array of statuses that will make up the legend. In format:
738
+	 *                               array(
739
+	 *                               'status_item' => 'status_name'
740
+	 *                               )
741
+	 * @param string $active_status  This is used to indicate what the active status is IF that is to be highlighted in
742
+	 *                               the legend.
743
+	 * @return string               html structure for status.
744
+	 * @throws EE_Error
745
+	 */
746
+	public static function status_legend($status_array, $active_status = '')
747
+	{
748
+		if (! is_array($status_array)) {
749
+			throw new EE_Error(
750
+				esc_html__(
751
+					'The EEH_Template::status_legend helper required the incoming status_array argument to be an array!',
752
+					'event_espresso'
753
+				)
754
+			);
755
+		}
756
+
757
+		$content = '
758 758
             <div class="ee-list-table-legend-container">
759 759
                 <h4 class="status-legend-title">
760 760
                     ' . esc_html__('Status Legend', 'event_espresso') . '
761 761
                 </h4>
762 762
                 <dl class="ee-list-table-legend">';
763 763
 
764
-        foreach ($status_array as $item => $status) {
765
-            $active_class = $active_status == $status ? 'class="ee-is-active-status"' : '';
766
-            $content      .= '
764
+		foreach ($status_array as $item => $status) {
765
+			$active_class = $active_status == $status ? 'class="ee-is-active-status"' : '';
766
+			$content      .= '
767 767
                     <dt id="' . esc_attr('ee-legend-item-tooltip-' . $item) . '" ' . $active_class . '>
768 768
                         <span class="' . esc_attr('ee-status-legend ee-status-legend-' . $status) . '"></span>
769 769
                         <span class="ee-legend-description">
770 770
                             ' . EEH_Template::pretty_status($status, false, 'sentence') . '
771 771
                         </span>
772 772
                     </dt>';
773
-        }
773
+		}
774 774
 
775
-        $content .= '
775
+		$content .= '
776 776
                 </dl>
777 777
             </div>
778 778
 ';
779
-        return $content;
780
-    }
781
-
782
-
783
-    /**
784
-     * Gets HTML for laying out a deeply-nested array (and objects) in a format
785
-     * that's nice for presenting in the wp admin
786
-     *
787
-     * @param mixed $data
788
-     * @return string
789
-     */
790
-    public static function layout_array_as_table($data)
791
-    {
792
-        if (is_object($data) || $data instanceof __PHP_Incomplete_Class) {
793
-            $data = (array) $data;
794
-        }
795
-        ob_start();
796
-        if (is_array($data)) {
797
-            if (EEH_Array::is_associative_array($data)) { ?>
779
+		return $content;
780
+	}
781
+
782
+
783
+	/**
784
+	 * Gets HTML for laying out a deeply-nested array (and objects) in a format
785
+	 * that's nice for presenting in the wp admin
786
+	 *
787
+	 * @param mixed $data
788
+	 * @return string
789
+	 */
790
+	public static function layout_array_as_table($data)
791
+	{
792
+		if (is_object($data) || $data instanceof __PHP_Incomplete_Class) {
793
+			$data = (array) $data;
794
+		}
795
+		ob_start();
796
+		if (is_array($data)) {
797
+			if (EEH_Array::is_associative_array($data)) { ?>
798 798
                 <table class="widefat">
799 799
                     <tbody>
800 800
                     <?php foreach ($data as $data_key => $data_values) { ?>
@@ -804,7 +804,7 @@  discard block
 block discarded – undo
804 804
                             </td>
805 805
                             <td>
806 806
                                 <?php
807
-                                echo EEH_Template::layout_array_as_table($data_values); ?>
807
+								echo EEH_Template::layout_array_as_table($data_values); ?>
808 808
                             </td>
809 809
                         </tr>
810 810
                     <?php } ?>
@@ -813,292 +813,292 @@  discard block
 block discarded – undo
813 813
             <?php } else { ?>
814 814
                 <ul>
815 815
                     <?php
816
-                    foreach ($data as $datum) {
817
-                        echo "<li>";
818
-                        echo EEH_Template::layout_array_as_table($datum);
819
-                        echo "</li>";
820
-                    } ?>
816
+					foreach ($data as $datum) {
817
+						echo "<li>";
818
+						echo EEH_Template::layout_array_as_table($datum);
819
+						echo "</li>";
820
+					} ?>
821 821
                 </ul>
822 822
             <?php }
823
-        } else {
824
-            // simple value
825
-            echo esc_html($data);
826
-        }
827
-        return ob_get_clean();
828
-    }
829
-
830
-
831
-    /**
832
-     * wrapper for EEH_Template::get_paging_html() that simply echos the generated paging html
833
-     *
834
-     * @param        $total_items
835
-     * @param        $current
836
-     * @param        $per_page
837
-     * @param        $url
838
-     * @param bool   $show_num_field
839
-     * @param string $paged_arg_name
840
-     * @param array  $items_label
841
-     * @see   self:get_paging_html() for argument docs.
842
-     * @since 4.4.0
843
-     */
844
-    public static function paging_html(
845
-        $total_items,
846
-        $current,
847
-        $per_page,
848
-        $url,
849
-        $show_num_field = true,
850
-        $paged_arg_name = 'paged',
851
-        $items_label = []
852
-    ) {
853
-        echo EEH_Template::get_paging_html(
854
-            $total_items,
855
-            $current,
856
-            $per_page,
857
-            $url,
858
-            $show_num_field,
859
-            $paged_arg_name,
860
-            $items_label
861
-        );
862
-    }
863
-
864
-
865
-    /**
866
-     * A method for generating paging similar to WP_List_Table
867
-     *
868
-     * @param integer $total_items      How many total items there are to page.
869
-     * @param integer $current          What the current page is.
870
-     * @param integer $per_page         How many items per page.
871
-     * @param string  $url              What the base url for page links is.
872
-     * @param boolean $show_num_field   Whether to show the input for changing page number.
873
-     * @param string  $paged_arg_name   The name of the key for the paged query argument.
874
-     * @param array   $items_label      An array of singular/plural values for the items label:
875
-     *                                  array(
876
-     *                                  'single' => 'item',
877
-     *                                  'plural' => 'items'
878
-     *                                  )
879
-     * @return  string
880
-     * @since    4.4.0
881
-     * @see      wp-admin/includes/class-wp-list-table.php WP_List_Table::pagination()
882
-     */
883
-    public static function get_paging_html(
884
-        $total_items,
885
-        $current,
886
-        $per_page,
887
-        $url,
888
-        $show_num_field = true,
889
-        $paged_arg_name = 'paged',
890
-        $items_label = []
891
-    ) {
892
-        $page_links     = [];
893
-        $disable_first  = $disable_last = '';
894
-        $total_items    = (int) $total_items;
895
-        $per_page       = (int) $per_page;
896
-        $current        = (int) $current;
897
-        $paged_arg_name = empty($paged_arg_name) ? 'paged' : sanitize_key($paged_arg_name);
898
-
899
-        // filter items_label
900
-        $items_label = apply_filters(
901
-            'FHEE__EEH_Template__get_paging_html__items_label',
902
-            $items_label
903
-        );
904
-
905
-        if (
906
-            empty($items_label)
907
-            || ! is_array($items_label)
908
-            || ! isset($items_label['single'], $items_label['plural'])
909
-        ) {
910
-            $items_label = [
911
-                'single' => esc_html__('1 item', 'event_espresso'),
912
-                'plural' => esc_html__('%s items', 'event_espresso'),
913
-            ];
914
-        } else {
915
-            $items_label = [
916
-                'single' => '1 ' . esc_html($items_label['single']),
917
-                'plural' => '%s ' . esc_html($items_label['plural']),
918
-            ];
919
-        }
920
-
921
-        $total_pages = ceil($total_items / $per_page);
922
-
923
-        if ($total_pages <= 1) {
924
-            return '';
925
-        }
926
-
927
-        $item_label = $total_items > 1 ? sprintf($items_label['plural'], $total_items) : $items_label['single'];
928
-
929
-        $output = '<span class="displaying-num">' . $item_label . '</span>';
930
-
931
-        if ($current === 1) {
932
-            $disable_first = ' disabled';
933
-        }
934
-        if ($current === $total_pages) {
935
-            $disable_last = ' disabled';
936
-        }
937
-
938
-        $page_links[] = sprintf(
939
-            "<a class='%s' title='%s' href='%s'>%s</a>",
940
-            'first-page' . $disable_first,
941
-            esc_attr__('Go to the first page', 'event_espresso'),
942
-            esc_url_raw(remove_query_arg($paged_arg_name, $url)),
943
-            '&laquo;'
944
-        );
945
-
946
-        $page_links[] = sprintf(
947
-            '<a class="%s" title="%s" href="%s">%s</a>',
948
-            'prev-page' . $disable_first,
949
-            esc_attr__('Go to the previous page', 'event_espresso'),
950
-            esc_url_raw(add_query_arg($paged_arg_name, max(1, $current - 1), $url)),
951
-            '&lsaquo;'
952
-        );
953
-
954
-        if (! $show_num_field) {
955
-            $html_current_page = $current;
956
-        } else {
957
-            $html_current_page = sprintf(
958
-                "<input class='current-page' title='%s' type='text' name=$paged_arg_name value='%s' size='%d' />",
959
-                esc_attr__('Current page', 'event_espresso'),
960
-                esc_attr($current),
961
-                strlen($total_pages)
962
-            );
963
-        }
964
-
965
-        $html_total_pages = sprintf(
966
-            '<span class="total-pages">%s</span>',
967
-            number_format_i18n($total_pages)
968
-        );
969
-        $page_links[]     = sprintf(
970
-            _x('%3$s%1$s of %2$s%4$s', 'paging', 'event_espresso'),
971
-            $html_current_page,
972
-            $html_total_pages,
973
-            '<span class="paging-input">',
974
-            '</span>'
975
-        );
976
-
977
-        $page_links[] = sprintf(
978
-            '<a class="%s" title="%s" href="%s">%s</a>',
979
-            'next-page' . $disable_last,
980
-            esc_attr__('Go to the next page', 'event_espresso'),
981
-            esc_url_raw(add_query_arg($paged_arg_name, min($total_pages, $current + 1), $url)),
982
-            '&rsaquo;'
983
-        );
984
-
985
-        $page_links[] = sprintf(
986
-            '<a class="%s" title="%s" href="%s">%s</a>',
987
-            'last-page' . $disable_last,
988
-            esc_attr__('Go to the last page', 'event_espresso'),
989
-            esc_url_raw(add_query_arg($paged_arg_name, $total_pages, $url)),
990
-            '&raquo;'
991
-        );
992
-
993
-        $output .= "\n" . '<span class="pagination-links">' . join("\n", $page_links) . '</span>';
994
-        // set page class
995
-        if ($total_pages) {
996
-            $page_class = $total_pages < 2 ? ' one-page' : '';
997
-        } else {
998
-            $page_class = ' no-pages';
999
-        }
1000
-
1001
-        return '<div class="tablenav"><div class="tablenav-pages' . $page_class . '">' . $output . '</div></div>';
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * @param string $wrap_class
1007
-     * @param string $wrap_id
1008
-     * @param array  $query_args
1009
-     * @return string
1010
-     */
1011
-    public static function powered_by_event_espresso($wrap_class = '', $wrap_id = '', array $query_args = [])
1012
-    {
1013
-        $admin = is_admin() && ! (defined('DOING_AJAX') && DOING_AJAX);
1014
-        if (
1015
-            ! $admin
1016
-            && ! apply_filters(
1017
-                'FHEE__EEH_Template__powered_by_event_espresso__show_reg_footer',
1018
-                EE_Registry::instance()->CFG->admin->show_reg_footer
1019
-            )
1020
-        ) {
1021
-            return '';
1022
-        }
1023
-        $tag        = $admin ? 'span' : 'div';
1024
-        $attributes = ! empty($wrap_id) ? " id=\"{$wrap_id}\"" : '';
1025
-        $wrap_class = $admin ? "{$wrap_class} float-left" : $wrap_class;
1026
-        $attributes .= ! empty($wrap_class)
1027
-            ? " class=\"{$wrap_class} powered-by-event-espresso-credit\""
1028
-            : ' class="powered-by-event-espresso-credit"';
1029
-        $query_args = array_merge(
1030
-            [
1031
-                'ap_id'        => EE_Registry::instance()->CFG->admin->affiliate_id(),
1032
-                'utm_source'   => 'powered_by_event_espresso',
1033
-                'utm_medium'   => 'link',
1034
-                'utm_campaign' => 'powered_by',
1035
-            ],
1036
-            $query_args
1037
-        );
1038
-        $powered_by = apply_filters(
1039
-            'FHEE__EEH_Template__powered_by_event_espresso_text',
1040
-            $admin ? 'Event Espresso - ' . EVENT_ESPRESSO_VERSION : 'Event Espresso'
1041
-        );
1042
-        $url        = add_query_arg($query_args, 'https://eventespresso.com/');
1043
-        $url        = apply_filters('FHEE__EEH_Template__powered_by_event_espresso__url', $url);
1044
-        return (string) apply_filters(
1045
-            'FHEE__EEH_Template__powered_by_event_espresso__html',
1046
-            sprintf(
1047
-                esc_html_x(
1048
-                    '%3$s%1$sOnline event registration and ticketing powered by %2$s%3$s',
1049
-                    'Online event registration and ticketing powered by [link to eventespresso.com]',
1050
-                    'event_espresso'
1051
-                ),
1052
-                "<{$tag}{$attributes}>",
1053
-                "<a href=\"{$url}\" target=\"_blank\" rel=\"nofollow\">{$powered_by}</a></{$tag}>",
1054
-                $admin ? '' : '<br />'
1055
-            ),
1056
-            $wrap_class,
1057
-            $wrap_id
1058
-        );
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * @param string $image_name
1064
-     * @return string|null
1065
-     * @since   4.10.14.p
1066
-     */
1067
-    public static function getScreenshotUrl($image_name)
1068
-    {
1069
-        return esc_url_raw(EE_GLOBAL_ASSETS_URL . 'images/screenshots/' . $image_name . '.jpg');
1070
-    }
823
+		} else {
824
+			// simple value
825
+			echo esc_html($data);
826
+		}
827
+		return ob_get_clean();
828
+	}
829
+
830
+
831
+	/**
832
+	 * wrapper for EEH_Template::get_paging_html() that simply echos the generated paging html
833
+	 *
834
+	 * @param        $total_items
835
+	 * @param        $current
836
+	 * @param        $per_page
837
+	 * @param        $url
838
+	 * @param bool   $show_num_field
839
+	 * @param string $paged_arg_name
840
+	 * @param array  $items_label
841
+	 * @see   self:get_paging_html() for argument docs.
842
+	 * @since 4.4.0
843
+	 */
844
+	public static function paging_html(
845
+		$total_items,
846
+		$current,
847
+		$per_page,
848
+		$url,
849
+		$show_num_field = true,
850
+		$paged_arg_name = 'paged',
851
+		$items_label = []
852
+	) {
853
+		echo EEH_Template::get_paging_html(
854
+			$total_items,
855
+			$current,
856
+			$per_page,
857
+			$url,
858
+			$show_num_field,
859
+			$paged_arg_name,
860
+			$items_label
861
+		);
862
+	}
863
+
864
+
865
+	/**
866
+	 * A method for generating paging similar to WP_List_Table
867
+	 *
868
+	 * @param integer $total_items      How many total items there are to page.
869
+	 * @param integer $current          What the current page is.
870
+	 * @param integer $per_page         How many items per page.
871
+	 * @param string  $url              What the base url for page links is.
872
+	 * @param boolean $show_num_field   Whether to show the input for changing page number.
873
+	 * @param string  $paged_arg_name   The name of the key for the paged query argument.
874
+	 * @param array   $items_label      An array of singular/plural values for the items label:
875
+	 *                                  array(
876
+	 *                                  'single' => 'item',
877
+	 *                                  'plural' => 'items'
878
+	 *                                  )
879
+	 * @return  string
880
+	 * @since    4.4.0
881
+	 * @see      wp-admin/includes/class-wp-list-table.php WP_List_Table::pagination()
882
+	 */
883
+	public static function get_paging_html(
884
+		$total_items,
885
+		$current,
886
+		$per_page,
887
+		$url,
888
+		$show_num_field = true,
889
+		$paged_arg_name = 'paged',
890
+		$items_label = []
891
+	) {
892
+		$page_links     = [];
893
+		$disable_first  = $disable_last = '';
894
+		$total_items    = (int) $total_items;
895
+		$per_page       = (int) $per_page;
896
+		$current        = (int) $current;
897
+		$paged_arg_name = empty($paged_arg_name) ? 'paged' : sanitize_key($paged_arg_name);
898
+
899
+		// filter items_label
900
+		$items_label = apply_filters(
901
+			'FHEE__EEH_Template__get_paging_html__items_label',
902
+			$items_label
903
+		);
904
+
905
+		if (
906
+			empty($items_label)
907
+			|| ! is_array($items_label)
908
+			|| ! isset($items_label['single'], $items_label['plural'])
909
+		) {
910
+			$items_label = [
911
+				'single' => esc_html__('1 item', 'event_espresso'),
912
+				'plural' => esc_html__('%s items', 'event_espresso'),
913
+			];
914
+		} else {
915
+			$items_label = [
916
+				'single' => '1 ' . esc_html($items_label['single']),
917
+				'plural' => '%s ' . esc_html($items_label['plural']),
918
+			];
919
+		}
920
+
921
+		$total_pages = ceil($total_items / $per_page);
922
+
923
+		if ($total_pages <= 1) {
924
+			return '';
925
+		}
926
+
927
+		$item_label = $total_items > 1 ? sprintf($items_label['plural'], $total_items) : $items_label['single'];
928
+
929
+		$output = '<span class="displaying-num">' . $item_label . '</span>';
930
+
931
+		if ($current === 1) {
932
+			$disable_first = ' disabled';
933
+		}
934
+		if ($current === $total_pages) {
935
+			$disable_last = ' disabled';
936
+		}
937
+
938
+		$page_links[] = sprintf(
939
+			"<a class='%s' title='%s' href='%s'>%s</a>",
940
+			'first-page' . $disable_first,
941
+			esc_attr__('Go to the first page', 'event_espresso'),
942
+			esc_url_raw(remove_query_arg($paged_arg_name, $url)),
943
+			'&laquo;'
944
+		);
945
+
946
+		$page_links[] = sprintf(
947
+			'<a class="%s" title="%s" href="%s">%s</a>',
948
+			'prev-page' . $disable_first,
949
+			esc_attr__('Go to the previous page', 'event_espresso'),
950
+			esc_url_raw(add_query_arg($paged_arg_name, max(1, $current - 1), $url)),
951
+			'&lsaquo;'
952
+		);
953
+
954
+		if (! $show_num_field) {
955
+			$html_current_page = $current;
956
+		} else {
957
+			$html_current_page = sprintf(
958
+				"<input class='current-page' title='%s' type='text' name=$paged_arg_name value='%s' size='%d' />",
959
+				esc_attr__('Current page', 'event_espresso'),
960
+				esc_attr($current),
961
+				strlen($total_pages)
962
+			);
963
+		}
964
+
965
+		$html_total_pages = sprintf(
966
+			'<span class="total-pages">%s</span>',
967
+			number_format_i18n($total_pages)
968
+		);
969
+		$page_links[]     = sprintf(
970
+			_x('%3$s%1$s of %2$s%4$s', 'paging', 'event_espresso'),
971
+			$html_current_page,
972
+			$html_total_pages,
973
+			'<span class="paging-input">',
974
+			'</span>'
975
+		);
976
+
977
+		$page_links[] = sprintf(
978
+			'<a class="%s" title="%s" href="%s">%s</a>',
979
+			'next-page' . $disable_last,
980
+			esc_attr__('Go to the next page', 'event_espresso'),
981
+			esc_url_raw(add_query_arg($paged_arg_name, min($total_pages, $current + 1), $url)),
982
+			'&rsaquo;'
983
+		);
984
+
985
+		$page_links[] = sprintf(
986
+			'<a class="%s" title="%s" href="%s">%s</a>',
987
+			'last-page' . $disable_last,
988
+			esc_attr__('Go to the last page', 'event_espresso'),
989
+			esc_url_raw(add_query_arg($paged_arg_name, $total_pages, $url)),
990
+			'&raquo;'
991
+		);
992
+
993
+		$output .= "\n" . '<span class="pagination-links">' . join("\n", $page_links) . '</span>';
994
+		// set page class
995
+		if ($total_pages) {
996
+			$page_class = $total_pages < 2 ? ' one-page' : '';
997
+		} else {
998
+			$page_class = ' no-pages';
999
+		}
1000
+
1001
+		return '<div class="tablenav"><div class="tablenav-pages' . $page_class . '">' . $output . '</div></div>';
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * @param string $wrap_class
1007
+	 * @param string $wrap_id
1008
+	 * @param array  $query_args
1009
+	 * @return string
1010
+	 */
1011
+	public static function powered_by_event_espresso($wrap_class = '', $wrap_id = '', array $query_args = [])
1012
+	{
1013
+		$admin = is_admin() && ! (defined('DOING_AJAX') && DOING_AJAX);
1014
+		if (
1015
+			! $admin
1016
+			&& ! apply_filters(
1017
+				'FHEE__EEH_Template__powered_by_event_espresso__show_reg_footer',
1018
+				EE_Registry::instance()->CFG->admin->show_reg_footer
1019
+			)
1020
+		) {
1021
+			return '';
1022
+		}
1023
+		$tag        = $admin ? 'span' : 'div';
1024
+		$attributes = ! empty($wrap_id) ? " id=\"{$wrap_id}\"" : '';
1025
+		$wrap_class = $admin ? "{$wrap_class} float-left" : $wrap_class;
1026
+		$attributes .= ! empty($wrap_class)
1027
+			? " class=\"{$wrap_class} powered-by-event-espresso-credit\""
1028
+			: ' class="powered-by-event-espresso-credit"';
1029
+		$query_args = array_merge(
1030
+			[
1031
+				'ap_id'        => EE_Registry::instance()->CFG->admin->affiliate_id(),
1032
+				'utm_source'   => 'powered_by_event_espresso',
1033
+				'utm_medium'   => 'link',
1034
+				'utm_campaign' => 'powered_by',
1035
+			],
1036
+			$query_args
1037
+		);
1038
+		$powered_by = apply_filters(
1039
+			'FHEE__EEH_Template__powered_by_event_espresso_text',
1040
+			$admin ? 'Event Espresso - ' . EVENT_ESPRESSO_VERSION : 'Event Espresso'
1041
+		);
1042
+		$url        = add_query_arg($query_args, 'https://eventespresso.com/');
1043
+		$url        = apply_filters('FHEE__EEH_Template__powered_by_event_espresso__url', $url);
1044
+		return (string) apply_filters(
1045
+			'FHEE__EEH_Template__powered_by_event_espresso__html',
1046
+			sprintf(
1047
+				esc_html_x(
1048
+					'%3$s%1$sOnline event registration and ticketing powered by %2$s%3$s',
1049
+					'Online event registration and ticketing powered by [link to eventespresso.com]',
1050
+					'event_espresso'
1051
+				),
1052
+				"<{$tag}{$attributes}>",
1053
+				"<a href=\"{$url}\" target=\"_blank\" rel=\"nofollow\">{$powered_by}</a></{$tag}>",
1054
+				$admin ? '' : '<br />'
1055
+			),
1056
+			$wrap_class,
1057
+			$wrap_id
1058
+		);
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * @param string $image_name
1064
+	 * @return string|null
1065
+	 * @since   4.10.14.p
1066
+	 */
1067
+	public static function getScreenshotUrl($image_name)
1068
+	{
1069
+		return esc_url_raw(EE_GLOBAL_ASSETS_URL . 'images/screenshots/' . $image_name . '.jpg');
1070
+	}
1071 1071
 }
1072 1072
 
1073 1073
 
1074 1074
 if (! function_exists('espresso_pagination')) {
1075
-    /**
1076
-     *    espresso_pagination
1077
-     *
1078
-     * @access    public
1079
-     * @return    void
1080
-     */
1081
-    function espresso_pagination()
1082
-    {
1083
-        global $wp_query;
1084
-        $big        = 999999999; // need an unlikely integer
1085
-        $pagination = paginate_links(
1086
-            [
1087
-                'base'         => str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
1088
-                'format'       => '?paged=%#%',
1089
-                'current'      => max(1, get_query_var('paged')),
1090
-                'total'        => $wp_query->max_num_pages,
1091
-                'show_all'     => true,
1092
-                'end_size'     => 10,
1093
-                'mid_size'     => 6,
1094
-                'prev_next'    => true,
1095
-                'prev_text'    => esc_html__('&lsaquo; PREV', 'event_espresso'),
1096
-                'next_text'    => esc_html__('NEXT &rsaquo;', 'event_espresso'),
1097
-                'type'         => 'plain',
1098
-                'add_args'     => false,
1099
-                'add_fragment' => '',
1100
-            ]
1101
-        );
1102
-        echo ! empty($pagination) ? '<div class="ee-pagination-dv ee-clear-float">' . $pagination . '</div>' : '';
1103
-    }
1075
+	/**
1076
+	 *    espresso_pagination
1077
+	 *
1078
+	 * @access    public
1079
+	 * @return    void
1080
+	 */
1081
+	function espresso_pagination()
1082
+	{
1083
+		global $wp_query;
1084
+		$big        = 999999999; // need an unlikely integer
1085
+		$pagination = paginate_links(
1086
+			[
1087
+				'base'         => str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
1088
+				'format'       => '?paged=%#%',
1089
+				'current'      => max(1, get_query_var('paged')),
1090
+				'total'        => $wp_query->max_num_pages,
1091
+				'show_all'     => true,
1092
+				'end_size'     => 10,
1093
+				'mid_size'     => 6,
1094
+				'prev_next'    => true,
1095
+				'prev_text'    => esc_html__('&lsaquo; PREV', 'event_espresso'),
1096
+				'next_text'    => esc_html__('NEXT &rsaquo;', 'event_espresso'),
1097
+				'type'         => 'plain',
1098
+				'add_args'     => false,
1099
+				'add_fragment' => '',
1100
+			]
1101
+		);
1102
+		echo ! empty($pagination) ? '<div class="ee-pagination-dv ee-clear-float">' . $pagination . '</div>' : '';
1103
+	}
1104 1104
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -8,7 +8,7 @@  discard block
 block discarded – undo
8 8
 use EventEspresso\core\services\request\RequestInterface;
9 9
 use EventEspresso\core\services\request\sanitizers\AllowedTags;
10 10
 
11
-if (! function_exists('espresso_get_template_part')) {
11
+if ( ! function_exists('espresso_get_template_part')) {
12 12
     /**
13 13
      * espresso_get_template_part
14 14
      * basically a copy of the WordPress get_template_part() function but uses EEH_Template::locate_template() instead, and doesn't add base versions of files
@@ -24,7 +24,7 @@  discard block
 block discarded – undo
24 24
 }
25 25
 
26 26
 
27
-if (! function_exists('espresso_get_object_css_class')) {
27
+if ( ! function_exists('espresso_get_object_css_class')) {
28 28
     /**
29 29
      * espresso_get_object_css_class - attempts to generate a css class based on the type of EE object passed
30 30
      *
@@ -80,14 +80,14 @@  discard block
 block discarded – undo
80 80
     {
81 81
         $currencyISO = $currency_locale->currencyIsoCode();
82 82
         if (
83
-            ! isset(EEH_Template::$currency_config[ $currencyISO ])
84
-            || ! EEH_Template::$currency_config[ $currencyISO ] instanceof EE_Currency_Config
83
+            ! isset(EEH_Template::$currency_config[$currencyISO])
84
+            || ! EEH_Template::$currency_config[$currencyISO] instanceof EE_Currency_Config
85 85
         ) {
86 86
             $CNT_ISO = EEM_Country::instance()->getCountryISOForCurrencyISO($currencyISO);
87 87
             $currency_config = new EE_Currency_Config($CNT_ISO);
88
-            EEH_Template::$currency_config[ $currencyISO ] = $currency_config;
88
+            EEH_Template::$currency_config[$currencyISO] = $currency_config;
89 89
         }
90
-        return EEH_Template::$currency_config[ $currencyISO ];
90
+        return EEH_Template::$currency_config[$currencyISO];
91 91
     }
92 92
 
93 93
 
@@ -97,7 +97,7 @@  discard block
 block discarded – undo
97 97
      */
98 98
     private static function getCurrencyFormatter()
99 99
     {
100
-        if (! EEH_Template::$currency_formatter instanceof CurrencyFormatter) {
100
+        if ( ! EEH_Template::$currency_formatter instanceof CurrencyFormatter) {
101 101
             EEH_Template::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
102 102
         }
103 103
         return EEH_Template::$currency_formatter;
@@ -125,9 +125,9 @@  discard block
 block discarded – undo
125 125
     {
126 126
         if (
127 127
             ! defined('EE_THEME_FUNCTIONS_LOADED')
128
-            && is_readable(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php')
128
+            && is_readable(EE_PUBLIC.EE_Config::get_current_theme().'/functions.php')
129 129
         ) {
130
-            require_once(EE_PUBLIC . EE_Config::get_current_theme() . '/functions.php');
130
+            require_once(EE_PUBLIC.EE_Config::get_current_theme().'/functions.php');
131 131
         }
132 132
     }
133 133
 
@@ -140,16 +140,16 @@  discard block
 block discarded – undo
140 140
     public static function get_espresso_themes()
141 141
     {
142 142
         if (empty(EEH_Template::$_espresso_themes)) {
143
-            $espresso_themes = glob(EE_PUBLIC . '*', GLOB_ONLYDIR);
143
+            $espresso_themes = glob(EE_PUBLIC.'*', GLOB_ONLYDIR);
144 144
             if (empty($espresso_themes)) {
145 145
                 return [];
146 146
             }
147 147
             if (($key = array_search('global_assets', $espresso_themes, true)) !== false) {
148
-                unset($espresso_themes[ $key ]);
148
+                unset($espresso_themes[$key]);
149 149
             }
150 150
             EEH_Template::$_espresso_themes = [];
151 151
             foreach ($espresso_themes as $espresso_theme) {
152
-                EEH_Template::$_espresso_themes[ basename($espresso_theme) ] = $espresso_theme;
152
+                EEH_Template::$_espresso_themes[basename($espresso_theme)] = $espresso_theme;
153 153
             }
154 154
         }
155 155
         return EEH_Template::$_espresso_themes;
@@ -265,10 +265,10 @@  discard block
 block discarded – undo
265 265
                 // get array of EE Custom Post Types
266 266
                 $EE_CPTs = $custom_post_types->getDefinitions();
267 267
                 // build template name based on request
268
-                if (isset($EE_CPTs[ $post_type ])) {
268
+                if (isset($EE_CPTs[$post_type])) {
269 269
                     $archive_or_single = is_archive() ? 'archive' : '';
270 270
                     $archive_or_single = is_single() ? 'single' : $archive_or_single;
271
-                    $templates         = $archive_or_single . '-' . $post_type . '.php';
271
+                    $templates         = $archive_or_single.'-'.$post_type.'.php';
272 272
                 }
273 273
             }
274 274
             // currently active EE template theme
@@ -277,18 +277,18 @@  discard block
 block discarded – undo
277 277
             // array of paths to folders that may contain templates
278 278
             $template_folder_paths = [
279 279
                 // first check the /wp-content/uploads/espresso/templates/(current EE theme)/  folder for an EE theme template file
280
-                EVENT_ESPRESSO_TEMPLATE_DIR . $current_theme,
280
+                EVENT_ESPRESSO_TEMPLATE_DIR.$current_theme,
281 281
                 // then in the root of the /wp-content/uploads/espresso/templates/ folder
282 282
                 EVENT_ESPRESSO_TEMPLATE_DIR,
283 283
             ];
284 284
 
285 285
             // add core plugin folders for checking only if we're not $check_if_custom
286
-            if (! $check_if_custom) {
287
-                $core_paths            = [
286
+            if ( ! $check_if_custom) {
287
+                $core_paths = [
288 288
                     // in the  /wp-content/plugins/(EE4 folder)/public/(current EE theme)/ folder within the plugin
289
-                    EE_PUBLIC . $current_theme,
289
+                    EE_PUBLIC.$current_theme,
290 290
                     // in the  /wp-content/plugins/(EE4 folder)/core/templates/(current EE theme)/ folder within the plugin
291
-                    EE_TEMPLATES . $current_theme,
291
+                    EE_TEMPLATES.$current_theme,
292 292
                     // or maybe relative from the plugin root: /wp-content/plugins/(EE4 folder)/
293 293
                     EE_PLUGIN_DIR_PATH,
294 294
                 ];
@@ -323,10 +323,10 @@  discard block
 block discarded – undo
323 323
                     );
324 324
                     if ($common_base_path !== '') {
325 325
                         // both paths have a common base, so just tack the filename onto our search path
326
-                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $file_name;
326
+                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path).$file_name;
327 327
                     } else {
328 328
                         // no common base path, so let's just concatenate
329
-                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path) . $template;
329
+                        $resolved_path = EEH_File::end_with_directory_separator($template_folder_path).$template;
330 330
                     }
331 331
                     // build up our template locations array by adding our resolved paths
332 332
                     $full_template_paths[] = $resolved_path;
@@ -334,7 +334,7 @@  discard block
 block discarded – undo
334 334
                 // if $template is an absolute path, then we'll tack it onto the start of our array so that it gets searched first
335 335
                 array_unshift($full_template_paths, $template);
336 336
                 // path to the directory of the current theme: /wp-content/themes/(current WP theme)/
337
-                array_unshift($full_template_paths, get_stylesheet_directory() . '/' . $file_name);
337
+                array_unshift($full_template_paths, get_stylesheet_directory().'/'.$file_name);
338 338
             }
339 339
             // filter final array of full template paths
340 340
             $full_template_paths = apply_filters(
@@ -385,7 +385,7 @@  discard block
 block discarded – undo
385 385
                 }
386 386
             }
387 387
             $common_base_path .= $directory;
388
-            $last_offset      = $index + 1;
388
+            $last_offset = $index + 1;
389 389
         }
390 390
         return substr($common_base_path, 0, -1);
391 391
     }
@@ -422,7 +422,7 @@  discard block
 block discarded – undo
422 422
         $template_args = (array) apply_filters('FHEE__EEH_Template__display_template__template_args', $template_args);
423 423
 
424 424
         // you gimme nuttin - YOU GET NUTTIN !!
425
-        if (! $template_path || ! is_readable($template_path)) {
425
+        if ( ! $template_path || ! is_readable($template_path)) {
426 426
             // ignore whether template is accessible ?
427 427
             if ($throw_exceptions) {
428 428
                 throw new DomainException(
@@ -432,7 +432,7 @@  discard block
 block discarded – undo
432 432
             return '';
433 433
         }
434 434
         // if $template_args are not in an array, then make it so
435
-        if (! is_array($template_args) && ! is_object($template_args)) {
435
+        if ( ! is_array($template_args) && ! is_object($template_args)) {
436 436
             $template_args = [$template_args];
437 437
         }
438 438
         extract($template_args, EXTR_SKIP);
@@ -471,20 +471,20 @@  discard block
 block discarded – undo
471 471
     public static function get_object_css_class($object = null, $prefix = '', $suffix = '')
472 472
     {
473 473
         // in the beginning...
474
-        $prefix = ! empty($prefix) ? rtrim($prefix, '-') . '-' : '';
474
+        $prefix = ! empty($prefix) ? rtrim($prefix, '-').'-' : '';
475 475
         // da muddle
476 476
         $class = '';
477 477
         // the end
478
-        $suffix = ! empty($suffix) ? '-' . ltrim($suffix, '-') : '';
478
+        $suffix = ! empty($suffix) ? '-'.ltrim($suffix, '-') : '';
479 479
         // is the passed object an EE object ?
480 480
         if ($object instanceof EE_Base_Class) {
481 481
             // grab the exact type of object
482 482
             $obj_class = get_class($object);
483 483
             // depending on the type of object...
484 484
             $class = strtolower(str_replace('_', '-', $obj_class));
485
-            $class .= method_exists($obj_class, 'name') ? '-' . sanitize_title($object->name()) : '';
485
+            $class .= method_exists($obj_class, 'name') ? '-'.sanitize_title($object->name()) : '';
486 486
         }
487
-        return $prefix . $class . $suffix;
487
+        return $prefix.$class.$suffix;
488 488
     }
489 489
 
490 490
 
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
         // filter raw amount (allows 0.00 to be changed to "free" for example)
527 527
         $amount_formatted = apply_filters('FHEE__EEH_Template__format_currency__amount', $amount, $return_raw);
528 528
         // still a number or was amount converted to a string like "free" ?
529
-        if (! is_float($amount_formatted)) {
529
+        if ( ! is_float($amount_formatted)) {
530 530
             return esc_html($amount_formatted);
531 531
         }
532 532
 
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
             $plural,
596 596
             $schema
597 597
         );
598
-        return $status[ $status_id ];
598
+        return $status[$status_id];
599 599
     }
600 600
 
601 601
 
@@ -612,14 +612,14 @@  discard block
 block discarded – undo
612 612
     public static function get_button_or_link($url, $label, $class = 'button button--primary', $icon = '', $title = '')
613 613
     {
614 614
         $icon_html = '';
615
-        if (! empty($icon)) {
615
+        if ( ! empty($icon)) {
616 616
             $dashicons = preg_split("(ee-icon |dashicons )", $icon);
617 617
             $dashicons = array_filter($dashicons);
618 618
             $count     = count($dashicons);
619 619
             $icon_html .= $count > 1 ? '<span class="ee-composite-dashicon">' : '';
620 620
             foreach ($dashicons as $dashicon) {
621
-                $type      = strpos($dashicon, 'ee-icon') !== false ? 'ee-icon ' : 'dashicons ';
622
-                $icon_html .= '<span class="' . $type . $dashicon . '"></span>';
621
+                $type = strpos($dashicon, 'ee-icon') !== false ? 'ee-icon ' : 'dashicons ';
622
+                $icon_html .= '<span class="'.$type.$dashicon.'"></span>';
623 623
             }
624 624
             $icon_html .= $count > 1 ? '</span>' : '';
625 625
         }
@@ -657,16 +657,16 @@  discard block
 block discarded – undo
657 657
         $action  = $action ?: $request->getRequestParam('action', 'default', 'key');
658 658
 
659 659
 
660
-        $help_tab_lnk = $page . '-' . $action . '-' . $help_tab_id;
660
+        $help_tab_lnk = $page.'-'.$action.'-'.$help_tab_id;
661 661
         $icon = $icon_style ? $icon_style : ' dashicons-editor-help';
662 662
         $help_text = $help_text ? $help_text : '';
663
-        $link = '<a id="' . $help_tab_lnk . '"';
664
-        $link .= ' class="ee-clickable espresso-help-tab-lnk dashicons' . $icon . '"';
665
-        $link .= ' title="' .  esc_attr__(
663
+        $link = '<a id="'.$help_tab_lnk.'"';
664
+        $link .= ' class="ee-clickable espresso-help-tab-lnk dashicons'.$icon.'"';
665
+        $link .= ' title="'.esc_attr__(
666 666
             'Click to open the \'Help\' tab for more information about this feature.',
667 667
             'event_espresso'
668
-        ) . '"';
669
-        $link .= ' > ' . $help_text . ' </a>';
668
+        ).'"';
669
+        $link .= ' > '.$help_text.' </a>';
670 670
         return $link;
671 671
     }
672 672
 
@@ -685,11 +685,11 @@  discard block
 block discarded – undo
685 685
         $id    = $tour->get_slug();
686 686
         $stops = $tour->get_stops();
687 687
 
688
-        $content = '<ol style="display:none" id="' . $id . '">';
688
+        $content = '<ol style="display:none" id="'.$id.'">';
689 689
 
690 690
         foreach ($stops as $stop) {
691
-            $data_id    = ! empty($stop['id']) ? ' data-id="' . $stop['id'] . '"' : '';
692
-            $data_class = empty($data_id) && ! empty($stop['class']) ? ' data-class="' . $stop['class'] . '"' : '';
691
+            $data_id    = ! empty($stop['id']) ? ' data-id="'.$stop['id'].'"' : '';
692
+            $data_class = empty($data_id) && ! empty($stop['class']) ? ' data-class="'.$stop['class'].'"' : '';
693 693
 
694 694
             // if container is set to modal then let's make sure we set the options accordingly
695 695
             if (empty($data_id) && empty($data_class)) {
@@ -697,15 +697,15 @@  discard block
 block discarded – undo
697 697
                 $stop['options']['expose'] = true;
698 698
             }
699 699
 
700
-            $custom_class  = ! empty($stop['custom_class']) ? ' class="' . $stop['custom_class'] . '"' : '';
701
-            $button_text   = ! empty($stop['button_text']) ? ' data-button="' . $stop['button_text'] . '"' : '';
700
+            $custom_class  = ! empty($stop['custom_class']) ? ' class="'.$stop['custom_class'].'"' : '';
701
+            $button_text   = ! empty($stop['button_text']) ? ' data-button="'.$stop['button_text'].'"' : '';
702 702
             $inner_content = isset($stop['content']) ? $stop['content'] : '';
703 703
 
704 704
             // options
705 705
             if (isset($stop['options']) && is_array($stop['options'])) {
706 706
                 $options = ' data-options="';
707 707
                 foreach ($stop['options'] as $option => $value) {
708
-                    $options .= $option . ':' . $value . ';';
708
+                    $options .= $option.':'.$value.';';
709 709
                 }
710 710
                 $options .= '"';
711 711
             } else {
@@ -745,7 +745,7 @@  discard block
 block discarded – undo
745 745
      */
746 746
     public static function status_legend($status_array, $active_status = '')
747 747
     {
748
-        if (! is_array($status_array)) {
748
+        if ( ! is_array($status_array)) {
749 749
             throw new EE_Error(
750 750
                 esc_html__(
751 751
                     'The EEH_Template::status_legend helper required the incoming status_array argument to be an array!',
@@ -757,17 +757,17 @@  discard block
 block discarded – undo
757 757
         $content = '
758 758
             <div class="ee-list-table-legend-container">
759 759
                 <h4 class="status-legend-title">
760
-                    ' . esc_html__('Status Legend', 'event_espresso') . '
760
+                    ' . esc_html__('Status Legend', 'event_espresso').'
761 761
                 </h4>
762 762
                 <dl class="ee-list-table-legend">';
763 763
 
764 764
         foreach ($status_array as $item => $status) {
765 765
             $active_class = $active_status == $status ? 'class="ee-is-active-status"' : '';
766
-            $content      .= '
767
-                    <dt id="' . esc_attr('ee-legend-item-tooltip-' . $item) . '" ' . $active_class . '>
768
-                        <span class="' . esc_attr('ee-status-legend ee-status-legend-' . $status) . '"></span>
766
+            $content .= '
767
+                    <dt id="' . esc_attr('ee-legend-item-tooltip-'.$item).'" '.$active_class.'>
768
+                        <span class="' . esc_attr('ee-status-legend ee-status-legend-'.$status).'"></span>
769 769
                         <span class="ee-legend-description">
770
-                            ' . EEH_Template::pretty_status($status, false, 'sentence') . '
770
+                            ' . EEH_Template::pretty_status($status, false, 'sentence').'
771 771
                         </span>
772 772
                     </dt>';
773 773
         }
@@ -913,8 +913,8 @@  discard block
 block discarded – undo
913 913
             ];
914 914
         } else {
915 915
             $items_label = [
916
-                'single' => '1 ' . esc_html($items_label['single']),
917
-                'plural' => '%s ' . esc_html($items_label['plural']),
916
+                'single' => '1 '.esc_html($items_label['single']),
917
+                'plural' => '%s '.esc_html($items_label['plural']),
918 918
             ];
919 919
         }
920 920
 
@@ -926,7 +926,7 @@  discard block
 block discarded – undo
926 926
 
927 927
         $item_label = $total_items > 1 ? sprintf($items_label['plural'], $total_items) : $items_label['single'];
928 928
 
929
-        $output = '<span class="displaying-num">' . $item_label . '</span>';
929
+        $output = '<span class="displaying-num">'.$item_label.'</span>';
930 930
 
931 931
         if ($current === 1) {
932 932
             $disable_first = ' disabled';
@@ -937,7 +937,7 @@  discard block
 block discarded – undo
937 937
 
938 938
         $page_links[] = sprintf(
939 939
             "<a class='%s' title='%s' href='%s'>%s</a>",
940
-            'first-page' . $disable_first,
940
+            'first-page'.$disable_first,
941 941
             esc_attr__('Go to the first page', 'event_espresso'),
942 942
             esc_url_raw(remove_query_arg($paged_arg_name, $url)),
943 943
             '&laquo;'
@@ -945,13 +945,13 @@  discard block
 block discarded – undo
945 945
 
946 946
         $page_links[] = sprintf(
947 947
             '<a class="%s" title="%s" href="%s">%s</a>',
948
-            'prev-page' . $disable_first,
948
+            'prev-page'.$disable_first,
949 949
             esc_attr__('Go to the previous page', 'event_espresso'),
950 950
             esc_url_raw(add_query_arg($paged_arg_name, max(1, $current - 1), $url)),
951 951
             '&lsaquo;'
952 952
         );
953 953
 
954
-        if (! $show_num_field) {
954
+        if ( ! $show_num_field) {
955 955
             $html_current_page = $current;
956 956
         } else {
957 957
             $html_current_page = sprintf(
@@ -966,7 +966,7 @@  discard block
 block discarded – undo
966 966
             '<span class="total-pages">%s</span>',
967 967
             number_format_i18n($total_pages)
968 968
         );
969
-        $page_links[]     = sprintf(
969
+        $page_links[] = sprintf(
970 970
             _x('%3$s%1$s of %2$s%4$s', 'paging', 'event_espresso'),
971 971
             $html_current_page,
972 972
             $html_total_pages,
@@ -976,7 +976,7 @@  discard block
 block discarded – undo
976 976
 
977 977
         $page_links[] = sprintf(
978 978
             '<a class="%s" title="%s" href="%s">%s</a>',
979
-            'next-page' . $disable_last,
979
+            'next-page'.$disable_last,
980 980
             esc_attr__('Go to the next page', 'event_espresso'),
981 981
             esc_url_raw(add_query_arg($paged_arg_name, min($total_pages, $current + 1), $url)),
982 982
             '&rsaquo;'
@@ -984,13 +984,13 @@  discard block
 block discarded – undo
984 984
 
985 985
         $page_links[] = sprintf(
986 986
             '<a class="%s" title="%s" href="%s">%s</a>',
987
-            'last-page' . $disable_last,
987
+            'last-page'.$disable_last,
988 988
             esc_attr__('Go to the last page', 'event_espresso'),
989 989
             esc_url_raw(add_query_arg($paged_arg_name, $total_pages, $url)),
990 990
             '&raquo;'
991 991
         );
992 992
 
993
-        $output .= "\n" . '<span class="pagination-links">' . join("\n", $page_links) . '</span>';
993
+        $output .= "\n".'<span class="pagination-links">'.join("\n", $page_links).'</span>';
994 994
         // set page class
995 995
         if ($total_pages) {
996 996
             $page_class = $total_pages < 2 ? ' one-page' : '';
@@ -998,7 +998,7 @@  discard block
 block discarded – undo
998 998
             $page_class = ' no-pages';
999 999
         }
1000 1000
 
1001
-        return '<div class="tablenav"><div class="tablenav-pages' . $page_class . '">' . $output . '</div></div>';
1001
+        return '<div class="tablenav"><div class="tablenav-pages'.$page_class.'">'.$output.'</div></div>';
1002 1002
     }
1003 1003
 
1004 1004
 
@@ -1037,7 +1037,7 @@  discard block
 block discarded – undo
1037 1037
         );
1038 1038
         $powered_by = apply_filters(
1039 1039
             'FHEE__EEH_Template__powered_by_event_espresso_text',
1040
-            $admin ? 'Event Espresso - ' . EVENT_ESPRESSO_VERSION : 'Event Espresso'
1040
+            $admin ? 'Event Espresso - '.EVENT_ESPRESSO_VERSION : 'Event Espresso'
1041 1041
         );
1042 1042
         $url        = add_query_arg($query_args, 'https://eventespresso.com/');
1043 1043
         $url        = apply_filters('FHEE__EEH_Template__powered_by_event_espresso__url', $url);
@@ -1066,12 +1066,12 @@  discard block
 block discarded – undo
1066 1066
      */
1067 1067
     public static function getScreenshotUrl($image_name)
1068 1068
     {
1069
-        return esc_url_raw(EE_GLOBAL_ASSETS_URL . 'images/screenshots/' . $image_name . '.jpg');
1069
+        return esc_url_raw(EE_GLOBAL_ASSETS_URL.'images/screenshots/'.$image_name.'.jpg');
1070 1070
     }
1071 1071
 }
1072 1072
 
1073 1073
 
1074
-if (! function_exists('espresso_pagination')) {
1074
+if ( ! function_exists('espresso_pagination')) {
1075 1075
     /**
1076 1076
      *    espresso_pagination
1077 1077
      *
@@ -1099,6 +1099,6 @@  discard block
 block discarded – undo
1099 1099
                 'add_fragment' => '',
1100 1100
             ]
1101 1101
         );
1102
-        echo ! empty($pagination) ? '<div class="ee-pagination-dv ee-clear-float">' . $pagination . '</div>' : '';
1102
+        echo ! empty($pagination) ? '<div class="ee-pagination-dv ee-clear-float">'.$pagination.'</div>' : '';
1103 1103
     }
1104 1104
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +2105 added lines, -2105 removed lines patch added patch discarded remove patch
@@ -23,2109 +23,2109 @@
 block discarded – undo
23 23
  */
24 24
 class EEH_Line_Item
25 25
 {
26
-    /**
27
-     * @return CurrencyFormatter
28
-     * @since   $VID:$
29
-     */
30
-    private static function currencyFormatter()
31
-    {
32
-        static $currency_formatter;
33
-        if (! $currency_formatter instanceof CurrencyFormatter) {
34
-            $currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
35
-        }
36
-        return $currency_formatter;
37
-    }
38
-
39
-    /**
40
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
41
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
42
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
43
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
44
-     * to keep the registration final prices in-sync with the transaction's total.
45
-     *
46
-     * @param EE_Line_Item $parent_line_item
47
-     * @param string       $name
48
-     * @param float        $unit_price
49
-     * @param string       $description
50
-     * @param int          $quantity
51
-     * @param boolean      $taxable
52
-     * @param boolean      $code if set to a value, ensures there is only one line item with that code
53
-     * @return boolean success
54
-     * @throws EE_Error
55
-     * @throws InvalidArgumentException
56
-     * @throws InvalidDataTypeException
57
-     * @throws InvalidInterfaceException
58
-     * @throws ReflectionException
59
-     */
60
-    public static function add_unrelated_item(
61
-        EE_Line_Item $parent_line_item,
62
-        $name,
63
-        $unit_price,
64
-        $description = '',
65
-        $quantity = 1,
66
-        $taxable = false,
67
-        $code = null
68
-    ) {
69
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
70
-        $line_item      = EE_Line_Item::new_instance(
71
-            [
72
-                'LIN_name'       => $name,
73
-                'LIN_desc'       => $description,
74
-                'LIN_unit_price' => $unit_price,
75
-                'LIN_quantity'   => $quantity,
76
-                'LIN_percent'    => null,
77
-                'LIN_is_taxable' => $taxable,
78
-                'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
79
-                'LIN_total'      => EEH_Line_Item::currencyFormatter()->roundForLocale(
80
-                    (float) $unit_price * (int) $quantity
81
-                ),
82
-                'LIN_type'       => EEM_Line_Item::type_line_item,
83
-                'LIN_code'       => $code,
84
-            ]
85
-        );
86
-        $line_item      = apply_filters(
87
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
88
-            $line_item,
89
-            $parent_line_item
90
-        );
91
-        return self::add_item($parent_line_item, $line_item);
92
-    }
93
-
94
-
95
-    /**
96
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
97
-     * in the correct spot in the line item tree. Does not automatically
98
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
99
-     * registrations' final prices (which should probably change because of this).
100
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
101
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
102
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
103
-     *
104
-     * @param EE_Line_Item $parent_line_item
105
-     * @param string       $name
106
-     * @param float        $percentage_amount
107
-     * @param string       $description
108
-     * @param boolean      $taxable
109
-     * @return boolean success
110
-     * @throws EE_Error|ReflectionException
111
-     */
112
-    public static function add_percentage_based_item(
113
-        EE_Line_Item $parent_line_item,
114
-        $name,
115
-        $percentage_amount,
116
-        $description = '',
117
-        $taxable = false
118
-    ) {
119
-        $line_item = EE_Line_Item::new_instance(
120
-            [
121
-                'LIN_name'       => $name,
122
-                'LIN_desc'       => $description,
123
-                'LIN_unit_price' => 0,
124
-                'LIN_percent'    => $percentage_amount,
125
-                'LIN_quantity'   => 1,
126
-                'LIN_is_taxable' => $taxable,
127
-                'LIN_total'      => EEH_Line_Item::currencyFormatter()->roundForLocale(
128
-                    $percentage_amount * $parent_line_item->total() / 100
129
-                ),
130
-                'LIN_type'       => EEM_Line_Item::type_line_item,
131
-                'LIN_parent'     => $parent_line_item->ID(),
132
-            ]
133
-        );
134
-        $line_item = apply_filters(
135
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
136
-            $line_item
137
-        );
138
-        return $parent_line_item->add_child_line_item($line_item, false);
139
-    }
140
-
141
-
142
-    /**
143
-     * Returns the new line item created by adding a purchase of the ticket
144
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
145
-     * If this ticket has already been purchased, just increments its count.
146
-     * Automatically re-calculates the line item totals and updates the related transaction. But
147
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
148
-     * should probably change because of this).
149
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
150
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
151
-     *
152
-     * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
153
-     * @param EE_Ticket    $ticket
154
-     * @param int          $qty
155
-     * @return EE_Line_Item
156
-     * @throws EE_Error
157
-     * @throws InvalidArgumentException
158
-     * @throws InvalidDataTypeException
159
-     * @throws InvalidInterfaceException
160
-     * @throws ReflectionException
161
-     */
162
-    public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
163
-    {
164
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
165
-            throw new EE_Error(
166
-                sprintf(
167
-                    esc_html__(
168
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
169
-                        'event_espresso'
170
-                    ),
171
-                    $ticket->ID(),
172
-                    $total_line_item->ID()
173
-                )
174
-            );
175
-        }
176
-        // either increment the qty for an existing ticket
177
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
178
-        // or add a new one
179
-        if (! $line_item instanceof EE_Line_Item) {
180
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
181
-        }
182
-        $total_line_item->recalculate_total_including_taxes();
183
-        return $line_item;
184
-    }
185
-
186
-
187
-    /**
188
-     * Returns the new line item created by adding a purchase of the ticket
189
-     *
190
-     * @param EE_Line_Item $total_line_item
191
-     * @param EE_Ticket    $ticket
192
-     * @param int          $qty
193
-     * @return EE_Line_Item
194
-     * @throws EE_Error
195
-     * @throws InvalidArgumentException
196
-     * @throws InvalidDataTypeException
197
-     * @throws InvalidInterfaceException
198
-     * @throws ReflectionException
199
-     */
200
-    public static function increment_ticket_qty_if_already_in_cart(
201
-        EE_Line_Item $total_line_item,
202
-        EE_Ticket $ticket,
203
-        $qty = 1
204
-    ) {
205
-        $line_item = null;
206
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
207
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
208
-            foreach ((array) $ticket_line_items as $ticket_line_item) {
209
-                if (
210
-                    $ticket_line_item instanceof EE_Line_Item
211
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
212
-                ) {
213
-                    $line_item = $ticket_line_item;
214
-                    break;
215
-                }
216
-            }
217
-        }
218
-        if ($line_item instanceof EE_Line_Item) {
219
-            EEH_Line_Item::increment_quantity($line_item, $qty);
220
-            return $line_item;
221
-        }
222
-        return null;
223
-    }
224
-
225
-
226
-    /**
227
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
228
-     * Does NOT save or recalculate other line items totals
229
-     *
230
-     * @param EE_Line_Item $line_item
231
-     * @param int          $qty
232
-     * @return void
233
-     * @throws EE_Error
234
-     * @throws InvalidArgumentException
235
-     * @throws InvalidDataTypeException
236
-     * @throws InvalidInterfaceException
237
-     * @throws ReflectionException
238
-     */
239
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
240
-    {
241
-        if (! $line_item->is_percent()) {
242
-            $qty += $line_item->quantity();
243
-            $line_item->set_quantity($qty);
244
-            $line_item->set_total($line_item->unit_price() * $qty);
245
-            $line_item->save();
246
-        }
247
-        foreach ($line_item->children() as $child) {
248
-            if ($child->is_sub_line_item()) {
249
-                EEH_Line_Item::update_quantity($child, $qty);
250
-            }
251
-        }
252
-    }
253
-
254
-
255
-    /**
256
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
257
-     * Does NOT save or recalculate other line items totals
258
-     *
259
-     * @param EE_Line_Item $line_item
260
-     * @param int          $qty
261
-     * @return void
262
-     * @throws EE_Error
263
-     * @throws InvalidArgumentException
264
-     * @throws InvalidDataTypeException
265
-     * @throws InvalidInterfaceException
266
-     * @throws ReflectionException
267
-     */
268
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
269
-    {
270
-        if (! $line_item->is_percent()) {
271
-            $qty = $line_item->quantity() - $qty;
272
-            $qty = max($qty, 0);
273
-            $line_item->set_quantity($qty);
274
-            $line_item->set_total($line_item->unit_price() * $qty);
275
-            $line_item->save();
276
-        }
277
-        foreach ($line_item->children() as $child) {
278
-            if ($child->is_sub_line_item()) {
279
-                EEH_Line_Item::update_quantity($child, $qty);
280
-            }
281
-        }
282
-    }
283
-
284
-
285
-    /**
286
-     * Updates the line item and its children's quantities to the specified number.
287
-     * Does NOT save them or recalculate totals.
288
-     *
289
-     * @param EE_Line_Item $line_item
290
-     * @param int          $new_quantity
291
-     * @throws EE_Error
292
-     * @throws InvalidArgumentException
293
-     * @throws InvalidDataTypeException
294
-     * @throws InvalidInterfaceException
295
-     * @throws ReflectionException
296
-     */
297
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
298
-    {
299
-        if (! $line_item->is_percent()) {
300
-            $line_item->set_quantity($new_quantity);
301
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
302
-            $line_item->save();
303
-        }
304
-        foreach ($line_item->children() as $child) {
305
-            if ($child->is_sub_line_item()) {
306
-                EEH_Line_Item::update_quantity($child, $new_quantity);
307
-            }
308
-        }
309
-    }
310
-
311
-
312
-    /**
313
-     * Returns the new line item created by adding a purchase of the ticket
314
-     *
315
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
316
-     * @param EE_Ticket    $ticket
317
-     * @param int          $qty
318
-     * @return EE_Line_Item
319
-     * @throws EE_Error
320
-     * @throws InvalidArgumentException
321
-     * @throws InvalidDataTypeException
322
-     * @throws InvalidInterfaceException
323
-     * @throws ReflectionException
324
-     */
325
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
326
-    {
327
-        $datetimes           = $ticket->datetimes();
328
-        $first_datetime      = reset($datetimes);
329
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
330
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
331
-            $first_datetime_name = $first_datetime->event()->name();
332
-        }
333
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
334
-        // get event subtotal line
335
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
336
-        // add $ticket to cart
337
-        $line_item = EE_Line_Item::new_instance(
338
-            [
339
-                'LIN_name'       => $ticket->name(),
340
-                'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
341
-                'LIN_unit_price' => $ticket->price(),
342
-                'LIN_quantity'   => $qty,
343
-                'LIN_is_taxable' => $ticket->taxable(),
344
-                'LIN_order'      => count($events_sub_total->children()),
345
-                'LIN_total'      => $ticket->price() * $qty,
346
-                'LIN_type'       => EEM_Line_Item::type_line_item,
347
-                'OBJ_ID'         => $ticket->ID(),
348
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
349
-            ]
350
-        );
351
-        $line_item = apply_filters(
352
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
353
-            $line_item
354
-        );
355
-        $events_sub_total->add_child_line_item($line_item);
356
-        // now add the sub-line items
357
-        $running_total_for_ticket = 0;
358
-        foreach ($ticket->prices(['order_by' => ['PRC_order' => 'ASC']]) as $price) {
359
-            $sign          = $price->is_discount() ? -1 : 1;
360
-            $price_total   = $price->is_percent()
361
-                ? $sign * $running_total_for_ticket * $price->amount() / 100
362
-                : $sign * $price->amount() * $qty;
363
-            $price_total = EEH_Line_Item::currencyFormatter()->roundForLocale($price_total);
364
-            $sub_line_item = EE_Line_Item::new_instance(
365
-                [
366
-                    'LIN_name'       => $price->name(),
367
-                    'LIN_desc'       => $price->desc(),
368
-                    'LIN_quantity'   => $price->is_percent() ? null : $qty,
369
-                    'LIN_is_taxable' => false,
370
-                    'LIN_order'      => $price->order(),
371
-                    'LIN_total'      => $price_total,
372
-                    'LIN_type'       => EEM_Line_Item::type_sub_line_item,
373
-                    'OBJ_ID'         => $price->ID(),
374
-                    'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
375
-                ]
376
-            );
377
-            /** @var EE_Line_Item $sub_line_item */
378
-            $sub_line_item = apply_filters(
379
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
380
-                $sub_line_item
381
-            );
382
-            if ($price->is_percent()) {
383
-                $sub_line_item->set_percent($sign * $price->amount());
384
-            } else {
385
-                $sub_line_item->set_unit_price($sign * $price->amount());
386
-            }
387
-            $running_total_for_ticket += $price_total;
388
-            $line_item->add_child_line_item($sub_line_item);
389
-        }
390
-        return $line_item;
391
-    }
392
-
393
-
394
-    /**
395
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
396
-     * re-calculates the line item totals and updates the related transaction. But
397
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
398
-     * should probably change because of this).
399
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
400
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
401
-     *
402
-     * @param EE_Line_Item $total_line_item
403
-     * @param EE_Line_Item $item to be added
404
-     * @return boolean
405
-     * @throws EE_Error
406
-     * @throws InvalidArgumentException
407
-     * @throws InvalidDataTypeException
408
-     * @throws InvalidInterfaceException
409
-     * @throws ReflectionException
410
-     */
411
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
412
-    {
413
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
414
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
415
-            $success = $pre_tax_subtotal->add_child_line_item($item);
416
-        } else {
417
-            return false;
418
-        }
419
-        $total_line_item->recalculate_total_including_taxes();
420
-        return $success;
421
-    }
422
-
423
-
424
-    /**
425
-     * cancels an existing ticket line item,
426
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
427
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
428
-     *
429
-     * @param EE_Line_Item $ticket_line_item
430
-     * @param int          $qty
431
-     * @return bool success
432
-     * @throws EE_Error
433
-     * @throws InvalidArgumentException
434
-     * @throws InvalidDataTypeException
435
-     * @throws InvalidInterfaceException
436
-     * @throws ReflectionException
437
-     */
438
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
439
-    {
440
-        // validate incoming line_item
441
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
442
-            throw new EE_Error(
443
-                sprintf(
444
-                    esc_html__(
445
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
446
-                        'event_espresso'
447
-                    ),
448
-                    $ticket_line_item->type()
449
-                )
450
-            );
451
-        }
452
-        if ($ticket_line_item->quantity() < $qty) {
453
-            throw new EE_Error(
454
-                sprintf(
455
-                    esc_html__(
456
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
457
-                        'event_espresso'
458
-                    ),
459
-                    $qty,
460
-                    $ticket_line_item->quantity()
461
-                )
462
-            );
463
-        }
464
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
465
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
466
-        foreach ($ticket_line_item->children() as $child_line_item) {
467
-            if (
468
-                $child_line_item->is_sub_line_item()
469
-                && ! $child_line_item->is_percent()
470
-                && ! $child_line_item->is_cancellation()
471
-            ) {
472
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
473
-            }
474
-        }
475
-        // get cancellation sub line item
476
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
477
-            $ticket_line_item,
478
-            EEM_Line_Item::type_cancellation
479
-        );
480
-        $cancellation_line_item = reset($cancellation_line_item);
481
-        // verify that this ticket was indeed previously cancelled
482
-        if ($cancellation_line_item instanceof EE_Line_Item) {
483
-            // increment cancelled quantity
484
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
485
-        } else {
486
-            // create cancellation sub line item
487
-            $cancellation_line_item = EE_Line_Item::new_instance(
488
-                [
489
-                    'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
490
-                    'LIN_desc'       => sprintf(
491
-                        esc_html_x(
492
-                            'Cancelled %1$s : %2$s',
493
-                            'Cancelled Ticket Name : 2015-01-01 11:11',
494
-                            'event_espresso'
495
-                        ),
496
-                        $ticket_line_item->name(),
497
-                        current_time(get_option('date_format') . ' ' . get_option('time_format'))
498
-                    ),
499
-                    'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
500
-                    'LIN_quantity'   => $qty,
501
-                    'LIN_is_taxable' => $ticket_line_item->is_taxable(),
502
-                    'LIN_order'      => count($ticket_line_item->children()),
503
-                    'LIN_total'      => 0, // $ticket_line_item->unit_price()
504
-                    'LIN_type'       => EEM_Line_Item::type_cancellation,
505
-                ]
506
-            );
507
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
508
-        }
509
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
510
-            // decrement parent line item quantity
511
-            $event_line_item = $ticket_line_item->parent();
512
-            if (
513
-                $event_line_item instanceof EE_Line_Item
514
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
515
-            ) {
516
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
517
-                $event_line_item->save();
518
-            }
519
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
520
-            return true;
521
-        }
522
-        return false;
523
-    }
524
-
525
-
526
-    /**
527
-     * reinstates (un-cancels?) a previously canceled ticket line item,
528
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
529
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
530
-     *
531
-     * @param EE_Line_Item $ticket_line_item
532
-     * @param int          $qty
533
-     * @return bool success
534
-     * @throws EE_Error
535
-     * @throws InvalidArgumentException
536
-     * @throws InvalidDataTypeException
537
-     * @throws InvalidInterfaceException
538
-     * @throws ReflectionException
539
-     */
540
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
541
-    {
542
-        // validate incoming line_item
543
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
544
-            throw new EE_Error(
545
-                sprintf(
546
-                    esc_html__(
547
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
548
-                        'event_espresso'
549
-                    ),
550
-                    $ticket_line_item->type()
551
-                )
552
-            );
553
-        }
554
-        // get cancellation sub line item
555
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
556
-            $ticket_line_item,
557
-            EEM_Line_Item::type_cancellation
558
-        );
559
-        $cancellation_line_item = reset($cancellation_line_item);
560
-        // verify that this ticket was indeed previously cancelled
561
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
562
-            return false;
563
-        }
564
-        if ($cancellation_line_item->quantity() > $qty) {
565
-            // decrement cancelled quantity
566
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
567
-        } elseif ($cancellation_line_item->quantity() === $qty) {
568
-            // decrement cancelled quantity in case anyone still has the object kicking around
569
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
570
-            // delete because quantity will end up as 0
571
-            $cancellation_line_item->delete();
572
-            // and attempt to destroy the object,
573
-            // even though PHP won't actually destroy it until it needs the memory
574
-            unset($cancellation_line_item);
575
-        } else {
576
-            // what ?!?! negative quantity ?!?!
577
-            throw new EE_Error(
578
-                sprintf(
579
-                    esc_html__(
580
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
581
-                        'event_espresso'
582
-                    ),
583
-                    $qty,
584
-                    $cancellation_line_item->quantity()
585
-                )
586
-            );
587
-        }
588
-        // increment ticket quantity
589
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
590
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
591
-            // increment parent line item quantity
592
-            $event_line_item = $ticket_line_item->parent();
593
-            if (
594
-                $event_line_item instanceof EE_Line_Item
595
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
596
-            ) {
597
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
598
-            }
599
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
600
-            return true;
601
-        }
602
-        return false;
603
-    }
604
-
605
-
606
-    /**
607
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
608
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
609
-     *
610
-     * @param EE_Line_Item $line_item
611
-     * @return float
612
-     * @throws EE_Error
613
-     * @throws InvalidArgumentException
614
-     * @throws InvalidDataTypeException
615
-     * @throws InvalidInterfaceException
616
-     * @throws ReflectionException
617
-     */
618
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
619
-    {
620
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
621
-        return $grand_total_line_item->recalculate_total_including_taxes();
622
-    }
623
-
624
-
625
-    /**
626
-     * Gets the line item which contains the subtotal of all the items
627
-     *
628
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
629
-     * @return EE_Line_Item
630
-     * @throws EE_Error
631
-     * @throws InvalidArgumentException
632
-     * @throws InvalidDataTypeException
633
-     * @throws InvalidInterfaceException
634
-     * @throws ReflectionException
635
-     */
636
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
637
-    {
638
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
639
-        return $pre_tax_subtotal instanceof EE_Line_Item
640
-            ? $pre_tax_subtotal
641
-            : self::create_pre_tax_subtotal($total_line_item);
642
-    }
643
-
644
-
645
-    /**
646
-     * Gets the line item for the taxes subtotal
647
-     *
648
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
649
-     * @return EE_Line_Item
650
-     * @throws EE_Error
651
-     * @throws InvalidArgumentException
652
-     * @throws InvalidDataTypeException
653
-     * @throws InvalidInterfaceException
654
-     * @throws ReflectionException
655
-     */
656
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
657
-    {
658
-        $taxes = $total_line_item->get_child_line_item('taxes');
659
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
660
-    }
661
-
662
-
663
-    /**
664
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
665
-     *
666
-     * @param EE_Line_Item   $line_item
667
-     * @param EE_Transaction $transaction
668
-     * @return void
669
-     * @throws EE_Error
670
-     * @throws InvalidArgumentException
671
-     * @throws InvalidDataTypeException
672
-     * @throws InvalidInterfaceException
673
-     * @throws ReflectionException
674
-     */
675
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
676
-    {
677
-        if ($transaction) {
678
-            /** @type EEM_Transaction $EEM_Transaction */
679
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
680
-            $TXN_ID          = $EEM_Transaction->ensure_is_ID($transaction);
681
-            $line_item->set_TXN_ID($TXN_ID);
682
-        }
683
-    }
684
-
685
-
686
-    /**
687
-     * Creates a new default total line item for the transaction,
688
-     * and its tickets subtotal and taxes subtotal line items (and adds the
689
-     * existing taxes as children of the taxes subtotal line item)
690
-     *
691
-     * @param EE_Transaction $transaction
692
-     * @return EE_Line_Item of type total
693
-     * @throws EE_Error
694
-     * @throws InvalidArgumentException
695
-     * @throws InvalidDataTypeException
696
-     * @throws InvalidInterfaceException
697
-     * @throws ReflectionException
698
-     */
699
-    public static function create_total_line_item($transaction = null)
700
-    {
701
-        $total_line_item = EE_Line_Item::new_instance(
702
-            [
703
-                'LIN_code' => 'total',
704
-                'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
705
-                'LIN_type' => EEM_Line_Item::type_total,
706
-                'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
707
-            ]
708
-        );
709
-        $total_line_item = apply_filters(
710
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
711
-            $total_line_item
712
-        );
713
-        self::set_TXN_ID($total_line_item, $transaction);
714
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
715
-        self::create_taxes_subtotal($total_line_item, $transaction);
716
-        return $total_line_item;
717
-    }
718
-
719
-
720
-    /**
721
-     * Creates a default items subtotal line item
722
-     *
723
-     * @param EE_Line_Item   $total_line_item
724
-     * @param EE_Transaction $transaction
725
-     * @return EE_Line_Item
726
-     * @throws EE_Error
727
-     * @throws InvalidArgumentException
728
-     * @throws InvalidDataTypeException
729
-     * @throws InvalidInterfaceException
730
-     * @throws ReflectionException
731
-     */
732
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
733
-    {
734
-        $pre_tax_line_item = EE_Line_Item::new_instance(
735
-            [
736
-                'LIN_code' => 'pre-tax-subtotal',
737
-                'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
738
-                'LIN_type' => EEM_Line_Item::type_sub_total,
739
-            ]
740
-        );
741
-        $pre_tax_line_item = apply_filters(
742
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
743
-            $pre_tax_line_item
744
-        );
745
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
746
-        $total_line_item->add_child_line_item($pre_tax_line_item);
747
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
748
-        return $pre_tax_line_item;
749
-    }
750
-
751
-
752
-    /**
753
-     * Creates a line item for the taxes subtotal and finds all the tax prices
754
-     * and applies taxes to it
755
-     *
756
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
757
-     * @param EE_Transaction $transaction
758
-     * @return EE_Line_Item
759
-     * @throws EE_Error
760
-     * @throws InvalidArgumentException
761
-     * @throws InvalidDataTypeException
762
-     * @throws InvalidInterfaceException
763
-     * @throws ReflectionException
764
-     */
765
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
766
-    {
767
-        $tax_line_item = EE_Line_Item::new_instance(
768
-            [
769
-                'LIN_code'  => 'taxes',
770
-                'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
771
-                'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
772
-                'LIN_order' => 1000,// this should always come last
773
-            ]
774
-        );
775
-        $tax_line_item = apply_filters(
776
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
777
-            $tax_line_item
778
-        );
779
-        self::set_TXN_ID($tax_line_item, $transaction);
780
-        $total_line_item->add_child_line_item($tax_line_item);
781
-        // and lastly, add the actual taxes
782
-        self::apply_taxes($total_line_item);
783
-        return $tax_line_item;
784
-    }
785
-
786
-
787
-    /**
788
-     * Creates a default items subtotal line item
789
-     *
790
-     * @param EE_Line_Item   $pre_tax_line_item
791
-     * @param EE_Transaction $transaction
792
-     * @param EE_Event       $event
793
-     * @return EE_Line_Item
794
-     * @throws EE_Error
795
-     * @throws InvalidArgumentException
796
-     * @throws InvalidDataTypeException
797
-     * @throws InvalidInterfaceException
798
-     * @throws ReflectionException
799
-     */
800
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
801
-    {
802
-        $event_line_item = EE_Line_Item::new_instance(
803
-            [
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
-        );
812
-        $event_line_item = apply_filters(
813
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
814
-            $event_line_item
815
-        );
816
-        self::set_TXN_ID($event_line_item, $transaction);
817
-        $pre_tax_line_item->add_child_line_item($event_line_item);
818
-        return $event_line_item;
819
-    }
820
-
821
-
822
-    /**
823
-     * Gets what the event ticket's code SHOULD be
824
-     *
825
-     * @param EE_Event $event
826
-     * @return string
827
-     * @throws EE_Error|ReflectionException
828
-     */
829
-    public static function get_event_code($event)
830
-    {
831
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
832
-    }
833
-
834
-
835
-    /**
836
-     * Gets the event name
837
-     *
838
-     * @param EE_Event $event
839
-     * @return string
840
-     * @throws EE_Error
841
-     */
842
-    public static function get_event_name($event)
843
-    {
844
-        return $event instanceof EE_Event
845
-            ? mb_substr($event->name(), 0, 245)
846
-            : esc_html__('Event', 'event_espresso');
847
-    }
848
-
849
-
850
-    /**
851
-     * Gets the event excerpt
852
-     *
853
-     * @param EE_Event $event
854
-     * @return string
855
-     * @throws EE_Error
856
-     */
857
-    public static function get_event_desc($event)
858
-    {
859
-        return $event instanceof EE_Event ? $event->short_description() : '';
860
-    }
861
-
862
-
863
-    /**
864
-     * Given the grand total line item and a ticket, finds the event sub-total
865
-     * line item the ticket's purchase should be added onto
866
-     *
867
-     * @access public
868
-     * @param EE_Line_Item $grand_total the grand total line item
869
-     * @param EE_Ticket    $ticket
870
-     * @return EE_Line_Item
871
-     * @throws EE_Error
872
-     * @throws InvalidArgumentException
873
-     * @throws InvalidDataTypeException
874
-     * @throws InvalidInterfaceException
875
-     * @throws ReflectionException
876
-     */
877
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
878
-    {
879
-        $first_datetime = $ticket->first_datetime();
880
-        if (! $first_datetime instanceof EE_Datetime) {
881
-            throw new EE_Error(
882
-                sprintf(
883
-                    esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
884
-                    $ticket->ID()
885
-                )
886
-            );
887
-        }
888
-        $event = $first_datetime->event();
889
-        if (! $event instanceof EE_Event) {
890
-            throw new EE_Error(
891
-                sprintf(
892
-                    esc_html__(
893
-                        'The supplied ticket (ID %d) has no event data associated with it.',
894
-                        'event_espresso'
895
-                    ),
896
-                    $ticket->ID()
897
-                )
898
-            );
899
-        }
900
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
901
-        if (! $events_sub_total instanceof EE_Line_Item) {
902
-            throw new EE_Error(
903
-                sprintf(
904
-                    esc_html__(
905
-                        'There is no events sub-total for ticket %s on total line item %d',
906
-                        'event_espresso'
907
-                    ),
908
-                    $ticket->ID(),
909
-                    $grand_total->ID()
910
-                )
911
-            );
912
-        }
913
-        return $events_sub_total;
914
-    }
915
-
916
-
917
-    /**
918
-     * Gets the event line item
919
-     *
920
-     * @param EE_Line_Item $grand_total
921
-     * @param EE_Event     $event
922
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
923
-     * @throws EE_Error
924
-     * @throws InvalidArgumentException
925
-     * @throws InvalidDataTypeException
926
-     * @throws InvalidInterfaceException
927
-     * @throws ReflectionException
928
-     */
929
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
930
-    {
931
-        /** @type EE_Event $event */
932
-        $event           = EEM_Event::instance()->ensure_is_obj($event, true);
933
-        $event_line_item = null;
934
-        $found           = false;
935
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
936
-            // default event subtotal, we should only ever find this the first time this method is called
937
-            if (! $event_line_item->OBJ_ID()) {
938
-                // let's use this! but first... set the event details
939
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
940
-                $found = true;
941
-                break;
942
-            }
943
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
944
-                // found existing line item for this event in the cart, so break out of loop and use this one
945
-                $found = true;
946
-                break;
947
-            }
948
-        }
949
-        if (! $found) {
950
-            // there is no event sub-total yet, so add it
951
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
952
-            // create a new "event" subtotal below that
953
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
954
-            // and set the event details
955
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
956
-        }
957
-        return $event_line_item;
958
-    }
959
-
960
-
961
-    /**
962
-     * Creates a default items subtotal line item
963
-     *
964
-     * @param EE_Line_Item   $event_line_item
965
-     * @param EE_Event       $event
966
-     * @param EE_Transaction $transaction
967
-     * @return void
968
-     * @throws EE_Error
969
-     * @throws InvalidArgumentException
970
-     * @throws InvalidDataTypeException
971
-     * @throws InvalidInterfaceException
972
-     * @throws ReflectionException
973
-     */
974
-    public static function set_event_subtotal_details(
975
-        EE_Line_Item $event_line_item,
976
-        EE_Event $event,
977
-        $transaction = null
978
-    ) {
979
-        if ($event instanceof EE_Event) {
980
-            $event_line_item->set_code(self::get_event_code($event));
981
-            $event_line_item->set_name(self::get_event_name($event));
982
-            $event_line_item->set_desc(self::get_event_desc($event));
983
-            $event_line_item->set_OBJ_ID($event->ID());
984
-        }
985
-        self::set_TXN_ID($event_line_item, $transaction);
986
-    }
987
-
988
-
989
-    /**
990
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
991
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
992
-     * any old taxes are removed
993
-     *
994
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
995
-     * @param bool         $update_txn_status
996
-     * @return bool
997
-     * @throws EE_Error
998
-     * @throws InvalidArgumentException
999
-     * @throws InvalidDataTypeException
1000
-     * @throws InvalidInterfaceException
1001
-     * @throws ReflectionException
1002
-     * @throws RuntimeException
1003
-     */
1004
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1005
-    {
1006
-        /** @type EEM_Price $EEM_Price */
1007
-        $EEM_Price = EE_Registry::instance()->load_model('Price');
1008
-        // get array of taxes via Price Model
1009
-        $ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
1010
-        ksort($ordered_taxes);
1011
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
1012
-        // just to be safe, remove its old tax line items
1013
-        $deleted = $taxes_line_item->delete_children_line_items();
1014
-        $updates = false;
1015
-        // loop thru taxes
1016
-        foreach ($ordered_taxes as $order => $taxes) {
1017
-            foreach ($taxes as $tax) {
1018
-                if ($tax instanceof EE_Price) {
1019
-                    $tax_line_item = EE_Line_Item::new_instance(
1020
-                        [
1021
-                            'LIN_name'       => $tax->name(),
1022
-                            'LIN_desc'       => $tax->desc(),
1023
-                            'LIN_percent'    => $tax->amount(),
1024
-                            'LIN_is_taxable' => false,
1025
-                            'LIN_order'      => $order,
1026
-                            'LIN_total'      => 0,
1027
-                            'LIN_type'       => EEM_Line_Item::type_tax,
1028
-                            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1029
-                            'OBJ_ID'         => $tax->ID(),
1030
-                        ]
1031
-                    );
1032
-                    $tax_line_item = apply_filters(
1033
-                        'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1034
-                        $tax_line_item
1035
-                    );
1036
-                    $updates       = $taxes_line_item->add_child_line_item($tax_line_item)
1037
-                        ?
1038
-                        true
1039
-                        :
1040
-                        $updates;
1041
-                }
1042
-            }
1043
-        }
1044
-        // only recalculate totals if something changed
1045
-        if ($deleted || $updates) {
1046
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1047
-            return true;
1048
-        }
1049
-        return false;
1050
-    }
1051
-
1052
-
1053
-    /**
1054
-     * Ensures that taxes have been applied to the order, if not applies them.
1055
-     * Returns the total amount of tax
1056
-     *
1057
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1058
-     * @return float
1059
-     * @throws EE_Error
1060
-     * @throws InvalidArgumentException
1061
-     * @throws InvalidDataTypeException
1062
-     * @throws InvalidInterfaceException
1063
-     * @throws ReflectionException
1064
-     */
1065
-    public static function ensure_taxes_applied($total_line_item)
1066
-    {
1067
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1068
-        if (! $taxes_subtotal->children()) {
1069
-            self::apply_taxes($total_line_item);
1070
-        }
1071
-        return $taxes_subtotal->total();
1072
-    }
1073
-
1074
-
1075
-    /**
1076
-     * Deletes ALL children of the passed line item
1077
-     *
1078
-     * @param EE_Line_Item $parent_line_item
1079
-     * @return bool
1080
-     * @throws EE_Error
1081
-     * @throws InvalidArgumentException
1082
-     * @throws InvalidDataTypeException
1083
-     * @throws InvalidInterfaceException
1084
-     * @throws ReflectionException
1085
-     */
1086
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1087
-    {
1088
-        $deleted = 0;
1089
-        foreach ($parent_line_item->children() as $child_line_item) {
1090
-            if ($child_line_item instanceof EE_Line_Item) {
1091
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1092
-                if ($child_line_item->ID()) {
1093
-                    $child_line_item->delete();
1094
-                    unset($child_line_item);
1095
-                } else {
1096
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1097
-                }
1098
-                $deleted++;
1099
-            }
1100
-        }
1101
-        return $deleted;
1102
-    }
1103
-
1104
-
1105
-    /**
1106
-     * Deletes the line items as indicated by the line item code(s) provided,
1107
-     * regardless of where they're found in the line item tree. Automatically
1108
-     * re-calculates the line item totals and updates the related transaction. But
1109
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1110
-     * should probably change because of this).
1111
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1112
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1113
-     *
1114
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1115
-     * @param array|bool|string $line_item_codes
1116
-     * @return int number of items successfully removed
1117
-     * @throws EE_Error|ReflectionException
1118
-     */
1119
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1120
-    {
1121
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1122
-            EE_Error::doing_it_wrong(
1123
-                'EEH_Line_Item::delete_items',
1124
-                esc_html__(
1125
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1126
-                    'event_espresso'
1127
-                ),
1128
-                '4.6.18'
1129
-            );
1130
-        }
1131
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1132
-
1133
-        // check if only a single line_item_id was passed
1134
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1135
-            // place single line_item_id in an array to appear as multiple line_item_ids
1136
-            $line_item_codes = [$line_item_codes];
1137
-        }
1138
-        $removals = 0;
1139
-        // cycle thru line_item_ids
1140
-        foreach ($line_item_codes as $line_item_id) {
1141
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1142
-        }
1143
-
1144
-        if ($removals > 0) {
1145
-            $total_line_item->recalculate_taxes_and_tax_total();
1146
-            return $removals;
1147
-        } else {
1148
-            return false;
1149
-        }
1150
-    }
1151
-
1152
-
1153
-    /**
1154
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1155
-     * tax and updates the total line item accordingly
1156
-     *
1157
-     * @param EE_Line_Item $total_line_item
1158
-     * @param float        $amount
1159
-     * @param string       $name
1160
-     * @param string       $description
1161
-     * @param string       $code
1162
-     * @param boolean      $add_to_existing_line_item
1163
-     *                          if true, and a duplicate line item with the same code is found,
1164
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1165
-     * @return EE_Line_Item the new tax line item created
1166
-     * @throws EE_Error
1167
-     * @throws InvalidArgumentException
1168
-     * @throws InvalidDataTypeException
1169
-     * @throws InvalidInterfaceException
1170
-     * @throws ReflectionException
1171
-     */
1172
-    public static function set_total_tax_to(
1173
-        EE_Line_Item $total_line_item,
1174
-        $amount,
1175
-        $name = null,
1176
-        $description = null,
1177
-        $code = null,
1178
-        $add_to_existing_line_item = false
1179
-    ) {
1180
-        $tax_subtotal  = self::get_taxes_subtotal($total_line_item);
1181
-        $taxable_total = $total_line_item->taxable_total();
1182
-
1183
-        if ($add_to_existing_line_item) {
1184
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1185
-            EEM_Line_Item::instance()->delete(
1186
-                [['LIN_code' => ['!=', $code], 'LIN_parent' => $tax_subtotal->ID()]]
1187
-            );
1188
-        } else {
1189
-            $new_tax = null;
1190
-            $tax_subtotal->delete_children_line_items();
1191
-        }
1192
-        if ($new_tax) {
1193
-            $new_tax->set_total($new_tax->total() + $amount);
1194
-            $percent = $taxable_total ? $new_tax->total() / $taxable_total * 100 : 0;
1195
-            $new_tax->set_percent(EEH_Line_Item::currencyFormatter()->roundForLocale($percent));
1196
-        } else {
1197
-            $percent = $taxable_total ? ($amount / $taxable_total * 100) : 0;
1198
-            // no existing tax item. Create it
1199
-            $new_tax = EE_Line_Item::new_instance(
1200
-                [
1201
-                    'TXN_ID'      => $total_line_item->TXN_ID(),
1202
-                    'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1203
-                    'LIN_desc'    => $description ? $description : '',
1204
-                    'LIN_percent' => EEH_Line_Item::currencyFormatter()->roundForLocale($percent),
1205
-                    'LIN_total'   => $amount,
1206
-                    'LIN_parent'  => $tax_subtotal->ID(),
1207
-                    'LIN_type'    => EEM_Line_Item::type_tax,
1208
-                    'LIN_code'    => $code,
1209
-                ]
1210
-            );
1211
-        }
1212
-
1213
-        $new_tax = apply_filters(
1214
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1215
-            $new_tax,
1216
-            $total_line_item
1217
-        );
1218
-        $new_tax->save();
1219
-        $tax_subtotal->set_total($new_tax->total());
1220
-        $tax_subtotal->save();
1221
-        $total_line_item->recalculate_total_including_taxes();
1222
-        return $new_tax;
1223
-    }
1224
-
1225
-
1226
-    /**
1227
-     * Makes all the line items which are children of $line_item taxable (or not).
1228
-     * Does NOT save the line items
1229
-     *
1230
-     * @param EE_Line_Item $line_item
1231
-     * @param boolean      $taxable
1232
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1233
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1234
-     * @throws EE_Error|ReflectionException
1235
-     */
1236
-    public static function set_line_items_taxable(
1237
-        EE_Line_Item $line_item,
1238
-        $taxable = true,
1239
-        $code_substring_for_whitelist = null
1240
-    ) {
1241
-        $whitelisted = false;
1242
-        if ($code_substring_for_whitelist !== null) {
1243
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1244
-        }
1245
-        if (! $whitelisted && $line_item->is_line_item()) {
1246
-            $line_item->set_is_taxable($taxable);
1247
-        }
1248
-        foreach ($line_item->children() as $child_line_item) {
1249
-            EEH_Line_Item::set_line_items_taxable(
1250
-                $child_line_item,
1251
-                $taxable,
1252
-                $code_substring_for_whitelist
1253
-            );
1254
-        }
1255
-    }
1256
-
1257
-
1258
-    /**
1259
-     * Gets all descendants that are event subtotals
1260
-     *
1261
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1262
-     * @return EE_Line_Item[]
1263
-     * @throws EE_Error|ReflectionException
1264
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1265
-     */
1266
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1267
-    {
1268
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1269
-    }
1270
-
1271
-
1272
-    /**
1273
-     * Gets all descendants subtotals that match the supplied object type
1274
-     *
1275
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1276
-     * @param string       $obj_type
1277
-     * @return EE_Line_Item[]
1278
-     * @throws EE_Error|ReflectionException
1279
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
-     */
1281
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1282
-    {
1283
-        return self::_get_descendants_by_type_and_object_type(
1284
-            $parent_line_item,
1285
-            EEM_Line_Item::type_sub_total,
1286
-            $obj_type
1287
-        );
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * Gets all descendants that are tickets
1293
-     *
1294
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1295
-     * @return EE_Line_Item[]
1296
-     * @throws EE_Error|ReflectionException
1297
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1298
-     */
1299
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1300
-    {
1301
-        return self::get_line_items_of_object_type(
1302
-            $parent_line_item,
1303
-            EEM_Line_Item::OBJ_TYPE_TICKET
1304
-        );
1305
-    }
1306
-
1307
-
1308
-    /**
1309
-     * Gets all descendants subtotals that match the supplied object type
1310
-     *
1311
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1312
-     * @param string       $obj_type
1313
-     * @return EE_Line_Item[]
1314
-     * @throws EE_Error|ReflectionException
1315
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
-     */
1317
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1318
-    {
1319
-        return self::_get_descendants_by_type_and_object_type(
1320
-            $parent_line_item,
1321
-            EEM_Line_Item::type_line_item,
1322
-            $obj_type
1323
-        );
1324
-    }
1325
-
1326
-
1327
-    /**
1328
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1329
-     *
1330
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1331
-     * @return EE_Line_Item[]
1332
-     * @throws EE_Error|ReflectionException
1333
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1334
-     */
1335
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1336
-    {
1337
-        return EEH_Line_Item::get_descendants_of_type(
1338
-            $parent_line_item,
1339
-            EEM_Line_Item::type_tax
1340
-        );
1341
-    }
1342
-
1343
-
1344
-    /**
1345
-     * Gets all the real items purchased which are children of this item
1346
-     *
1347
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1348
-     * @return EE_Line_Item[]
1349
-     * @throws EE_Error|ReflectionException
1350
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1351
-     */
1352
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1353
-    {
1354
-        return EEH_Line_Item::get_descendants_of_type(
1355
-            $parent_line_item,
1356
-            EEM_Line_Item::type_line_item
1357
-        );
1358
-    }
1359
-
1360
-
1361
-    /**
1362
-     * Gets all descendants of supplied line item that match the supplied line item type
1363
-     *
1364
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1365
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1366
-     * @return EE_Line_Item[]
1367
-     * @throws EE_Error|ReflectionException
1368
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
-     */
1370
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1371
-    {
1372
-        return self::_get_descendants_by_type_and_object_type($parent_line_item, $line_item_type);
1373
-    }
1374
-
1375
-
1376
-    /**
1377
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1378
-     * as well
1379
-     *
1380
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1381
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1382
-     * @param string | NULL $obj_type         object model class name (minus prefix)
1383
-     *                                        or NULL to ignore object type when searching
1384
-     * @return EE_Line_Item[]
1385
-     * @throws EE_Error|ReflectionException
1386
-     */
1387
-    protected static function _get_descendants_by_type_and_object_type(
1388
-        EE_Line_Item $parent_line_item,
1389
-        $line_item_type,
1390
-        $obj_type = null
1391
-    ) {
1392
-        $objects = [];
1393
-        foreach ($parent_line_item->children() as $child_line_item) {
1394
-            if ($child_line_item instanceof EE_Line_Item) {
1395
-                if (
1396
-                    $child_line_item->type() === $line_item_type
1397
-                    && (
1398
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1399
-                    )
1400
-                ) {
1401
-                    $objects[] = $child_line_item;
1402
-                } else {
1403
-                    // go-through-all-its children looking for more matches
1404
-                    $objects = array_merge(
1405
-                        $objects,
1406
-                        self::_get_descendants_by_type_and_object_type(
1407
-                            $child_line_item,
1408
-                            $line_item_type,
1409
-                            $obj_type
1410
-                        )
1411
-                    );
1412
-                }
1413
-            }
1414
-        }
1415
-        return $objects;
1416
-    }
1417
-
1418
-
1419
-    /**
1420
-     * Gets all descendants subtotals that match the supplied object type
1421
-     *
1422
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1423
-     * @param string       $OBJ_type         object type (like Event)
1424
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1425
-     * @return EE_Line_Item[]
1426
-     * @throws EE_Error|ReflectionException
1427
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1428
-     */
1429
-    public static function get_line_items_by_object_type_and_IDs(
1430
-        EE_Line_Item $parent_line_item,
1431
-        $OBJ_type = '',
1432
-        $OBJ_IDs = []
1433
-    ) {
1434
-        return self::_get_descendants_by_object_type_and_object_ID(
1435
-            $parent_line_item,
1436
-            $OBJ_type,
1437
-            $OBJ_IDs
1438
-        );
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1444
-     * as well
1445
-     *
1446
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1447
-     * @param string       $OBJ_type         object type (like Event)
1448
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1449
-     * @return EE_Line_Item[]
1450
-     * @throws EE_Error|ReflectionException
1451
-     */
1452
-    protected static function _get_descendants_by_object_type_and_object_ID(
1453
-        EE_Line_Item $parent_line_item,
1454
-        $OBJ_type,
1455
-        $OBJ_IDs
1456
-    ) {
1457
-        $objects = [];
1458
-        foreach ($parent_line_item->children() as $child_line_item) {
1459
-            if ($child_line_item instanceof EE_Line_Item) {
1460
-                if (
1461
-                    $child_line_item->OBJ_type() === $OBJ_type
1462
-                    && is_array($OBJ_IDs)
1463
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1464
-                ) {
1465
-                    $objects[] = $child_line_item;
1466
-                } else {
1467
-                    // go-through-all-its children looking for more matches
1468
-                    $objects = array_merge(
1469
-                        $objects,
1470
-                        self::_get_descendants_by_object_type_and_object_ID(
1471
-                            $child_line_item,
1472
-                            $OBJ_type,
1473
-                            $OBJ_IDs
1474
-                        )
1475
-                    );
1476
-                }
1477
-            }
1478
-        }
1479
-        return $objects;
1480
-    }
1481
-
1482
-
1483
-    /**
1484
-     * Uses a breadth-first-search in order to find the nearest descendant of
1485
-     * the specified type and returns it, else NULL
1486
-     *
1487
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1488
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1489
-     * @return EE_Line_Item
1490
-     * @throws EE_Error
1491
-     * @throws InvalidArgumentException
1492
-     * @throws InvalidDataTypeException
1493
-     * @throws InvalidInterfaceException
1494
-     * @throws ReflectionException
1495
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
-     */
1497
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1498
-    {
1499
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1500
-    }
1501
-
1502
-
1503
-    /**
1504
-     * Uses a breadth-first-search in order to find the nearest descendant
1505
-     * having the specified LIN_code and returns it, else NULL
1506
-     *
1507
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1508
-     * @param string       $code             any value used for LIN_code
1509
-     * @return EE_Line_Item
1510
-     * @throws EE_Error
1511
-     * @throws InvalidArgumentException
1512
-     * @throws InvalidDataTypeException
1513
-     * @throws InvalidInterfaceException
1514
-     * @throws ReflectionException
1515
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
-     */
1517
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1518
-    {
1519
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1520
-    }
1521
-
1522
-
1523
-    /**
1524
-     * Uses a breadth-first-search in order to find the nearest descendant
1525
-     * having the specified LIN_code and returns it, else NULL
1526
-     *
1527
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1528
-     * @param string       $search_field     name of EE_Line_Item property
1529
-     * @param string       $value            any value stored in $search_field
1530
-     * @return EE_Line_Item
1531
-     * @throws EE_Error
1532
-     * @throws InvalidArgumentException
1533
-     * @throws InvalidDataTypeException
1534
-     * @throws InvalidInterfaceException
1535
-     * @throws ReflectionException
1536
-     */
1537
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1538
-    {
1539
-        foreach ($parent_line_item->children() as $child) {
1540
-            if ($child->get($search_field) == $value) {
1541
-                return $child;
1542
-            }
1543
-        }
1544
-        foreach ($parent_line_item->children() as $child) {
1545
-            $descendant_found = self::_get_nearest_descendant(
1546
-                $child,
1547
-                $search_field,
1548
-                $value
1549
-            );
1550
-            if ($descendant_found) {
1551
-                return $descendant_found;
1552
-            }
1553
-        }
1554
-        return null;
1555
-    }
1556
-
1557
-
1558
-    /**
1559
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1560
-     * else recursively walks up the line item tree until a parent of type total is found,
1561
-     *
1562
-     * @param EE_Line_Item $line_item
1563
-     * @return EE_Line_Item
1564
-     * @throws EE_Error|ReflectionException
1565
-     */
1566
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1567
-    {
1568
-        if ($line_item->TXN_ID()) {
1569
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1570
-            if ($total_line_item instanceof EE_Line_Item) {
1571
-                return $total_line_item;
1572
-            }
1573
-        } else {
1574
-            $line_item_parent = $line_item->parent();
1575
-            if ($line_item_parent instanceof EE_Line_Item) {
1576
-                if ($line_item_parent->is_total()) {
1577
-                    return $line_item_parent;
1578
-                }
1579
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1580
-            }
1581
-        }
1582
-        throw new EE_Error(
1583
-            sprintf(
1584
-                esc_html__(
1585
-                    'A valid grand total for line item %1$d was not found.',
1586
-                    'event_espresso'
1587
-                ),
1588
-                $line_item->ID()
1589
-            )
1590
-        );
1591
-    }
1592
-
1593
-
1594
-    /**
1595
-     * Prints out a representation of the line item tree
1596
-     *
1597
-     * @param EE_Line_Item $line_item
1598
-     * @param int          $indentation
1599
-     * @return void
1600
-     * @throws EE_Error|ReflectionException
1601
-     */
1602
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1603
-    {
1604
-        echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1605
-        if (! $indentation) {
1606
-            echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1607
-        }
1608
-        for ($i = 0; $i < $indentation; $i++) {
1609
-            echo '. ';
1610
-        }
1611
-        $breakdown = '';
1612
-        if ($line_item->is_line_item()) {
1613
-            if ($line_item->is_percent()) {
1614
-                $breakdown = "{$line_item->percent()}%";
1615
-            } else {
1616
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1617
-            }
1618
-        }
1619
-        echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1620
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1621
-        echo '$' . (string) $line_item->total();
1622
-        if ($breakdown) {
1623
-            echo " ( {$breakdown} )";
1624
-        }
1625
-        if ($line_item->is_taxable()) {
1626
-            echo '  * taxable';
1627
-        }
1628
-        if ($line_item->children()) {
1629
-            foreach ($line_item->children() as $child) {
1630
-                self::visualize($child, $indentation + 1);
1631
-            }
1632
-        }
1633
-    }
1634
-
1635
-
1636
-    /**
1637
-     * Calculates the registration's final price, taking into account that they
1638
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1639
-     * and receive a portion of any transaction-wide discounts.
1640
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1641
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1642
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1643
-     * and brent's final price should be $5.50.
1644
-     * In order to do this, we basically need to traverse the line item tree calculating
1645
-     * the running totals (just as if we were recalculating the total), but when we identify
1646
-     * regular line items, we need to keep track of their share of the grand total.
1647
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1648
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1649
-     * when there are non-taxable items; otherwise they would be the same)
1650
-     *
1651
-     * @param EE_Line_Item $line_item
1652
-     * @param array        $billable_ticket_quantities          array of EE_Ticket IDs and their corresponding quantity
1653
-     *                                                          that can be included in price calculations at this
1654
-     *                                                          moment
1655
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1656
-     *                                                          plus the key 'total', and 'taxable' which also has keys
1657
-     *                                                          of all the ticket IDs. Eg array(
1658
-     *                                                          12 => 4.3
1659
-     *                                                          23 => 8.0
1660
-     *                                                          'total' => 16.6,
1661
-     *                                                          'taxable' => array(
1662
-     *                                                          12 => 10,
1663
-     *                                                          23 => 4
1664
-     *                                                          ).
1665
-     *                                                          So to find which registrations have which final price,
1666
-     *                                                          we need to find which line item is theirs, which can be
1667
-     *                                                          done with
1668
-     *                                                          `EEM_Line_Item::instance()->get_line_item_for_registration(
1669
-     *                                                          $registration );`
1670
-     * @throws EE_Error
1671
-     * @throws InvalidArgumentException
1672
-     * @throws InvalidDataTypeException
1673
-     * @throws InvalidInterfaceException
1674
-     * @throws ReflectionException
1675
-     */
1676
-    public static function calculate_reg_final_prices_per_line_item(
1677
-        EE_Line_Item $line_item,
1678
-        $billable_ticket_quantities = []
1679
-    ) {
1680
-        $running_totals = [
1681
-            'total'   => 0,
1682
-            'taxable' => ['total' => 0],
1683
-        ];
1684
-        foreach ($line_item->children() as $child_line_item) {
1685
-            switch ($child_line_item->type()) {
1686
-                case EEM_Line_Item::type_sub_total:
1687
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1688
-                        $child_line_item,
1689
-                        $billable_ticket_quantities
1690
-                    );
1691
-                    // combine arrays but preserve numeric keys
1692
-                    $running_totals                     =
1693
-                        array_replace_recursive($running_totals_from_subtotal, $running_totals);
1694
-                    $running_totals['total']            += $running_totals_from_subtotal['total'];
1695
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1696
-                    break;
1697
-
1698
-                case EEM_Line_Item::type_tax_sub_total:
1699
-                    // find how much the taxes percentage is
1700
-                    if ($child_line_item->percent() !== 0) {
1701
-                        $tax_percent_decimal = $child_line_item->percent(true);
1702
-                    } else {
1703
-                        $tax_percent_decimal =
1704
-                            EEH_Line_Item::currencyFormatter()->roundForLocale(
1705
-                                EE_Taxes::get_total_taxes_percentage() / 100
1706
-                            );
1707
-                    }
1708
-                    // and apply to all the taxable totals, and add to the pretax totals
1709
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1710
-                        // "total" and "taxable" array key is an exception
1711
-                        if ($line_item_id === 'taxable') {
1712
-                            continue;
1713
-                        }
1714
-                        $taxable_total                   = $running_totals['taxable'][ $line_item_id ];
1715
-                        $running_totals[ $line_item_id ] += EEH_Line_Item::currencyFormatter()->roundForLocale(
1716
-                            $taxable_total * $tax_percent_decimal
1717
-                        );
1718
-                    }
1719
-                    break;
1720
-
1721
-                case EEM_Line_Item::type_line_item:
1722
-                    // ticket line items or ????
1723
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1724
-                        // kk it's a ticket
1725
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1726
-                            // huh? that shouldn't happen.
1727
-                            $running_totals['total'] += $child_line_item->total();
1728
-                        } else {
1729
-                            // its not in our running totals yet. great.
1730
-                            if ($child_line_item->is_taxable()) {
1731
-                                $taxable_amount = $child_line_item->unit_price();
1732
-                            } else {
1733
-                                $taxable_amount = 0;
1734
-                            }
1735
-                            // are we only calculating totals for some tickets?
1736
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1737
-                                $quantity                                            =
1738
-                                    $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1739
-                                $running_totals[ $child_line_item->ID() ]            = $quantity
1740
-                                    ? $child_line_item->unit_price()
1741
-                                    : 0;
1742
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1743
-                                    ? $taxable_amount
1744
-                                    : 0;
1745
-                            } else {
1746
-                                $quantity                                            = $child_line_item->quantity();
1747
-                                $running_totals[ $child_line_item->ID() ]            = $child_line_item->unit_price();
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1749
-                            }
1750
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1751
-                            $running_totals['total']            += $child_line_item->unit_price() * $quantity;
1752
-                        }
1753
-                    } else {
1754
-                        // it's some other type of item added to the cart
1755
-                        // it should affect the running totals
1756
-                        // basically we want to convert it into a PERCENT modifier. Because
1757
-                        // more clearly affect all registration's final price equally
1758
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1759
-                            ? EEH_Line_Item::currencyFormatter()->roundForLocale(
1760
-                                $child_line_item->total() / $running_totals['total']
1761
-                            ) + 1
1762
-                            : 1;
1763
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1764
-                            // the "taxable" array key is an exception
1765
-                            if ($line_item_id === 'taxable') {
1766
-                                continue;
1767
-                            }
1768
-                            // update the running totals
1769
-                            // yes this actually even works for the running grand total!
1770
-                            $running_totals[ $line_item_id ] = EEH_Line_Item::currencyFormatter()->roundForLocale(
1771
-                                $line_items_percent_of_running_total * $this_running_total
1772
-                            );
1773
-
1774
-                            if ($child_line_item->is_taxable()) {
1775
-                                $running_totals['taxable'][ $line_item_id ] =
1776
-                                    EEH_Line_Item::currencyFormatter()->roundForLocale(
1777
-                                        $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ]
1778
-                                    );
1779
-                            }
1780
-                        }
1781
-                    }
1782
-                    break;
1783
-            }
1784
-        }
1785
-        return $running_totals;
1786
-    }
1787
-
1788
-
1789
-    /**
1790
-     * @param EE_Line_Item $total_line_item
1791
-     * @param EE_Line_Item $ticket_line_item
1792
-     * @return float | null
1793
-     * @throws EE_Error
1794
-     * @throws InvalidArgumentException
1795
-     * @throws InvalidDataTypeException
1796
-     * @throws InvalidInterfaceException
1797
-     * @throws OutOfRangeException
1798
-     * @throws ReflectionException
1799
-     */
1800
-    public static function calculate_final_price_for_ticket_line_item(
1801
-        EE_Line_Item $total_line_item,
1802
-        EE_Line_Item $ticket_line_item
1803
-    ) {
1804
-        static $final_prices_per_ticket_line_item = [];
1805
-        if (empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1806
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] =
1807
-                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) could not be calculated.',
1818
-                'event_espresso'
1819
-            ),
1820
-            $ticket_line_item->ID()
1821
-        );
1822
-        if (WP_DEBUG) {
1823
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1824
-            throw new OutOfRangeException($message);
1825
-        }
1826
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1827
-        return null;
1828
-    }
1829
-
1830
-
1831
-    /**
1832
-     * Creates a duplicate of the line item tree, except only includes billable items
1833
-     * and the portion of line items attributed to billable things
1834
-     *
1835
-     * @param EE_Line_Item      $line_item
1836
-     * @param EE_Registration[] $registrations
1837
-     * @return EE_Line_Item
1838
-     * @throws EE_Error
1839
-     * @throws InvalidArgumentException
1840
-     * @throws InvalidDataTypeException
1841
-     * @throws InvalidInterfaceException
1842
-     * @throws ReflectionException
1843
-     */
1844
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1845
-    {
1846
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1847
-        foreach ($line_item->children() as $child_li) {
1848
-            $copy_li->add_child_line_item(
1849
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1850
-            );
1851
-        }
1852
-        // if this is the grand total line item, make sure the totals all add up
1853
-        // (we could have duplicated this logic AS we copied the line items, but
1854
-        // it seems DRYer this way)
1855
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1856
-            $copy_li->recalculate_total_including_taxes();
1857
-        }
1858
-        return $copy_li;
1859
-    }
1860
-
1861
-
1862
-    /**
1863
-     * Creates a new, unsaved line item from $line_item that factors in the
1864
-     * number of billable registrations on $registrations.
1865
-     *
1866
-     * @param EE_Line_Item      $line_item
1867
-     * @param EE_Registration[] $registrations
1868
-     * @return EE_Line_Item
1869
-     * @throws EE_Error
1870
-     * @throws InvalidArgumentException
1871
-     * @throws InvalidDataTypeException
1872
-     * @throws InvalidInterfaceException
1873
-     * @throws ReflectionException
1874
-     */
1875
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1876
-    {
1877
-        $new_li_fields = $line_item->model_field_array();
1878
-        if (
1879
-            $line_item->type() === EEM_Line_Item::type_line_item
1880
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1881
-        ) {
1882
-            $count = 0;
1883
-            foreach ($registrations as $registration) {
1884
-                if (
1885
-                    $line_item->OBJ_ID() === $registration->ticket_ID() &&
1886
-                    in_array(
1887
-                        $registration->status_ID(),
1888
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1889
-                        true
1890
-                    )
1891
-                ) {
1892
-                    $count++;
1893
-                }
1894
-            }
1895
-            $new_li_fields['LIN_quantity'] = $count;
1896
-        }
1897
-        // don't set the total. We'll leave that up to the code that calculates it
1898
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1899
-        return EE_Line_Item::new_instance($new_li_fields);
1900
-    }
1901
-
1902
-
1903
-    /**
1904
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1905
-     * are removed, and line items with a quantity of 0
1906
-     *
1907
-     * @param EE_Line_Item $line_item |null
1908
-     * @return EE_Line_Item|null
1909
-     * @throws EE_Error
1910
-     * @throws InvalidArgumentException
1911
-     * @throws InvalidDataTypeException
1912
-     * @throws InvalidInterfaceException
1913
-     * @throws ReflectionException
1914
-     */
1915
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1916
-    {
1917
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1918
-        if ($copied_li === null) {
1919
-            return null;
1920
-        }
1921
-        // if this is an event subtotal, we want to only include it if it
1922
-        // has a non-zero total and at least one ticket line item child
1923
-        $ticket_children = 0;
1924
-        foreach ($line_item->children() as $child_li) {
1925
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1926
-            if ($child_li_copy !== null) {
1927
-                $copied_li->add_child_line_item($child_li_copy);
1928
-                if (
1929
-                    $child_li_copy->type() === EEM_Line_Item::type_line_item
1930
-                    && $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1931
-                ) {
1932
-                    $ticket_children++;
1933
-                }
1934
-            }
1935
-        }
1936
-        // if this is an event subtotal with NO ticket children
1937
-        // we basically want to ignore it
1938
-        if (
1939
-            $ticket_children === 0
1940
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1941
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1942
-            && $line_item->total() === 0
1943
-        ) {
1944
-            return null;
1945
-        }
1946
-        return $copied_li;
1947
-    }
1948
-
1949
-
1950
-    /**
1951
-     * Creates a new, unsaved line item, but if it's a ticket line item
1952
-     * with a total of 0, or a subtotal of 0, returns null instead
1953
-     *
1954
-     * @param EE_Line_Item $line_item
1955
-     * @return EE_Line_Item
1956
-     * @throws EE_Error
1957
-     * @throws InvalidArgumentException
1958
-     * @throws InvalidDataTypeException
1959
-     * @throws InvalidInterfaceException
1960
-     * @throws ReflectionException
1961
-     */
1962
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1963
-    {
1964
-        if (
1965
-            $line_item->type() === EEM_Line_Item::type_line_item
1966
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1967
-            && $line_item->quantity() === 0
1968
-        ) {
1969
-            return null;
1970
-        }
1971
-        $new_li_fields = $line_item->model_field_array();
1972
-        // don't set the total. We'll leave that up to the code that calculates it
1973
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1974
-        return EE_Line_Item::new_instance($new_li_fields);
1975
-    }
1976
-
1977
-
1978
-    /**
1979
-     * Cycles through all of the ticket line items for the supplied total line item
1980
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1981
-     *
1982
-     * @param EE_Line_Item $total_line_item
1983
-     * @throws EE_Error
1984
-     * @throws InvalidArgumentException
1985
-     * @throws InvalidDataTypeException
1986
-     * @throws InvalidInterfaceException
1987
-     * @throws ReflectionException
1988
-     * @since 4.9.79.p
1989
-     */
1990
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1991
-    {
1992
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1993
-        foreach ($ticket_line_items as $ticket_line_item) {
1994
-            if (
1995
-                $ticket_line_item instanceof EE_Line_Item
1996
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1997
-            ) {
1998
-                $ticket = $ticket_line_item->ticket();
1999
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2000
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
2001
-                    $ticket_line_item->save();
2002
-                }
2003
-            }
2004
-        }
2005
-    }
2006
-
2007
-
2008
-
2009
-    /**************************************** @DEPRECATED METHODS *************************************** */
2010
-    /**
2011
-     * @param EE_Line_Item $total_line_item
2012
-     * @return EE_Line_Item
2013
-     * @throws EE_Error
2014
-     * @throws InvalidArgumentException
2015
-     * @throws InvalidDataTypeException
2016
-     * @throws InvalidInterfaceException
2017
-     * @throws ReflectionException
2018
-     * @deprecated
2019
-     */
2020
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
2021
-    {
2022
-        EE_Error::doing_it_wrong(
2023
-            'EEH_Line_Item::get_items_subtotal()',
2024
-            sprintf(
2025
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2026
-                'EEH_Line_Item::get_pre_tax_subtotal()'
2027
-            ),
2028
-            '4.6.0'
2029
-        );
2030
-        return self::get_pre_tax_subtotal($total_line_item);
2031
-    }
2032
-
2033
-
2034
-    /**
2035
-     * @param EE_Transaction $transaction
2036
-     * @return EE_Line_Item
2037
-     * @throws EE_Error
2038
-     * @throws InvalidArgumentException
2039
-     * @throws InvalidDataTypeException
2040
-     * @throws InvalidInterfaceException
2041
-     * @throws ReflectionException
2042
-     * @deprecated
2043
-     */
2044
-    public static function create_default_total_line_item($transaction = null)
2045
-    {
2046
-        EE_Error::doing_it_wrong(
2047
-            'EEH_Line_Item::create_default_total_line_item()',
2048
-            sprintf(
2049
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2050
-                'EEH_Line_Item::create_total_line_item()'
2051
-            ),
2052
-            '4.6.0'
2053
-        );
2054
-        return self::create_total_line_item($transaction);
2055
-    }
2056
-
2057
-
2058
-    /**
2059
-     * @param EE_Line_Item   $total_line_item
2060
-     * @param EE_Transaction $transaction
2061
-     * @return EE_Line_Item
2062
-     * @throws EE_Error
2063
-     * @throws InvalidArgumentException
2064
-     * @throws InvalidDataTypeException
2065
-     * @throws InvalidInterfaceException
2066
-     * @throws ReflectionException
2067
-     * @deprecated
2068
-     */
2069
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2070
-    {
2071
-        EE_Error::doing_it_wrong(
2072
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2073
-            sprintf(
2074
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2075
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2076
-            ),
2077
-            '4.6.0'
2078
-        );
2079
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2080
-    }
2081
-
2082
-
2083
-    /**
2084
-     * @param EE_Line_Item   $total_line_item
2085
-     * @param EE_Transaction $transaction
2086
-     * @return EE_Line_Item
2087
-     * @throws EE_Error
2088
-     * @throws InvalidArgumentException
2089
-     * @throws InvalidDataTypeException
2090
-     * @throws InvalidInterfaceException
2091
-     * @throws ReflectionException
2092
-     * @deprecated
2093
-     */
2094
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2095
-    {
2096
-        EE_Error::doing_it_wrong(
2097
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2098
-            sprintf(
2099
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2100
-                'EEH_Line_Item::create_taxes_subtotal()'
2101
-            ),
2102
-            '4.6.0'
2103
-        );
2104
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2105
-    }
2106
-
2107
-
2108
-    /**
2109
-     * @param EE_Line_Item   $total_line_item
2110
-     * @param EE_Transaction $transaction
2111
-     * @return EE_Line_Item
2112
-     * @throws EE_Error
2113
-     * @throws InvalidArgumentException
2114
-     * @throws InvalidDataTypeException
2115
-     * @throws InvalidInterfaceException
2116
-     * @throws ReflectionException
2117
-     * @deprecated
2118
-     */
2119
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2120
-    {
2121
-        EE_Error::doing_it_wrong(
2122
-            'EEH_Line_Item::create_default_event_subtotal()',
2123
-            sprintf(
2124
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2125
-                'EEH_Line_Item::create_event_subtotal()'
2126
-            ),
2127
-            '4.6.0'
2128
-        );
2129
-        return self::create_event_subtotal($total_line_item, $transaction);
2130
-    }
26
+	/**
27
+	 * @return CurrencyFormatter
28
+	 * @since   $VID:$
29
+	 */
30
+	private static function currencyFormatter()
31
+	{
32
+		static $currency_formatter;
33
+		if (! $currency_formatter instanceof CurrencyFormatter) {
34
+			$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
35
+		}
36
+		return $currency_formatter;
37
+	}
38
+
39
+	/**
40
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
41
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
42
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
43
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
44
+	 * to keep the registration final prices in-sync with the transaction's total.
45
+	 *
46
+	 * @param EE_Line_Item $parent_line_item
47
+	 * @param string       $name
48
+	 * @param float        $unit_price
49
+	 * @param string       $description
50
+	 * @param int          $quantity
51
+	 * @param boolean      $taxable
52
+	 * @param boolean      $code if set to a value, ensures there is only one line item with that code
53
+	 * @return boolean success
54
+	 * @throws EE_Error
55
+	 * @throws InvalidArgumentException
56
+	 * @throws InvalidDataTypeException
57
+	 * @throws InvalidInterfaceException
58
+	 * @throws ReflectionException
59
+	 */
60
+	public static function add_unrelated_item(
61
+		EE_Line_Item $parent_line_item,
62
+		$name,
63
+		$unit_price,
64
+		$description = '',
65
+		$quantity = 1,
66
+		$taxable = false,
67
+		$code = null
68
+	) {
69
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
70
+		$line_item      = EE_Line_Item::new_instance(
71
+			[
72
+				'LIN_name'       => $name,
73
+				'LIN_desc'       => $description,
74
+				'LIN_unit_price' => $unit_price,
75
+				'LIN_quantity'   => $quantity,
76
+				'LIN_percent'    => null,
77
+				'LIN_is_taxable' => $taxable,
78
+				'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
79
+				'LIN_total'      => EEH_Line_Item::currencyFormatter()->roundForLocale(
80
+					(float) $unit_price * (int) $quantity
81
+				),
82
+				'LIN_type'       => EEM_Line_Item::type_line_item,
83
+				'LIN_code'       => $code,
84
+			]
85
+		);
86
+		$line_item      = apply_filters(
87
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
88
+			$line_item,
89
+			$parent_line_item
90
+		);
91
+		return self::add_item($parent_line_item, $line_item);
92
+	}
93
+
94
+
95
+	/**
96
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
97
+	 * in the correct spot in the line item tree. Does not automatically
98
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
99
+	 * registrations' final prices (which should probably change because of this).
100
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
101
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
102
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
103
+	 *
104
+	 * @param EE_Line_Item $parent_line_item
105
+	 * @param string       $name
106
+	 * @param float        $percentage_amount
107
+	 * @param string       $description
108
+	 * @param boolean      $taxable
109
+	 * @return boolean success
110
+	 * @throws EE_Error|ReflectionException
111
+	 */
112
+	public static function add_percentage_based_item(
113
+		EE_Line_Item $parent_line_item,
114
+		$name,
115
+		$percentage_amount,
116
+		$description = '',
117
+		$taxable = false
118
+	) {
119
+		$line_item = EE_Line_Item::new_instance(
120
+			[
121
+				'LIN_name'       => $name,
122
+				'LIN_desc'       => $description,
123
+				'LIN_unit_price' => 0,
124
+				'LIN_percent'    => $percentage_amount,
125
+				'LIN_quantity'   => 1,
126
+				'LIN_is_taxable' => $taxable,
127
+				'LIN_total'      => EEH_Line_Item::currencyFormatter()->roundForLocale(
128
+					$percentage_amount * $parent_line_item->total() / 100
129
+				),
130
+				'LIN_type'       => EEM_Line_Item::type_line_item,
131
+				'LIN_parent'     => $parent_line_item->ID(),
132
+			]
133
+		);
134
+		$line_item = apply_filters(
135
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
136
+			$line_item
137
+		);
138
+		return $parent_line_item->add_child_line_item($line_item, false);
139
+	}
140
+
141
+
142
+	/**
143
+	 * Returns the new line item created by adding a purchase of the ticket
144
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
145
+	 * If this ticket has already been purchased, just increments its count.
146
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
147
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
148
+	 * should probably change because of this).
149
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
150
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
151
+	 *
152
+	 * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
153
+	 * @param EE_Ticket    $ticket
154
+	 * @param int          $qty
155
+	 * @return EE_Line_Item
156
+	 * @throws EE_Error
157
+	 * @throws InvalidArgumentException
158
+	 * @throws InvalidDataTypeException
159
+	 * @throws InvalidInterfaceException
160
+	 * @throws ReflectionException
161
+	 */
162
+	public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
163
+	{
164
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
165
+			throw new EE_Error(
166
+				sprintf(
167
+					esc_html__(
168
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
169
+						'event_espresso'
170
+					),
171
+					$ticket->ID(),
172
+					$total_line_item->ID()
173
+				)
174
+			);
175
+		}
176
+		// either increment the qty for an existing ticket
177
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
178
+		// or add a new one
179
+		if (! $line_item instanceof EE_Line_Item) {
180
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
181
+		}
182
+		$total_line_item->recalculate_total_including_taxes();
183
+		return $line_item;
184
+	}
185
+
186
+
187
+	/**
188
+	 * Returns the new line item created by adding a purchase of the ticket
189
+	 *
190
+	 * @param EE_Line_Item $total_line_item
191
+	 * @param EE_Ticket    $ticket
192
+	 * @param int          $qty
193
+	 * @return EE_Line_Item
194
+	 * @throws EE_Error
195
+	 * @throws InvalidArgumentException
196
+	 * @throws InvalidDataTypeException
197
+	 * @throws InvalidInterfaceException
198
+	 * @throws ReflectionException
199
+	 */
200
+	public static function increment_ticket_qty_if_already_in_cart(
201
+		EE_Line_Item $total_line_item,
202
+		EE_Ticket $ticket,
203
+		$qty = 1
204
+	) {
205
+		$line_item = null;
206
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
207
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
208
+			foreach ((array) $ticket_line_items as $ticket_line_item) {
209
+				if (
210
+					$ticket_line_item instanceof EE_Line_Item
211
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
212
+				) {
213
+					$line_item = $ticket_line_item;
214
+					break;
215
+				}
216
+			}
217
+		}
218
+		if ($line_item instanceof EE_Line_Item) {
219
+			EEH_Line_Item::increment_quantity($line_item, $qty);
220
+			return $line_item;
221
+		}
222
+		return null;
223
+	}
224
+
225
+
226
+	/**
227
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
228
+	 * Does NOT save or recalculate other line items totals
229
+	 *
230
+	 * @param EE_Line_Item $line_item
231
+	 * @param int          $qty
232
+	 * @return void
233
+	 * @throws EE_Error
234
+	 * @throws InvalidArgumentException
235
+	 * @throws InvalidDataTypeException
236
+	 * @throws InvalidInterfaceException
237
+	 * @throws ReflectionException
238
+	 */
239
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
240
+	{
241
+		if (! $line_item->is_percent()) {
242
+			$qty += $line_item->quantity();
243
+			$line_item->set_quantity($qty);
244
+			$line_item->set_total($line_item->unit_price() * $qty);
245
+			$line_item->save();
246
+		}
247
+		foreach ($line_item->children() as $child) {
248
+			if ($child->is_sub_line_item()) {
249
+				EEH_Line_Item::update_quantity($child, $qty);
250
+			}
251
+		}
252
+	}
253
+
254
+
255
+	/**
256
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
257
+	 * Does NOT save or recalculate other line items totals
258
+	 *
259
+	 * @param EE_Line_Item $line_item
260
+	 * @param int          $qty
261
+	 * @return void
262
+	 * @throws EE_Error
263
+	 * @throws InvalidArgumentException
264
+	 * @throws InvalidDataTypeException
265
+	 * @throws InvalidInterfaceException
266
+	 * @throws ReflectionException
267
+	 */
268
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
269
+	{
270
+		if (! $line_item->is_percent()) {
271
+			$qty = $line_item->quantity() - $qty;
272
+			$qty = max($qty, 0);
273
+			$line_item->set_quantity($qty);
274
+			$line_item->set_total($line_item->unit_price() * $qty);
275
+			$line_item->save();
276
+		}
277
+		foreach ($line_item->children() as $child) {
278
+			if ($child->is_sub_line_item()) {
279
+				EEH_Line_Item::update_quantity($child, $qty);
280
+			}
281
+		}
282
+	}
283
+
284
+
285
+	/**
286
+	 * Updates the line item and its children's quantities to the specified number.
287
+	 * Does NOT save them or recalculate totals.
288
+	 *
289
+	 * @param EE_Line_Item $line_item
290
+	 * @param int          $new_quantity
291
+	 * @throws EE_Error
292
+	 * @throws InvalidArgumentException
293
+	 * @throws InvalidDataTypeException
294
+	 * @throws InvalidInterfaceException
295
+	 * @throws ReflectionException
296
+	 */
297
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
298
+	{
299
+		if (! $line_item->is_percent()) {
300
+			$line_item->set_quantity($new_quantity);
301
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
302
+			$line_item->save();
303
+		}
304
+		foreach ($line_item->children() as $child) {
305
+			if ($child->is_sub_line_item()) {
306
+				EEH_Line_Item::update_quantity($child, $new_quantity);
307
+			}
308
+		}
309
+	}
310
+
311
+
312
+	/**
313
+	 * Returns the new line item created by adding a purchase of the ticket
314
+	 *
315
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
316
+	 * @param EE_Ticket    $ticket
317
+	 * @param int          $qty
318
+	 * @return EE_Line_Item
319
+	 * @throws EE_Error
320
+	 * @throws InvalidArgumentException
321
+	 * @throws InvalidDataTypeException
322
+	 * @throws InvalidInterfaceException
323
+	 * @throws ReflectionException
324
+	 */
325
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
326
+	{
327
+		$datetimes           = $ticket->datetimes();
328
+		$first_datetime      = reset($datetimes);
329
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
330
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
331
+			$first_datetime_name = $first_datetime->event()->name();
332
+		}
333
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
334
+		// get event subtotal line
335
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
336
+		// add $ticket to cart
337
+		$line_item = EE_Line_Item::new_instance(
338
+			[
339
+				'LIN_name'       => $ticket->name(),
340
+				'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
341
+				'LIN_unit_price' => $ticket->price(),
342
+				'LIN_quantity'   => $qty,
343
+				'LIN_is_taxable' => $ticket->taxable(),
344
+				'LIN_order'      => count($events_sub_total->children()),
345
+				'LIN_total'      => $ticket->price() * $qty,
346
+				'LIN_type'       => EEM_Line_Item::type_line_item,
347
+				'OBJ_ID'         => $ticket->ID(),
348
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
349
+			]
350
+		);
351
+		$line_item = apply_filters(
352
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
353
+			$line_item
354
+		);
355
+		$events_sub_total->add_child_line_item($line_item);
356
+		// now add the sub-line items
357
+		$running_total_for_ticket = 0;
358
+		foreach ($ticket->prices(['order_by' => ['PRC_order' => 'ASC']]) as $price) {
359
+			$sign          = $price->is_discount() ? -1 : 1;
360
+			$price_total   = $price->is_percent()
361
+				? $sign * $running_total_for_ticket * $price->amount() / 100
362
+				: $sign * $price->amount() * $qty;
363
+			$price_total = EEH_Line_Item::currencyFormatter()->roundForLocale($price_total);
364
+			$sub_line_item = EE_Line_Item::new_instance(
365
+				[
366
+					'LIN_name'       => $price->name(),
367
+					'LIN_desc'       => $price->desc(),
368
+					'LIN_quantity'   => $price->is_percent() ? null : $qty,
369
+					'LIN_is_taxable' => false,
370
+					'LIN_order'      => $price->order(),
371
+					'LIN_total'      => $price_total,
372
+					'LIN_type'       => EEM_Line_Item::type_sub_line_item,
373
+					'OBJ_ID'         => $price->ID(),
374
+					'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
375
+				]
376
+			);
377
+			/** @var EE_Line_Item $sub_line_item */
378
+			$sub_line_item = apply_filters(
379
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
380
+				$sub_line_item
381
+			);
382
+			if ($price->is_percent()) {
383
+				$sub_line_item->set_percent($sign * $price->amount());
384
+			} else {
385
+				$sub_line_item->set_unit_price($sign * $price->amount());
386
+			}
387
+			$running_total_for_ticket += $price_total;
388
+			$line_item->add_child_line_item($sub_line_item);
389
+		}
390
+		return $line_item;
391
+	}
392
+
393
+
394
+	/**
395
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
396
+	 * re-calculates the line item totals and updates the related transaction. But
397
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
398
+	 * should probably change because of this).
399
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
400
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
401
+	 *
402
+	 * @param EE_Line_Item $total_line_item
403
+	 * @param EE_Line_Item $item to be added
404
+	 * @return boolean
405
+	 * @throws EE_Error
406
+	 * @throws InvalidArgumentException
407
+	 * @throws InvalidDataTypeException
408
+	 * @throws InvalidInterfaceException
409
+	 * @throws ReflectionException
410
+	 */
411
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
412
+	{
413
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
414
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
415
+			$success = $pre_tax_subtotal->add_child_line_item($item);
416
+		} else {
417
+			return false;
418
+		}
419
+		$total_line_item->recalculate_total_including_taxes();
420
+		return $success;
421
+	}
422
+
423
+
424
+	/**
425
+	 * cancels an existing ticket line item,
426
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
427
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
428
+	 *
429
+	 * @param EE_Line_Item $ticket_line_item
430
+	 * @param int          $qty
431
+	 * @return bool success
432
+	 * @throws EE_Error
433
+	 * @throws InvalidArgumentException
434
+	 * @throws InvalidDataTypeException
435
+	 * @throws InvalidInterfaceException
436
+	 * @throws ReflectionException
437
+	 */
438
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
439
+	{
440
+		// validate incoming line_item
441
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
442
+			throw new EE_Error(
443
+				sprintf(
444
+					esc_html__(
445
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
446
+						'event_espresso'
447
+					),
448
+					$ticket_line_item->type()
449
+				)
450
+			);
451
+		}
452
+		if ($ticket_line_item->quantity() < $qty) {
453
+			throw new EE_Error(
454
+				sprintf(
455
+					esc_html__(
456
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
457
+						'event_espresso'
458
+					),
459
+					$qty,
460
+					$ticket_line_item->quantity()
461
+				)
462
+			);
463
+		}
464
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
465
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
466
+		foreach ($ticket_line_item->children() as $child_line_item) {
467
+			if (
468
+				$child_line_item->is_sub_line_item()
469
+				&& ! $child_line_item->is_percent()
470
+				&& ! $child_line_item->is_cancellation()
471
+			) {
472
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
473
+			}
474
+		}
475
+		// get cancellation sub line item
476
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
477
+			$ticket_line_item,
478
+			EEM_Line_Item::type_cancellation
479
+		);
480
+		$cancellation_line_item = reset($cancellation_line_item);
481
+		// verify that this ticket was indeed previously cancelled
482
+		if ($cancellation_line_item instanceof EE_Line_Item) {
483
+			// increment cancelled quantity
484
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
485
+		} else {
486
+			// create cancellation sub line item
487
+			$cancellation_line_item = EE_Line_Item::new_instance(
488
+				[
489
+					'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
490
+					'LIN_desc'       => sprintf(
491
+						esc_html_x(
492
+							'Cancelled %1$s : %2$s',
493
+							'Cancelled Ticket Name : 2015-01-01 11:11',
494
+							'event_espresso'
495
+						),
496
+						$ticket_line_item->name(),
497
+						current_time(get_option('date_format') . ' ' . get_option('time_format'))
498
+					),
499
+					'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
500
+					'LIN_quantity'   => $qty,
501
+					'LIN_is_taxable' => $ticket_line_item->is_taxable(),
502
+					'LIN_order'      => count($ticket_line_item->children()),
503
+					'LIN_total'      => 0, // $ticket_line_item->unit_price()
504
+					'LIN_type'       => EEM_Line_Item::type_cancellation,
505
+				]
506
+			);
507
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
508
+		}
509
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
510
+			// decrement parent line item quantity
511
+			$event_line_item = $ticket_line_item->parent();
512
+			if (
513
+				$event_line_item instanceof EE_Line_Item
514
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
515
+			) {
516
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
517
+				$event_line_item->save();
518
+			}
519
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
520
+			return true;
521
+		}
522
+		return false;
523
+	}
524
+
525
+
526
+	/**
527
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
528
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
529
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
530
+	 *
531
+	 * @param EE_Line_Item $ticket_line_item
532
+	 * @param int          $qty
533
+	 * @return bool success
534
+	 * @throws EE_Error
535
+	 * @throws InvalidArgumentException
536
+	 * @throws InvalidDataTypeException
537
+	 * @throws InvalidInterfaceException
538
+	 * @throws ReflectionException
539
+	 */
540
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
541
+	{
542
+		// validate incoming line_item
543
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
544
+			throw new EE_Error(
545
+				sprintf(
546
+					esc_html__(
547
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
548
+						'event_espresso'
549
+					),
550
+					$ticket_line_item->type()
551
+				)
552
+			);
553
+		}
554
+		// get cancellation sub line item
555
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
556
+			$ticket_line_item,
557
+			EEM_Line_Item::type_cancellation
558
+		);
559
+		$cancellation_line_item = reset($cancellation_line_item);
560
+		// verify that this ticket was indeed previously cancelled
561
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
562
+			return false;
563
+		}
564
+		if ($cancellation_line_item->quantity() > $qty) {
565
+			// decrement cancelled quantity
566
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
567
+		} elseif ($cancellation_line_item->quantity() === $qty) {
568
+			// decrement cancelled quantity in case anyone still has the object kicking around
569
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
570
+			// delete because quantity will end up as 0
571
+			$cancellation_line_item->delete();
572
+			// and attempt to destroy the object,
573
+			// even though PHP won't actually destroy it until it needs the memory
574
+			unset($cancellation_line_item);
575
+		} else {
576
+			// what ?!?! negative quantity ?!?!
577
+			throw new EE_Error(
578
+				sprintf(
579
+					esc_html__(
580
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
581
+						'event_espresso'
582
+					),
583
+					$qty,
584
+					$cancellation_line_item->quantity()
585
+				)
586
+			);
587
+		}
588
+		// increment ticket quantity
589
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
590
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
591
+			// increment parent line item quantity
592
+			$event_line_item = $ticket_line_item->parent();
593
+			if (
594
+				$event_line_item instanceof EE_Line_Item
595
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
596
+			) {
597
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
598
+			}
599
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
600
+			return true;
601
+		}
602
+		return false;
603
+	}
604
+
605
+
606
+	/**
607
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
608
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
609
+	 *
610
+	 * @param EE_Line_Item $line_item
611
+	 * @return float
612
+	 * @throws EE_Error
613
+	 * @throws InvalidArgumentException
614
+	 * @throws InvalidDataTypeException
615
+	 * @throws InvalidInterfaceException
616
+	 * @throws ReflectionException
617
+	 */
618
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
619
+	{
620
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
621
+		return $grand_total_line_item->recalculate_total_including_taxes();
622
+	}
623
+
624
+
625
+	/**
626
+	 * Gets the line item which contains the subtotal of all the items
627
+	 *
628
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
629
+	 * @return EE_Line_Item
630
+	 * @throws EE_Error
631
+	 * @throws InvalidArgumentException
632
+	 * @throws InvalidDataTypeException
633
+	 * @throws InvalidInterfaceException
634
+	 * @throws ReflectionException
635
+	 */
636
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
637
+	{
638
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
639
+		return $pre_tax_subtotal instanceof EE_Line_Item
640
+			? $pre_tax_subtotal
641
+			: self::create_pre_tax_subtotal($total_line_item);
642
+	}
643
+
644
+
645
+	/**
646
+	 * Gets the line item for the taxes subtotal
647
+	 *
648
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
649
+	 * @return EE_Line_Item
650
+	 * @throws EE_Error
651
+	 * @throws InvalidArgumentException
652
+	 * @throws InvalidDataTypeException
653
+	 * @throws InvalidInterfaceException
654
+	 * @throws ReflectionException
655
+	 */
656
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
657
+	{
658
+		$taxes = $total_line_item->get_child_line_item('taxes');
659
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
660
+	}
661
+
662
+
663
+	/**
664
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
665
+	 *
666
+	 * @param EE_Line_Item   $line_item
667
+	 * @param EE_Transaction $transaction
668
+	 * @return void
669
+	 * @throws EE_Error
670
+	 * @throws InvalidArgumentException
671
+	 * @throws InvalidDataTypeException
672
+	 * @throws InvalidInterfaceException
673
+	 * @throws ReflectionException
674
+	 */
675
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
676
+	{
677
+		if ($transaction) {
678
+			/** @type EEM_Transaction $EEM_Transaction */
679
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
680
+			$TXN_ID          = $EEM_Transaction->ensure_is_ID($transaction);
681
+			$line_item->set_TXN_ID($TXN_ID);
682
+		}
683
+	}
684
+
685
+
686
+	/**
687
+	 * Creates a new default total line item for the transaction,
688
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
689
+	 * existing taxes as children of the taxes subtotal line item)
690
+	 *
691
+	 * @param EE_Transaction $transaction
692
+	 * @return EE_Line_Item of type total
693
+	 * @throws EE_Error
694
+	 * @throws InvalidArgumentException
695
+	 * @throws InvalidDataTypeException
696
+	 * @throws InvalidInterfaceException
697
+	 * @throws ReflectionException
698
+	 */
699
+	public static function create_total_line_item($transaction = null)
700
+	{
701
+		$total_line_item = EE_Line_Item::new_instance(
702
+			[
703
+				'LIN_code' => 'total',
704
+				'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
705
+				'LIN_type' => EEM_Line_Item::type_total,
706
+				'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
707
+			]
708
+		);
709
+		$total_line_item = apply_filters(
710
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
711
+			$total_line_item
712
+		);
713
+		self::set_TXN_ID($total_line_item, $transaction);
714
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
715
+		self::create_taxes_subtotal($total_line_item, $transaction);
716
+		return $total_line_item;
717
+	}
718
+
719
+
720
+	/**
721
+	 * Creates a default items subtotal line item
722
+	 *
723
+	 * @param EE_Line_Item   $total_line_item
724
+	 * @param EE_Transaction $transaction
725
+	 * @return EE_Line_Item
726
+	 * @throws EE_Error
727
+	 * @throws InvalidArgumentException
728
+	 * @throws InvalidDataTypeException
729
+	 * @throws InvalidInterfaceException
730
+	 * @throws ReflectionException
731
+	 */
732
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
733
+	{
734
+		$pre_tax_line_item = EE_Line_Item::new_instance(
735
+			[
736
+				'LIN_code' => 'pre-tax-subtotal',
737
+				'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
738
+				'LIN_type' => EEM_Line_Item::type_sub_total,
739
+			]
740
+		);
741
+		$pre_tax_line_item = apply_filters(
742
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
743
+			$pre_tax_line_item
744
+		);
745
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
746
+		$total_line_item->add_child_line_item($pre_tax_line_item);
747
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
748
+		return $pre_tax_line_item;
749
+	}
750
+
751
+
752
+	/**
753
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
754
+	 * and applies taxes to it
755
+	 *
756
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
757
+	 * @param EE_Transaction $transaction
758
+	 * @return EE_Line_Item
759
+	 * @throws EE_Error
760
+	 * @throws InvalidArgumentException
761
+	 * @throws InvalidDataTypeException
762
+	 * @throws InvalidInterfaceException
763
+	 * @throws ReflectionException
764
+	 */
765
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
766
+	{
767
+		$tax_line_item = EE_Line_Item::new_instance(
768
+			[
769
+				'LIN_code'  => 'taxes',
770
+				'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
771
+				'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
772
+				'LIN_order' => 1000,// this should always come last
773
+			]
774
+		);
775
+		$tax_line_item = apply_filters(
776
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
777
+			$tax_line_item
778
+		);
779
+		self::set_TXN_ID($tax_line_item, $transaction);
780
+		$total_line_item->add_child_line_item($tax_line_item);
781
+		// and lastly, add the actual taxes
782
+		self::apply_taxes($total_line_item);
783
+		return $tax_line_item;
784
+	}
785
+
786
+
787
+	/**
788
+	 * Creates a default items subtotal line item
789
+	 *
790
+	 * @param EE_Line_Item   $pre_tax_line_item
791
+	 * @param EE_Transaction $transaction
792
+	 * @param EE_Event       $event
793
+	 * @return EE_Line_Item
794
+	 * @throws EE_Error
795
+	 * @throws InvalidArgumentException
796
+	 * @throws InvalidDataTypeException
797
+	 * @throws InvalidInterfaceException
798
+	 * @throws ReflectionException
799
+	 */
800
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
801
+	{
802
+		$event_line_item = EE_Line_Item::new_instance(
803
+			[
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
+		);
812
+		$event_line_item = apply_filters(
813
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
814
+			$event_line_item
815
+		);
816
+		self::set_TXN_ID($event_line_item, $transaction);
817
+		$pre_tax_line_item->add_child_line_item($event_line_item);
818
+		return $event_line_item;
819
+	}
820
+
821
+
822
+	/**
823
+	 * Gets what the event ticket's code SHOULD be
824
+	 *
825
+	 * @param EE_Event $event
826
+	 * @return string
827
+	 * @throws EE_Error|ReflectionException
828
+	 */
829
+	public static function get_event_code($event)
830
+	{
831
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
832
+	}
833
+
834
+
835
+	/**
836
+	 * Gets the event name
837
+	 *
838
+	 * @param EE_Event $event
839
+	 * @return string
840
+	 * @throws EE_Error
841
+	 */
842
+	public static function get_event_name($event)
843
+	{
844
+		return $event instanceof EE_Event
845
+			? mb_substr($event->name(), 0, 245)
846
+			: esc_html__('Event', 'event_espresso');
847
+	}
848
+
849
+
850
+	/**
851
+	 * Gets the event excerpt
852
+	 *
853
+	 * @param EE_Event $event
854
+	 * @return string
855
+	 * @throws EE_Error
856
+	 */
857
+	public static function get_event_desc($event)
858
+	{
859
+		return $event instanceof EE_Event ? $event->short_description() : '';
860
+	}
861
+
862
+
863
+	/**
864
+	 * Given the grand total line item and a ticket, finds the event sub-total
865
+	 * line item the ticket's purchase should be added onto
866
+	 *
867
+	 * @access public
868
+	 * @param EE_Line_Item $grand_total the grand total line item
869
+	 * @param EE_Ticket    $ticket
870
+	 * @return EE_Line_Item
871
+	 * @throws EE_Error
872
+	 * @throws InvalidArgumentException
873
+	 * @throws InvalidDataTypeException
874
+	 * @throws InvalidInterfaceException
875
+	 * @throws ReflectionException
876
+	 */
877
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
878
+	{
879
+		$first_datetime = $ticket->first_datetime();
880
+		if (! $first_datetime instanceof EE_Datetime) {
881
+			throw new EE_Error(
882
+				sprintf(
883
+					esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
884
+					$ticket->ID()
885
+				)
886
+			);
887
+		}
888
+		$event = $first_datetime->event();
889
+		if (! $event instanceof EE_Event) {
890
+			throw new EE_Error(
891
+				sprintf(
892
+					esc_html__(
893
+						'The supplied ticket (ID %d) has no event data associated with it.',
894
+						'event_espresso'
895
+					),
896
+					$ticket->ID()
897
+				)
898
+			);
899
+		}
900
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
901
+		if (! $events_sub_total instanceof EE_Line_Item) {
902
+			throw new EE_Error(
903
+				sprintf(
904
+					esc_html__(
905
+						'There is no events sub-total for ticket %s on total line item %d',
906
+						'event_espresso'
907
+					),
908
+					$ticket->ID(),
909
+					$grand_total->ID()
910
+				)
911
+			);
912
+		}
913
+		return $events_sub_total;
914
+	}
915
+
916
+
917
+	/**
918
+	 * Gets the event line item
919
+	 *
920
+	 * @param EE_Line_Item $grand_total
921
+	 * @param EE_Event     $event
922
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
923
+	 * @throws EE_Error
924
+	 * @throws InvalidArgumentException
925
+	 * @throws InvalidDataTypeException
926
+	 * @throws InvalidInterfaceException
927
+	 * @throws ReflectionException
928
+	 */
929
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
930
+	{
931
+		/** @type EE_Event $event */
932
+		$event           = EEM_Event::instance()->ensure_is_obj($event, true);
933
+		$event_line_item = null;
934
+		$found           = false;
935
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
936
+			// default event subtotal, we should only ever find this the first time this method is called
937
+			if (! $event_line_item->OBJ_ID()) {
938
+				// let's use this! but first... set the event details
939
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
940
+				$found = true;
941
+				break;
942
+			}
943
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
944
+				// found existing line item for this event in the cart, so break out of loop and use this one
945
+				$found = true;
946
+				break;
947
+			}
948
+		}
949
+		if (! $found) {
950
+			// there is no event sub-total yet, so add it
951
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
952
+			// create a new "event" subtotal below that
953
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
954
+			// and set the event details
955
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
956
+		}
957
+		return $event_line_item;
958
+	}
959
+
960
+
961
+	/**
962
+	 * Creates a default items subtotal line item
963
+	 *
964
+	 * @param EE_Line_Item   $event_line_item
965
+	 * @param EE_Event       $event
966
+	 * @param EE_Transaction $transaction
967
+	 * @return void
968
+	 * @throws EE_Error
969
+	 * @throws InvalidArgumentException
970
+	 * @throws InvalidDataTypeException
971
+	 * @throws InvalidInterfaceException
972
+	 * @throws ReflectionException
973
+	 */
974
+	public static function set_event_subtotal_details(
975
+		EE_Line_Item $event_line_item,
976
+		EE_Event $event,
977
+		$transaction = null
978
+	) {
979
+		if ($event instanceof EE_Event) {
980
+			$event_line_item->set_code(self::get_event_code($event));
981
+			$event_line_item->set_name(self::get_event_name($event));
982
+			$event_line_item->set_desc(self::get_event_desc($event));
983
+			$event_line_item->set_OBJ_ID($event->ID());
984
+		}
985
+		self::set_TXN_ID($event_line_item, $transaction);
986
+	}
987
+
988
+
989
+	/**
990
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
991
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
992
+	 * any old taxes are removed
993
+	 *
994
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
995
+	 * @param bool         $update_txn_status
996
+	 * @return bool
997
+	 * @throws EE_Error
998
+	 * @throws InvalidArgumentException
999
+	 * @throws InvalidDataTypeException
1000
+	 * @throws InvalidInterfaceException
1001
+	 * @throws ReflectionException
1002
+	 * @throws RuntimeException
1003
+	 */
1004
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1005
+	{
1006
+		/** @type EEM_Price $EEM_Price */
1007
+		$EEM_Price = EE_Registry::instance()->load_model('Price');
1008
+		// get array of taxes via Price Model
1009
+		$ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
1010
+		ksort($ordered_taxes);
1011
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
1012
+		// just to be safe, remove its old tax line items
1013
+		$deleted = $taxes_line_item->delete_children_line_items();
1014
+		$updates = false;
1015
+		// loop thru taxes
1016
+		foreach ($ordered_taxes as $order => $taxes) {
1017
+			foreach ($taxes as $tax) {
1018
+				if ($tax instanceof EE_Price) {
1019
+					$tax_line_item = EE_Line_Item::new_instance(
1020
+						[
1021
+							'LIN_name'       => $tax->name(),
1022
+							'LIN_desc'       => $tax->desc(),
1023
+							'LIN_percent'    => $tax->amount(),
1024
+							'LIN_is_taxable' => false,
1025
+							'LIN_order'      => $order,
1026
+							'LIN_total'      => 0,
1027
+							'LIN_type'       => EEM_Line_Item::type_tax,
1028
+							'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1029
+							'OBJ_ID'         => $tax->ID(),
1030
+						]
1031
+					);
1032
+					$tax_line_item = apply_filters(
1033
+						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1034
+						$tax_line_item
1035
+					);
1036
+					$updates       = $taxes_line_item->add_child_line_item($tax_line_item)
1037
+						?
1038
+						true
1039
+						:
1040
+						$updates;
1041
+				}
1042
+			}
1043
+		}
1044
+		// only recalculate totals if something changed
1045
+		if ($deleted || $updates) {
1046
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1047
+			return true;
1048
+		}
1049
+		return false;
1050
+	}
1051
+
1052
+
1053
+	/**
1054
+	 * Ensures that taxes have been applied to the order, if not applies them.
1055
+	 * Returns the total amount of tax
1056
+	 *
1057
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1058
+	 * @return float
1059
+	 * @throws EE_Error
1060
+	 * @throws InvalidArgumentException
1061
+	 * @throws InvalidDataTypeException
1062
+	 * @throws InvalidInterfaceException
1063
+	 * @throws ReflectionException
1064
+	 */
1065
+	public static function ensure_taxes_applied($total_line_item)
1066
+	{
1067
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1068
+		if (! $taxes_subtotal->children()) {
1069
+			self::apply_taxes($total_line_item);
1070
+		}
1071
+		return $taxes_subtotal->total();
1072
+	}
1073
+
1074
+
1075
+	/**
1076
+	 * Deletes ALL children of the passed line item
1077
+	 *
1078
+	 * @param EE_Line_Item $parent_line_item
1079
+	 * @return bool
1080
+	 * @throws EE_Error
1081
+	 * @throws InvalidArgumentException
1082
+	 * @throws InvalidDataTypeException
1083
+	 * @throws InvalidInterfaceException
1084
+	 * @throws ReflectionException
1085
+	 */
1086
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1087
+	{
1088
+		$deleted = 0;
1089
+		foreach ($parent_line_item->children() as $child_line_item) {
1090
+			if ($child_line_item instanceof EE_Line_Item) {
1091
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1092
+				if ($child_line_item->ID()) {
1093
+					$child_line_item->delete();
1094
+					unset($child_line_item);
1095
+				} else {
1096
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1097
+				}
1098
+				$deleted++;
1099
+			}
1100
+		}
1101
+		return $deleted;
1102
+	}
1103
+
1104
+
1105
+	/**
1106
+	 * Deletes the line items as indicated by the line item code(s) provided,
1107
+	 * regardless of where they're found in the line item tree. Automatically
1108
+	 * re-calculates the line item totals and updates the related transaction. But
1109
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1110
+	 * should probably change because of this).
1111
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1112
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1113
+	 *
1114
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1115
+	 * @param array|bool|string $line_item_codes
1116
+	 * @return int number of items successfully removed
1117
+	 * @throws EE_Error|ReflectionException
1118
+	 */
1119
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1120
+	{
1121
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1122
+			EE_Error::doing_it_wrong(
1123
+				'EEH_Line_Item::delete_items',
1124
+				esc_html__(
1125
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1126
+					'event_espresso'
1127
+				),
1128
+				'4.6.18'
1129
+			);
1130
+		}
1131
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1132
+
1133
+		// check if only a single line_item_id was passed
1134
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1135
+			// place single line_item_id in an array to appear as multiple line_item_ids
1136
+			$line_item_codes = [$line_item_codes];
1137
+		}
1138
+		$removals = 0;
1139
+		// cycle thru line_item_ids
1140
+		foreach ($line_item_codes as $line_item_id) {
1141
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1142
+		}
1143
+
1144
+		if ($removals > 0) {
1145
+			$total_line_item->recalculate_taxes_and_tax_total();
1146
+			return $removals;
1147
+		} else {
1148
+			return false;
1149
+		}
1150
+	}
1151
+
1152
+
1153
+	/**
1154
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1155
+	 * tax and updates the total line item accordingly
1156
+	 *
1157
+	 * @param EE_Line_Item $total_line_item
1158
+	 * @param float        $amount
1159
+	 * @param string       $name
1160
+	 * @param string       $description
1161
+	 * @param string       $code
1162
+	 * @param boolean      $add_to_existing_line_item
1163
+	 *                          if true, and a duplicate line item with the same code is found,
1164
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1165
+	 * @return EE_Line_Item the new tax line item created
1166
+	 * @throws EE_Error
1167
+	 * @throws InvalidArgumentException
1168
+	 * @throws InvalidDataTypeException
1169
+	 * @throws InvalidInterfaceException
1170
+	 * @throws ReflectionException
1171
+	 */
1172
+	public static function set_total_tax_to(
1173
+		EE_Line_Item $total_line_item,
1174
+		$amount,
1175
+		$name = null,
1176
+		$description = null,
1177
+		$code = null,
1178
+		$add_to_existing_line_item = false
1179
+	) {
1180
+		$tax_subtotal  = self::get_taxes_subtotal($total_line_item);
1181
+		$taxable_total = $total_line_item->taxable_total();
1182
+
1183
+		if ($add_to_existing_line_item) {
1184
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1185
+			EEM_Line_Item::instance()->delete(
1186
+				[['LIN_code' => ['!=', $code], 'LIN_parent' => $tax_subtotal->ID()]]
1187
+			);
1188
+		} else {
1189
+			$new_tax = null;
1190
+			$tax_subtotal->delete_children_line_items();
1191
+		}
1192
+		if ($new_tax) {
1193
+			$new_tax->set_total($new_tax->total() + $amount);
1194
+			$percent = $taxable_total ? $new_tax->total() / $taxable_total * 100 : 0;
1195
+			$new_tax->set_percent(EEH_Line_Item::currencyFormatter()->roundForLocale($percent));
1196
+		} else {
1197
+			$percent = $taxable_total ? ($amount / $taxable_total * 100) : 0;
1198
+			// no existing tax item. Create it
1199
+			$new_tax = EE_Line_Item::new_instance(
1200
+				[
1201
+					'TXN_ID'      => $total_line_item->TXN_ID(),
1202
+					'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1203
+					'LIN_desc'    => $description ? $description : '',
1204
+					'LIN_percent' => EEH_Line_Item::currencyFormatter()->roundForLocale($percent),
1205
+					'LIN_total'   => $amount,
1206
+					'LIN_parent'  => $tax_subtotal->ID(),
1207
+					'LIN_type'    => EEM_Line_Item::type_tax,
1208
+					'LIN_code'    => $code,
1209
+				]
1210
+			);
1211
+		}
1212
+
1213
+		$new_tax = apply_filters(
1214
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1215
+			$new_tax,
1216
+			$total_line_item
1217
+		);
1218
+		$new_tax->save();
1219
+		$tax_subtotal->set_total($new_tax->total());
1220
+		$tax_subtotal->save();
1221
+		$total_line_item->recalculate_total_including_taxes();
1222
+		return $new_tax;
1223
+	}
1224
+
1225
+
1226
+	/**
1227
+	 * Makes all the line items which are children of $line_item taxable (or not).
1228
+	 * Does NOT save the line items
1229
+	 *
1230
+	 * @param EE_Line_Item $line_item
1231
+	 * @param boolean      $taxable
1232
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1233
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1234
+	 * @throws EE_Error|ReflectionException
1235
+	 */
1236
+	public static function set_line_items_taxable(
1237
+		EE_Line_Item $line_item,
1238
+		$taxable = true,
1239
+		$code_substring_for_whitelist = null
1240
+	) {
1241
+		$whitelisted = false;
1242
+		if ($code_substring_for_whitelist !== null) {
1243
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1244
+		}
1245
+		if (! $whitelisted && $line_item->is_line_item()) {
1246
+			$line_item->set_is_taxable($taxable);
1247
+		}
1248
+		foreach ($line_item->children() as $child_line_item) {
1249
+			EEH_Line_Item::set_line_items_taxable(
1250
+				$child_line_item,
1251
+				$taxable,
1252
+				$code_substring_for_whitelist
1253
+			);
1254
+		}
1255
+	}
1256
+
1257
+
1258
+	/**
1259
+	 * Gets all descendants that are event subtotals
1260
+	 *
1261
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1262
+	 * @return EE_Line_Item[]
1263
+	 * @throws EE_Error|ReflectionException
1264
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1265
+	 */
1266
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1267
+	{
1268
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1269
+	}
1270
+
1271
+
1272
+	/**
1273
+	 * Gets all descendants subtotals that match the supplied object type
1274
+	 *
1275
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1276
+	 * @param string       $obj_type
1277
+	 * @return EE_Line_Item[]
1278
+	 * @throws EE_Error|ReflectionException
1279
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
+	 */
1281
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1282
+	{
1283
+		return self::_get_descendants_by_type_and_object_type(
1284
+			$parent_line_item,
1285
+			EEM_Line_Item::type_sub_total,
1286
+			$obj_type
1287
+		);
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * Gets all descendants that are tickets
1293
+	 *
1294
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1295
+	 * @return EE_Line_Item[]
1296
+	 * @throws EE_Error|ReflectionException
1297
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1298
+	 */
1299
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1300
+	{
1301
+		return self::get_line_items_of_object_type(
1302
+			$parent_line_item,
1303
+			EEM_Line_Item::OBJ_TYPE_TICKET
1304
+		);
1305
+	}
1306
+
1307
+
1308
+	/**
1309
+	 * Gets all descendants subtotals that match the supplied object type
1310
+	 *
1311
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1312
+	 * @param string       $obj_type
1313
+	 * @return EE_Line_Item[]
1314
+	 * @throws EE_Error|ReflectionException
1315
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
+	 */
1317
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1318
+	{
1319
+		return self::_get_descendants_by_type_and_object_type(
1320
+			$parent_line_item,
1321
+			EEM_Line_Item::type_line_item,
1322
+			$obj_type
1323
+		);
1324
+	}
1325
+
1326
+
1327
+	/**
1328
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1329
+	 *
1330
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1331
+	 * @return EE_Line_Item[]
1332
+	 * @throws EE_Error|ReflectionException
1333
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1334
+	 */
1335
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1336
+	{
1337
+		return EEH_Line_Item::get_descendants_of_type(
1338
+			$parent_line_item,
1339
+			EEM_Line_Item::type_tax
1340
+		);
1341
+	}
1342
+
1343
+
1344
+	/**
1345
+	 * Gets all the real items purchased which are children of this item
1346
+	 *
1347
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1348
+	 * @return EE_Line_Item[]
1349
+	 * @throws EE_Error|ReflectionException
1350
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1351
+	 */
1352
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1353
+	{
1354
+		return EEH_Line_Item::get_descendants_of_type(
1355
+			$parent_line_item,
1356
+			EEM_Line_Item::type_line_item
1357
+		);
1358
+	}
1359
+
1360
+
1361
+	/**
1362
+	 * Gets all descendants of supplied line item that match the supplied line item type
1363
+	 *
1364
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1365
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1366
+	 * @return EE_Line_Item[]
1367
+	 * @throws EE_Error|ReflectionException
1368
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
+	 */
1370
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1371
+	{
1372
+		return self::_get_descendants_by_type_and_object_type($parent_line_item, $line_item_type);
1373
+	}
1374
+
1375
+
1376
+	/**
1377
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1378
+	 * as well
1379
+	 *
1380
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1381
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1382
+	 * @param string | NULL $obj_type         object model class name (minus prefix)
1383
+	 *                                        or NULL to ignore object type when searching
1384
+	 * @return EE_Line_Item[]
1385
+	 * @throws EE_Error|ReflectionException
1386
+	 */
1387
+	protected static function _get_descendants_by_type_and_object_type(
1388
+		EE_Line_Item $parent_line_item,
1389
+		$line_item_type,
1390
+		$obj_type = null
1391
+	) {
1392
+		$objects = [];
1393
+		foreach ($parent_line_item->children() as $child_line_item) {
1394
+			if ($child_line_item instanceof EE_Line_Item) {
1395
+				if (
1396
+					$child_line_item->type() === $line_item_type
1397
+					&& (
1398
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1399
+					)
1400
+				) {
1401
+					$objects[] = $child_line_item;
1402
+				} else {
1403
+					// go-through-all-its children looking for more matches
1404
+					$objects = array_merge(
1405
+						$objects,
1406
+						self::_get_descendants_by_type_and_object_type(
1407
+							$child_line_item,
1408
+							$line_item_type,
1409
+							$obj_type
1410
+						)
1411
+					);
1412
+				}
1413
+			}
1414
+		}
1415
+		return $objects;
1416
+	}
1417
+
1418
+
1419
+	/**
1420
+	 * Gets all descendants subtotals that match the supplied object type
1421
+	 *
1422
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1423
+	 * @param string       $OBJ_type         object type (like Event)
1424
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1425
+	 * @return EE_Line_Item[]
1426
+	 * @throws EE_Error|ReflectionException
1427
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1428
+	 */
1429
+	public static function get_line_items_by_object_type_and_IDs(
1430
+		EE_Line_Item $parent_line_item,
1431
+		$OBJ_type = '',
1432
+		$OBJ_IDs = []
1433
+	) {
1434
+		return self::_get_descendants_by_object_type_and_object_ID(
1435
+			$parent_line_item,
1436
+			$OBJ_type,
1437
+			$OBJ_IDs
1438
+		);
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1444
+	 * as well
1445
+	 *
1446
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1447
+	 * @param string       $OBJ_type         object type (like Event)
1448
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1449
+	 * @return EE_Line_Item[]
1450
+	 * @throws EE_Error|ReflectionException
1451
+	 */
1452
+	protected static function _get_descendants_by_object_type_and_object_ID(
1453
+		EE_Line_Item $parent_line_item,
1454
+		$OBJ_type,
1455
+		$OBJ_IDs
1456
+	) {
1457
+		$objects = [];
1458
+		foreach ($parent_line_item->children() as $child_line_item) {
1459
+			if ($child_line_item instanceof EE_Line_Item) {
1460
+				if (
1461
+					$child_line_item->OBJ_type() === $OBJ_type
1462
+					&& is_array($OBJ_IDs)
1463
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1464
+				) {
1465
+					$objects[] = $child_line_item;
1466
+				} else {
1467
+					// go-through-all-its children looking for more matches
1468
+					$objects = array_merge(
1469
+						$objects,
1470
+						self::_get_descendants_by_object_type_and_object_ID(
1471
+							$child_line_item,
1472
+							$OBJ_type,
1473
+							$OBJ_IDs
1474
+						)
1475
+					);
1476
+				}
1477
+			}
1478
+		}
1479
+		return $objects;
1480
+	}
1481
+
1482
+
1483
+	/**
1484
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1485
+	 * the specified type and returns it, else NULL
1486
+	 *
1487
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1488
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1489
+	 * @return EE_Line_Item
1490
+	 * @throws EE_Error
1491
+	 * @throws InvalidArgumentException
1492
+	 * @throws InvalidDataTypeException
1493
+	 * @throws InvalidInterfaceException
1494
+	 * @throws ReflectionException
1495
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
+	 */
1497
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1498
+	{
1499
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1500
+	}
1501
+
1502
+
1503
+	/**
1504
+	 * Uses a breadth-first-search in order to find the nearest descendant
1505
+	 * having the specified LIN_code and returns it, else NULL
1506
+	 *
1507
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1508
+	 * @param string       $code             any value used for LIN_code
1509
+	 * @return EE_Line_Item
1510
+	 * @throws EE_Error
1511
+	 * @throws InvalidArgumentException
1512
+	 * @throws InvalidDataTypeException
1513
+	 * @throws InvalidInterfaceException
1514
+	 * @throws ReflectionException
1515
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
+	 */
1517
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1518
+	{
1519
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1520
+	}
1521
+
1522
+
1523
+	/**
1524
+	 * Uses a breadth-first-search in order to find the nearest descendant
1525
+	 * having the specified LIN_code and returns it, else NULL
1526
+	 *
1527
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1528
+	 * @param string       $search_field     name of EE_Line_Item property
1529
+	 * @param string       $value            any value stored in $search_field
1530
+	 * @return EE_Line_Item
1531
+	 * @throws EE_Error
1532
+	 * @throws InvalidArgumentException
1533
+	 * @throws InvalidDataTypeException
1534
+	 * @throws InvalidInterfaceException
1535
+	 * @throws ReflectionException
1536
+	 */
1537
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1538
+	{
1539
+		foreach ($parent_line_item->children() as $child) {
1540
+			if ($child->get($search_field) == $value) {
1541
+				return $child;
1542
+			}
1543
+		}
1544
+		foreach ($parent_line_item->children() as $child) {
1545
+			$descendant_found = self::_get_nearest_descendant(
1546
+				$child,
1547
+				$search_field,
1548
+				$value
1549
+			);
1550
+			if ($descendant_found) {
1551
+				return $descendant_found;
1552
+			}
1553
+		}
1554
+		return null;
1555
+	}
1556
+
1557
+
1558
+	/**
1559
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1560
+	 * else recursively walks up the line item tree until a parent of type total is found,
1561
+	 *
1562
+	 * @param EE_Line_Item $line_item
1563
+	 * @return EE_Line_Item
1564
+	 * @throws EE_Error|ReflectionException
1565
+	 */
1566
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1567
+	{
1568
+		if ($line_item->TXN_ID()) {
1569
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1570
+			if ($total_line_item instanceof EE_Line_Item) {
1571
+				return $total_line_item;
1572
+			}
1573
+		} else {
1574
+			$line_item_parent = $line_item->parent();
1575
+			if ($line_item_parent instanceof EE_Line_Item) {
1576
+				if ($line_item_parent->is_total()) {
1577
+					return $line_item_parent;
1578
+				}
1579
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1580
+			}
1581
+		}
1582
+		throw new EE_Error(
1583
+			sprintf(
1584
+				esc_html__(
1585
+					'A valid grand total for line item %1$d was not found.',
1586
+					'event_espresso'
1587
+				),
1588
+				$line_item->ID()
1589
+			)
1590
+		);
1591
+	}
1592
+
1593
+
1594
+	/**
1595
+	 * Prints out a representation of the line item tree
1596
+	 *
1597
+	 * @param EE_Line_Item $line_item
1598
+	 * @param int          $indentation
1599
+	 * @return void
1600
+	 * @throws EE_Error|ReflectionException
1601
+	 */
1602
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1603
+	{
1604
+		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1605
+		if (! $indentation) {
1606
+			echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1607
+		}
1608
+		for ($i = 0; $i < $indentation; $i++) {
1609
+			echo '. ';
1610
+		}
1611
+		$breakdown = '';
1612
+		if ($line_item->is_line_item()) {
1613
+			if ($line_item->is_percent()) {
1614
+				$breakdown = "{$line_item->percent()}%";
1615
+			} else {
1616
+				$breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1617
+			}
1618
+		}
1619
+		echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1620
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1621
+		echo '$' . (string) $line_item->total();
1622
+		if ($breakdown) {
1623
+			echo " ( {$breakdown} )";
1624
+		}
1625
+		if ($line_item->is_taxable()) {
1626
+			echo '  * taxable';
1627
+		}
1628
+		if ($line_item->children()) {
1629
+			foreach ($line_item->children() as $child) {
1630
+				self::visualize($child, $indentation + 1);
1631
+			}
1632
+		}
1633
+	}
1634
+
1635
+
1636
+	/**
1637
+	 * Calculates the registration's final price, taking into account that they
1638
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1639
+	 * and receive a portion of any transaction-wide discounts.
1640
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1641
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1642
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1643
+	 * and brent's final price should be $5.50.
1644
+	 * In order to do this, we basically need to traverse the line item tree calculating
1645
+	 * the running totals (just as if we were recalculating the total), but when we identify
1646
+	 * regular line items, we need to keep track of their share of the grand total.
1647
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1648
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1649
+	 * when there are non-taxable items; otherwise they would be the same)
1650
+	 *
1651
+	 * @param EE_Line_Item $line_item
1652
+	 * @param array        $billable_ticket_quantities          array of EE_Ticket IDs and their corresponding quantity
1653
+	 *                                                          that can be included in price calculations at this
1654
+	 *                                                          moment
1655
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1656
+	 *                                                          plus the key 'total', and 'taxable' which also has keys
1657
+	 *                                                          of all the ticket IDs. Eg array(
1658
+	 *                                                          12 => 4.3
1659
+	 *                                                          23 => 8.0
1660
+	 *                                                          'total' => 16.6,
1661
+	 *                                                          'taxable' => array(
1662
+	 *                                                          12 => 10,
1663
+	 *                                                          23 => 4
1664
+	 *                                                          ).
1665
+	 *                                                          So to find which registrations have which final price,
1666
+	 *                                                          we need to find which line item is theirs, which can be
1667
+	 *                                                          done with
1668
+	 *                                                          `EEM_Line_Item::instance()->get_line_item_for_registration(
1669
+	 *                                                          $registration );`
1670
+	 * @throws EE_Error
1671
+	 * @throws InvalidArgumentException
1672
+	 * @throws InvalidDataTypeException
1673
+	 * @throws InvalidInterfaceException
1674
+	 * @throws ReflectionException
1675
+	 */
1676
+	public static function calculate_reg_final_prices_per_line_item(
1677
+		EE_Line_Item $line_item,
1678
+		$billable_ticket_quantities = []
1679
+	) {
1680
+		$running_totals = [
1681
+			'total'   => 0,
1682
+			'taxable' => ['total' => 0],
1683
+		];
1684
+		foreach ($line_item->children() as $child_line_item) {
1685
+			switch ($child_line_item->type()) {
1686
+				case EEM_Line_Item::type_sub_total:
1687
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1688
+						$child_line_item,
1689
+						$billable_ticket_quantities
1690
+					);
1691
+					// combine arrays but preserve numeric keys
1692
+					$running_totals                     =
1693
+						array_replace_recursive($running_totals_from_subtotal, $running_totals);
1694
+					$running_totals['total']            += $running_totals_from_subtotal['total'];
1695
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1696
+					break;
1697
+
1698
+				case EEM_Line_Item::type_tax_sub_total:
1699
+					// find how much the taxes percentage is
1700
+					if ($child_line_item->percent() !== 0) {
1701
+						$tax_percent_decimal = $child_line_item->percent(true);
1702
+					} else {
1703
+						$tax_percent_decimal =
1704
+							EEH_Line_Item::currencyFormatter()->roundForLocale(
1705
+								EE_Taxes::get_total_taxes_percentage() / 100
1706
+							);
1707
+					}
1708
+					// and apply to all the taxable totals, and add to the pretax totals
1709
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1710
+						// "total" and "taxable" array key is an exception
1711
+						if ($line_item_id === 'taxable') {
1712
+							continue;
1713
+						}
1714
+						$taxable_total                   = $running_totals['taxable'][ $line_item_id ];
1715
+						$running_totals[ $line_item_id ] += EEH_Line_Item::currencyFormatter()->roundForLocale(
1716
+							$taxable_total * $tax_percent_decimal
1717
+						);
1718
+					}
1719
+					break;
1720
+
1721
+				case EEM_Line_Item::type_line_item:
1722
+					// ticket line items or ????
1723
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1724
+						// kk it's a ticket
1725
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1726
+							// huh? that shouldn't happen.
1727
+							$running_totals['total'] += $child_line_item->total();
1728
+						} else {
1729
+							// its not in our running totals yet. great.
1730
+							if ($child_line_item->is_taxable()) {
1731
+								$taxable_amount = $child_line_item->unit_price();
1732
+							} else {
1733
+								$taxable_amount = 0;
1734
+							}
1735
+							// are we only calculating totals for some tickets?
1736
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1737
+								$quantity                                            =
1738
+									$billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1739
+								$running_totals[ $child_line_item->ID() ]            = $quantity
1740
+									? $child_line_item->unit_price()
1741
+									: 0;
1742
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1743
+									? $taxable_amount
1744
+									: 0;
1745
+							} else {
1746
+								$quantity                                            = $child_line_item->quantity();
1747
+								$running_totals[ $child_line_item->ID() ]            = $child_line_item->unit_price();
1748
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1749
+							}
1750
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1751
+							$running_totals['total']            += $child_line_item->unit_price() * $quantity;
1752
+						}
1753
+					} else {
1754
+						// it's some other type of item added to the cart
1755
+						// it should affect the running totals
1756
+						// basically we want to convert it into a PERCENT modifier. Because
1757
+						// more clearly affect all registration's final price equally
1758
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1759
+							? EEH_Line_Item::currencyFormatter()->roundForLocale(
1760
+								$child_line_item->total() / $running_totals['total']
1761
+							) + 1
1762
+							: 1;
1763
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1764
+							// the "taxable" array key is an exception
1765
+							if ($line_item_id === 'taxable') {
1766
+								continue;
1767
+							}
1768
+							// update the running totals
1769
+							// yes this actually even works for the running grand total!
1770
+							$running_totals[ $line_item_id ] = EEH_Line_Item::currencyFormatter()->roundForLocale(
1771
+								$line_items_percent_of_running_total * $this_running_total
1772
+							);
1773
+
1774
+							if ($child_line_item->is_taxable()) {
1775
+								$running_totals['taxable'][ $line_item_id ] =
1776
+									EEH_Line_Item::currencyFormatter()->roundForLocale(
1777
+										$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ]
1778
+									);
1779
+							}
1780
+						}
1781
+					}
1782
+					break;
1783
+			}
1784
+		}
1785
+		return $running_totals;
1786
+	}
1787
+
1788
+
1789
+	/**
1790
+	 * @param EE_Line_Item $total_line_item
1791
+	 * @param EE_Line_Item $ticket_line_item
1792
+	 * @return float | null
1793
+	 * @throws EE_Error
1794
+	 * @throws InvalidArgumentException
1795
+	 * @throws InvalidDataTypeException
1796
+	 * @throws InvalidInterfaceException
1797
+	 * @throws OutOfRangeException
1798
+	 * @throws ReflectionException
1799
+	 */
1800
+	public static function calculate_final_price_for_ticket_line_item(
1801
+		EE_Line_Item $total_line_item,
1802
+		EE_Line_Item $ticket_line_item
1803
+	) {
1804
+		static $final_prices_per_ticket_line_item = [];
1805
+		if (empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1806
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] =
1807
+				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) could not be calculated.',
1818
+				'event_espresso'
1819
+			),
1820
+			$ticket_line_item->ID()
1821
+		);
1822
+		if (WP_DEBUG) {
1823
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1824
+			throw new OutOfRangeException($message);
1825
+		}
1826
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1827
+		return null;
1828
+	}
1829
+
1830
+
1831
+	/**
1832
+	 * Creates a duplicate of the line item tree, except only includes billable items
1833
+	 * and the portion of line items attributed to billable things
1834
+	 *
1835
+	 * @param EE_Line_Item      $line_item
1836
+	 * @param EE_Registration[] $registrations
1837
+	 * @return EE_Line_Item
1838
+	 * @throws EE_Error
1839
+	 * @throws InvalidArgumentException
1840
+	 * @throws InvalidDataTypeException
1841
+	 * @throws InvalidInterfaceException
1842
+	 * @throws ReflectionException
1843
+	 */
1844
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1845
+	{
1846
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1847
+		foreach ($line_item->children() as $child_li) {
1848
+			$copy_li->add_child_line_item(
1849
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1850
+			);
1851
+		}
1852
+		// if this is the grand total line item, make sure the totals all add up
1853
+		// (we could have duplicated this logic AS we copied the line items, but
1854
+		// it seems DRYer this way)
1855
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1856
+			$copy_li->recalculate_total_including_taxes();
1857
+		}
1858
+		return $copy_li;
1859
+	}
1860
+
1861
+
1862
+	/**
1863
+	 * Creates a new, unsaved line item from $line_item that factors in the
1864
+	 * number of billable registrations on $registrations.
1865
+	 *
1866
+	 * @param EE_Line_Item      $line_item
1867
+	 * @param EE_Registration[] $registrations
1868
+	 * @return EE_Line_Item
1869
+	 * @throws EE_Error
1870
+	 * @throws InvalidArgumentException
1871
+	 * @throws InvalidDataTypeException
1872
+	 * @throws InvalidInterfaceException
1873
+	 * @throws ReflectionException
1874
+	 */
1875
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1876
+	{
1877
+		$new_li_fields = $line_item->model_field_array();
1878
+		if (
1879
+			$line_item->type() === EEM_Line_Item::type_line_item
1880
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1881
+		) {
1882
+			$count = 0;
1883
+			foreach ($registrations as $registration) {
1884
+				if (
1885
+					$line_item->OBJ_ID() === $registration->ticket_ID() &&
1886
+					in_array(
1887
+						$registration->status_ID(),
1888
+						EEM_Registration::reg_statuses_that_allow_payment(),
1889
+						true
1890
+					)
1891
+				) {
1892
+					$count++;
1893
+				}
1894
+			}
1895
+			$new_li_fields['LIN_quantity'] = $count;
1896
+		}
1897
+		// don't set the total. We'll leave that up to the code that calculates it
1898
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1899
+		return EE_Line_Item::new_instance($new_li_fields);
1900
+	}
1901
+
1902
+
1903
+	/**
1904
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1905
+	 * are removed, and line items with a quantity of 0
1906
+	 *
1907
+	 * @param EE_Line_Item $line_item |null
1908
+	 * @return EE_Line_Item|null
1909
+	 * @throws EE_Error
1910
+	 * @throws InvalidArgumentException
1911
+	 * @throws InvalidDataTypeException
1912
+	 * @throws InvalidInterfaceException
1913
+	 * @throws ReflectionException
1914
+	 */
1915
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1916
+	{
1917
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1918
+		if ($copied_li === null) {
1919
+			return null;
1920
+		}
1921
+		// if this is an event subtotal, we want to only include it if it
1922
+		// has a non-zero total and at least one ticket line item child
1923
+		$ticket_children = 0;
1924
+		foreach ($line_item->children() as $child_li) {
1925
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1926
+			if ($child_li_copy !== null) {
1927
+				$copied_li->add_child_line_item($child_li_copy);
1928
+				if (
1929
+					$child_li_copy->type() === EEM_Line_Item::type_line_item
1930
+					&& $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1931
+				) {
1932
+					$ticket_children++;
1933
+				}
1934
+			}
1935
+		}
1936
+		// if this is an event subtotal with NO ticket children
1937
+		// we basically want to ignore it
1938
+		if (
1939
+			$ticket_children === 0
1940
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1941
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1942
+			&& $line_item->total() === 0
1943
+		) {
1944
+			return null;
1945
+		}
1946
+		return $copied_li;
1947
+	}
1948
+
1949
+
1950
+	/**
1951
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1952
+	 * with a total of 0, or a subtotal of 0, returns null instead
1953
+	 *
1954
+	 * @param EE_Line_Item $line_item
1955
+	 * @return EE_Line_Item
1956
+	 * @throws EE_Error
1957
+	 * @throws InvalidArgumentException
1958
+	 * @throws InvalidDataTypeException
1959
+	 * @throws InvalidInterfaceException
1960
+	 * @throws ReflectionException
1961
+	 */
1962
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1963
+	{
1964
+		if (
1965
+			$line_item->type() === EEM_Line_Item::type_line_item
1966
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1967
+			&& $line_item->quantity() === 0
1968
+		) {
1969
+			return null;
1970
+		}
1971
+		$new_li_fields = $line_item->model_field_array();
1972
+		// don't set the total. We'll leave that up to the code that calculates it
1973
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1974
+		return EE_Line_Item::new_instance($new_li_fields);
1975
+	}
1976
+
1977
+
1978
+	/**
1979
+	 * Cycles through all of the ticket line items for the supplied total line item
1980
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1981
+	 *
1982
+	 * @param EE_Line_Item $total_line_item
1983
+	 * @throws EE_Error
1984
+	 * @throws InvalidArgumentException
1985
+	 * @throws InvalidDataTypeException
1986
+	 * @throws InvalidInterfaceException
1987
+	 * @throws ReflectionException
1988
+	 * @since 4.9.79.p
1989
+	 */
1990
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1991
+	{
1992
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1993
+		foreach ($ticket_line_items as $ticket_line_item) {
1994
+			if (
1995
+				$ticket_line_item instanceof EE_Line_Item
1996
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1997
+			) {
1998
+				$ticket = $ticket_line_item->ticket();
1999
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2000
+					$ticket_line_item->set_is_taxable($ticket->taxable());
2001
+					$ticket_line_item->save();
2002
+				}
2003
+			}
2004
+		}
2005
+	}
2006
+
2007
+
2008
+
2009
+	/**************************************** @DEPRECATED METHODS *************************************** */
2010
+	/**
2011
+	 * @param EE_Line_Item $total_line_item
2012
+	 * @return EE_Line_Item
2013
+	 * @throws EE_Error
2014
+	 * @throws InvalidArgumentException
2015
+	 * @throws InvalidDataTypeException
2016
+	 * @throws InvalidInterfaceException
2017
+	 * @throws ReflectionException
2018
+	 * @deprecated
2019
+	 */
2020
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
2021
+	{
2022
+		EE_Error::doing_it_wrong(
2023
+			'EEH_Line_Item::get_items_subtotal()',
2024
+			sprintf(
2025
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2026
+				'EEH_Line_Item::get_pre_tax_subtotal()'
2027
+			),
2028
+			'4.6.0'
2029
+		);
2030
+		return self::get_pre_tax_subtotal($total_line_item);
2031
+	}
2032
+
2033
+
2034
+	/**
2035
+	 * @param EE_Transaction $transaction
2036
+	 * @return EE_Line_Item
2037
+	 * @throws EE_Error
2038
+	 * @throws InvalidArgumentException
2039
+	 * @throws InvalidDataTypeException
2040
+	 * @throws InvalidInterfaceException
2041
+	 * @throws ReflectionException
2042
+	 * @deprecated
2043
+	 */
2044
+	public static function create_default_total_line_item($transaction = null)
2045
+	{
2046
+		EE_Error::doing_it_wrong(
2047
+			'EEH_Line_Item::create_default_total_line_item()',
2048
+			sprintf(
2049
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2050
+				'EEH_Line_Item::create_total_line_item()'
2051
+			),
2052
+			'4.6.0'
2053
+		);
2054
+		return self::create_total_line_item($transaction);
2055
+	}
2056
+
2057
+
2058
+	/**
2059
+	 * @param EE_Line_Item   $total_line_item
2060
+	 * @param EE_Transaction $transaction
2061
+	 * @return EE_Line_Item
2062
+	 * @throws EE_Error
2063
+	 * @throws InvalidArgumentException
2064
+	 * @throws InvalidDataTypeException
2065
+	 * @throws InvalidInterfaceException
2066
+	 * @throws ReflectionException
2067
+	 * @deprecated
2068
+	 */
2069
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2070
+	{
2071
+		EE_Error::doing_it_wrong(
2072
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2073
+			sprintf(
2074
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2075
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2076
+			),
2077
+			'4.6.0'
2078
+		);
2079
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2080
+	}
2081
+
2082
+
2083
+	/**
2084
+	 * @param EE_Line_Item   $total_line_item
2085
+	 * @param EE_Transaction $transaction
2086
+	 * @return EE_Line_Item
2087
+	 * @throws EE_Error
2088
+	 * @throws InvalidArgumentException
2089
+	 * @throws InvalidDataTypeException
2090
+	 * @throws InvalidInterfaceException
2091
+	 * @throws ReflectionException
2092
+	 * @deprecated
2093
+	 */
2094
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2095
+	{
2096
+		EE_Error::doing_it_wrong(
2097
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2098
+			sprintf(
2099
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2100
+				'EEH_Line_Item::create_taxes_subtotal()'
2101
+			),
2102
+			'4.6.0'
2103
+		);
2104
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2105
+	}
2106
+
2107
+
2108
+	/**
2109
+	 * @param EE_Line_Item   $total_line_item
2110
+	 * @param EE_Transaction $transaction
2111
+	 * @return EE_Line_Item
2112
+	 * @throws EE_Error
2113
+	 * @throws InvalidArgumentException
2114
+	 * @throws InvalidDataTypeException
2115
+	 * @throws InvalidInterfaceException
2116
+	 * @throws ReflectionException
2117
+	 * @deprecated
2118
+	 */
2119
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2120
+	{
2121
+		EE_Error::doing_it_wrong(
2122
+			'EEH_Line_Item::create_default_event_subtotal()',
2123
+			sprintf(
2124
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2125
+				'EEH_Line_Item::create_event_subtotal()'
2126
+			),
2127
+			'4.6.0'
2128
+		);
2129
+		return self::create_event_subtotal($total_line_item, $transaction);
2130
+	}
2131 2131
 }
Please login to merge, or discard this patch.
Spacing   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -30,7 +30,7 @@  discard block
 block discarded – undo
30 30
     private static function currencyFormatter()
31 31
     {
32 32
         static $currency_formatter;
33
-        if (! $currency_formatter instanceof CurrencyFormatter) {
33
+        if ( ! $currency_formatter instanceof CurrencyFormatter) {
34 34
             $currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
35 35
         }
36 36
         return $currency_formatter;
@@ -83,7 +83,7 @@  discard block
 block discarded – undo
83 83
                 'LIN_code'       => $code,
84 84
             ]
85 85
         );
86
-        $line_item      = apply_filters(
86
+        $line_item = apply_filters(
87 87
             'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
88 88
             $line_item,
89 89
             $parent_line_item
@@ -161,7 +161,7 @@  discard block
 block discarded – undo
161 161
      */
162 162
     public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
163 163
     {
164
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
165 165
             throw new EE_Error(
166 166
                 sprintf(
167 167
                     esc_html__(
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
         // either increment the qty for an existing ticket
177 177
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
178 178
         // or add a new one
179
-        if (! $line_item instanceof EE_Line_Item) {
179
+        if ( ! $line_item instanceof EE_Line_Item) {
180 180
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
181 181
         }
182 182
         $total_line_item->recalculate_total_including_taxes();
@@ -238,7 +238,7 @@  discard block
 block discarded – undo
238 238
      */
239 239
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
240 240
     {
241
-        if (! $line_item->is_percent()) {
241
+        if ( ! $line_item->is_percent()) {
242 242
             $qty += $line_item->quantity();
243 243
             $line_item->set_quantity($qty);
244 244
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
      */
268 268
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
269 269
     {
270
-        if (! $line_item->is_percent()) {
270
+        if ( ! $line_item->is_percent()) {
271 271
             $qty = $line_item->quantity() - $qty;
272 272
             $qty = max($qty, 0);
273 273
             $line_item->set_quantity($qty);
@@ -296,7 +296,7 @@  discard block
 block discarded – undo
296 296
      */
297 297
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
298 298
     {
299
-        if (! $line_item->is_percent()) {
299
+        if ( ! $line_item->is_percent()) {
300 300
             $line_item->set_quantity($new_quantity);
301 301
             $line_item->set_total($line_item->unit_price() * $new_quantity);
302 302
             $line_item->save();
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
         $line_item = EE_Line_Item::new_instance(
338 338
             [
339 339
                 'LIN_name'       => $ticket->name(),
340
-                'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
+                'LIN_desc'       => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
341 341
                 'LIN_unit_price' => $ticket->price(),
342 342
                 'LIN_quantity'   => $qty,
343 343
                 'LIN_is_taxable' => $ticket->taxable(),
@@ -494,7 +494,7 @@  discard block
 block discarded – undo
494 494
                             'event_espresso'
495 495
                         ),
496 496
                         $ticket_line_item->name(),
497
-                        current_time(get_option('date_format') . ' ' . get_option('time_format'))
497
+                        current_time(get_option('date_format').' '.get_option('time_format'))
498 498
                     ),
499 499
                     'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
500 500
                     'LIN_quantity'   => $qty,
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
         );
559 559
         $cancellation_line_item = reset($cancellation_line_item);
560 560
         // verify that this ticket was indeed previously cancelled
561
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
561
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
562 562
             return false;
563 563
         }
564 564
         if ($cancellation_line_item->quantity() > $qty) {
@@ -769,7 +769,7 @@  discard block
 block discarded – undo
769 769
                 'LIN_code'  => 'taxes',
770 770
                 'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
771 771
                 'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
772
-                'LIN_order' => 1000,// this should always come last
772
+                'LIN_order' => 1000, // this should always come last
773 773
             ]
774 774
         );
775 775
         $tax_line_item = apply_filters(
@@ -828,7 +828,7 @@  discard block
 block discarded – undo
828 828
      */
829 829
     public static function get_event_code($event)
830 830
     {
831
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
832 832
     }
833 833
 
834 834
 
@@ -877,7 +877,7 @@  discard block
 block discarded – undo
877 877
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
878 878
     {
879 879
         $first_datetime = $ticket->first_datetime();
880
-        if (! $first_datetime instanceof EE_Datetime) {
880
+        if ( ! $first_datetime instanceof EE_Datetime) {
881 881
             throw new EE_Error(
882 882
                 sprintf(
883 883
                     esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
@@ -886,7 +886,7 @@  discard block
 block discarded – undo
886 886
             );
887 887
         }
888 888
         $event = $first_datetime->event();
889
-        if (! $event instanceof EE_Event) {
889
+        if ( ! $event instanceof EE_Event) {
890 890
             throw new EE_Error(
891 891
                 sprintf(
892 892
                     esc_html__(
@@ -898,7 +898,7 @@  discard block
 block discarded – undo
898 898
             );
899 899
         }
900 900
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
901
-        if (! $events_sub_total instanceof EE_Line_Item) {
901
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
902 902
             throw new EE_Error(
903 903
                 sprintf(
904 904
                     esc_html__(
@@ -934,7 +934,7 @@  discard block
 block discarded – undo
934 934
         $found           = false;
935 935
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
936 936
             // default event subtotal, we should only ever find this the first time this method is called
937
-            if (! $event_line_item->OBJ_ID()) {
937
+            if ( ! $event_line_item->OBJ_ID()) {
938 938
                 // let's use this! but first... set the event details
939 939
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
940 940
                 $found = true;
@@ -946,7 +946,7 @@  discard block
 block discarded – undo
946 946
                 break;
947 947
             }
948 948
         }
949
-        if (! $found) {
949
+        if ( ! $found) {
950 950
             // there is no event sub-total yet, so add it
951 951
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
952 952
             // create a new "event" subtotal below that
@@ -1033,7 +1033,7 @@  discard block
 block discarded – undo
1033 1033
                         'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1034 1034
                         $tax_line_item
1035 1035
                     );
1036
-                    $updates       = $taxes_line_item->add_child_line_item($tax_line_item)
1036
+                    $updates = $taxes_line_item->add_child_line_item($tax_line_item)
1037 1037
                         ?
1038 1038
                         true
1039 1039
                         :
@@ -1065,7 +1065,7 @@  discard block
 block discarded – undo
1065 1065
     public static function ensure_taxes_applied($total_line_item)
1066 1066
     {
1067 1067
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1068
-        if (! $taxes_subtotal->children()) {
1068
+        if ( ! $taxes_subtotal->children()) {
1069 1069
             self::apply_taxes($total_line_item);
1070 1070
         }
1071 1071
         return $taxes_subtotal->total();
@@ -1131,7 +1131,7 @@  discard block
 block discarded – undo
1131 1131
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1132 1132
 
1133 1133
         // check if only a single line_item_id was passed
1134
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1134
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
1135 1135
             // place single line_item_id in an array to appear as multiple line_item_ids
1136 1136
             $line_item_codes = [$line_item_codes];
1137 1137
         }
@@ -1242,7 +1242,7 @@  discard block
 block discarded – undo
1242 1242
         if ($code_substring_for_whitelist !== null) {
1243 1243
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1244 1244
         }
1245
-        if (! $whitelisted && $line_item->is_line_item()) {
1245
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1246 1246
             $line_item->set_is_taxable($taxable);
1247 1247
         }
1248 1248
         foreach ($line_item->children() as $child_line_item) {
@@ -1602,7 +1602,7 @@  discard block
 block discarded – undo
1602 1602
     public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1603 1603
     {
1604 1604
         echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1605
-        if (! $indentation) {
1605
+        if ( ! $indentation) {
1606 1606
             echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1607 1607
         }
1608 1608
         for ($i = 0; $i < $indentation; $i++) {
@@ -1613,12 +1613,12 @@  discard block
 block discarded – undo
1613 1613
             if ($line_item->is_percent()) {
1614 1614
                 $breakdown = "{$line_item->percent()}%";
1615 1615
             } else {
1616
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1616
+                $breakdown = '$'."{$line_item->unit_price()} x {$line_item->quantity()}";
1617 1617
             }
1618 1618
         }
1619 1619
         echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1620 1620
         echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1621
-        echo '$' . (string) $line_item->total();
1621
+        echo '$'.(string) $line_item->total();
1622 1622
         if ($breakdown) {
1623 1623
             echo " ( {$breakdown} )";
1624 1624
         }
@@ -1689,7 +1689,7 @@  discard block
 block discarded – undo
1689 1689
                         $billable_ticket_quantities
1690 1690
                     );
1691 1691
                     // combine arrays but preserve numeric keys
1692
-                    $running_totals                     =
1692
+                    $running_totals =
1693 1693
                         array_replace_recursive($running_totals_from_subtotal, $running_totals);
1694 1694
                     $running_totals['total']            += $running_totals_from_subtotal['total'];
1695 1695
                     $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
@@ -1711,8 +1711,8 @@  discard block
 block discarded – undo
1711 1711
                         if ($line_item_id === 'taxable') {
1712 1712
                             continue;
1713 1713
                         }
1714
-                        $taxable_total                   = $running_totals['taxable'][ $line_item_id ];
1715
-                        $running_totals[ $line_item_id ] += EEH_Line_Item::currencyFormatter()->roundForLocale(
1714
+                        $taxable_total = $running_totals['taxable'][$line_item_id];
1715
+                        $running_totals[$line_item_id] += EEH_Line_Item::currencyFormatter()->roundForLocale(
1716 1716
                             $taxable_total * $tax_percent_decimal
1717 1717
                         );
1718 1718
                     }
@@ -1722,7 +1722,7 @@  discard block
 block discarded – undo
1722 1722
                     // ticket line items or ????
1723 1723
                     if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1724 1724
                         // kk it's a ticket
1725
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1725
+                        if (isset($running_totals[$child_line_item->ID()])) {
1726 1726
                             // huh? that shouldn't happen.
1727 1727
                             $running_totals['total'] += $child_line_item->total();
1728 1728
                         } else {
@@ -1733,19 +1733,19 @@  discard block
 block discarded – undo
1733 1733
                                 $taxable_amount = 0;
1734 1734
                             }
1735 1735
                             // are we only calculating totals for some tickets?
1736
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1736
+                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1737 1737
                                 $quantity                                            =
1738
-                                    $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1739
-                                $running_totals[ $child_line_item->ID() ]            = $quantity
1738
+                                    $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1739
+                                $running_totals[$child_line_item->ID()]            = $quantity
1740 1740
                                     ? $child_line_item->unit_price()
1741 1741
                                     : 0;
1742
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1742
+                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1743 1743
                                     ? $taxable_amount
1744 1744
                                     : 0;
1745 1745
                             } else {
1746 1746
                                 $quantity                                            = $child_line_item->quantity();
1747
-                                $running_totals[ $child_line_item->ID() ]            = $child_line_item->unit_price();
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1747
+                                $running_totals[$child_line_item->ID()]            = $child_line_item->unit_price();
1748
+                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1749 1749
                             }
1750 1750
                             $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1751 1751
                             $running_totals['total']            += $child_line_item->unit_price() * $quantity;
@@ -1767,14 +1767,14 @@  discard block
 block discarded – undo
1767 1767
                             }
1768 1768
                             // update the running totals
1769 1769
                             // yes this actually even works for the running grand total!
1770
-                            $running_totals[ $line_item_id ] = EEH_Line_Item::currencyFormatter()->roundForLocale(
1770
+                            $running_totals[$line_item_id] = EEH_Line_Item::currencyFormatter()->roundForLocale(
1771 1771
                                 $line_items_percent_of_running_total * $this_running_total
1772 1772
                             );
1773 1773
 
1774 1774
                             if ($child_line_item->is_taxable()) {
1775
-                                $running_totals['taxable'][ $line_item_id ] =
1775
+                                $running_totals['taxable'][$line_item_id] =
1776 1776
                                     EEH_Line_Item::currencyFormatter()->roundForLocale(
1777
-                                        $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ]
1777
+                                        $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id]
1778 1778
                                     );
1779 1779
                             }
1780 1780
                         }
@@ -1802,15 +1802,15 @@  discard block
 block discarded – undo
1802 1802
         EE_Line_Item $ticket_line_item
1803 1803
     ) {
1804 1804
         static $final_prices_per_ticket_line_item = [];
1805
-        if (empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1806
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] =
1805
+        if (empty($final_prices_per_ticket_line_item[$total_line_item->ID()])) {
1806
+            $final_prices_per_ticket_line_item[$total_line_item->ID()] =
1807 1807
                 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__(
@@ -1820,7 +1820,7 @@  discard block
 block discarded – undo
1820 1820
             $ticket_line_item->ID()
1821 1821
         );
1822 1822
         if (WP_DEBUG) {
1823
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1823
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1824 1824
             throw new OutOfRangeException($message);
1825 1825
         }
1826 1826
         EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/helpers/EEH_Money.helper.php 2 patches
Indentation   +272 added lines, -272 removed lines patch added patch discarded remove patch
@@ -14,299 +14,299 @@
 block discarded – undo
14 14
  */
15 15
 class EEH_Money extends EEH_Base
16 16
 {
17
-    /**
18
-     * @var CurrencyFormatter
19
-     * @since   $VID:$
20
-     */
21
-    private static $currency_formatter;
17
+	/**
18
+	 * @var CurrencyFormatter
19
+	 * @since   $VID:$
20
+	 */
21
+	private static $currency_formatter;
22 22
 
23 23
 
24
-    /**
25
-     * @return CurrencyFormatter
26
-     * @since   $VID:$
27
-     */
28
-    private static function getCurrencyFormatter()
29
-    {
30
-        if (! EEH_Money::$currency_formatter instanceof CurrencyFormatter) {
31
-            EEH_Money::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
32
-        }
33
-        return EEH_Money::$currency_formatter;
34
-    }
24
+	/**
25
+	 * @return CurrencyFormatter
26
+	 * @since   $VID:$
27
+	 */
28
+	private static function getCurrencyFormatter()
29
+	{
30
+		if (! EEH_Money::$currency_formatter instanceof CurrencyFormatter) {
31
+			EEH_Money::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
32
+		}
33
+		return EEH_Money::$currency_formatter;
34
+	}
35 35
 
36 36
 
37
-    /**
38
-     * @param string $CNT_ISO
39
-     * @return Locale
40
-     * @throws EE_Error
41
-     * @since   $VID:$
42
-     */
43
-    private static function getLocaleForCountryISO($CNT_ISO)
44
-    {
45
-        $currency_config = EEH_Money::get_currency_config($CNT_ISO);
46
-        return EEH_Money::getCurrencyFormatter()->getLocaleForCurrencyISO($currency_config->code);
47
-    }
37
+	/**
38
+	 * @param string $CNT_ISO
39
+	 * @return Locale
40
+	 * @throws EE_Error
41
+	 * @since   $VID:$
42
+	 */
43
+	private static function getLocaleForCountryISO($CNT_ISO)
44
+	{
45
+		$currency_config = EEH_Money::get_currency_config($CNT_ISO);
46
+		return EEH_Money::getCurrencyFormatter()->getLocaleForCurrencyISO($currency_config->code);
47
+	}
48 48
 
49 49
 
50
-    /**
51
-     * @param float|int|string $money_value
52
-     * @param int|null         $format one of the CurrencyFormatter::FORMAT_* constants
53
-     * @param string|Locale    $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
54
-     * @return string
55
-     * @since $VID:$
56
-     */
57
-    public static function formatForLocale(
58
-        $money_value,
59
-        $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
60
-        $locale = ''
61
-    ) {
62
-        return EEH_Money::getCurrencyFormatter()->formatForLocale($money_value, $format, null, $locale);
63
-    }
50
+	/**
51
+	 * @param float|int|string $money_value
52
+	 * @param int|null         $format one of the CurrencyFormatter::FORMAT_* constants
53
+	 * @param string|Locale    $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
54
+	 * @return string
55
+	 * @since $VID:$
56
+	 */
57
+	public static function formatForLocale(
58
+		$money_value,
59
+		$format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
60
+		$locale = ''
61
+	) {
62
+		return EEH_Money::getCurrencyFormatter()->formatForLocale($money_value, $format, null, $locale);
63
+	}
64 64
 
65 65
 
66
-    /**
67
-     * @param string|Locale $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
68
-     * @return string ex: 'USD'
69
-     * @since $VID:$
70
-     */
71
-    public static function getCurrencyIsoCodeForLocale($locale = '')
72
-    {
73
-        return EEH_Money::getCurrencyFormatter()->getCurrencyIsoCodeForLocale($locale);
74
-    }
66
+	/**
67
+	 * @param string|Locale $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
68
+	 * @return string ex: 'USD'
69
+	 * @since $VID:$
70
+	 */
71
+	public static function getCurrencyIsoCodeForLocale($locale = '')
72
+	{
73
+		return EEH_Money::getCurrencyFormatter()->getCurrencyIsoCodeForLocale($locale);
74
+	}
75 75
 
76 76
 
77
-    /**
78
-     * @param string|Locale $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
79
-     * @return string ex: '$'
80
-     * @since $VID:$
81
-     */
82
-    public static function getCurrencySymbolForLocale($locale = '')
83
-    {
84
-        return EEH_Money::getCurrencyFormatter()->getCurrencySymbolForLocale($locale);
85
-    }
77
+	/**
78
+	 * @param string|Locale $locale locale name ex: en_US, en_CA, fr_CA, de_DE, etc, or Locale object
79
+	 * @return string ex: '$'
80
+	 * @since $VID:$
81
+	 */
82
+	public static function getCurrencySymbolForLocale($locale = '')
83
+	{
84
+		return EEH_Money::getCurrencyFormatter()->getCurrencySymbolForLocale($locale);
85
+	}
86 86
 
87
-    /**
88
-     * This removes all localized money formatting from the incoming value
89
-     * Note: uses this site's currency settings for deciding what is considered a
90
-     * "thousands separator" (usually the character "," )
91
-     * and what is a "decimal mark" (usually the character ".")
92
-     *
93
-     * @param int|float|string $money_value
94
-     * @param string           $CNT_ISO
95
-     * @return float
96
-     * @throws EE_Error
97
-     */
98
-    public static function strip_localized_money_formatting($money_value, $CNT_ISO = '')
99
-    {
100
-        $locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
101
-        return EEH_Money::getCurrencyFormatter()->parseForLocale($money_value, $locale);
102
-    }
87
+	/**
88
+	 * This removes all localized money formatting from the incoming value
89
+	 * Note: uses this site's currency settings for deciding what is considered a
90
+	 * "thousands separator" (usually the character "," )
91
+	 * and what is a "decimal mark" (usually the character ".")
92
+	 *
93
+	 * @param int|float|string $money_value
94
+	 * @param string           $CNT_ISO
95
+	 * @return float
96
+	 * @throws EE_Error
97
+	 */
98
+	public static function strip_localized_money_formatting($money_value, $CNT_ISO = '')
99
+	{
100
+		$locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
101
+		return EEH_Money::getCurrencyFormatter()->parseForLocale($money_value, $locale);
102
+	}
103 103
 
104 104
 
105
-    /**
106
-     * This converts an incoming localized money value into a standard float item (to three decimal places)
107
-     * Only use this if you know the $money_value follows your currency configuration's
108
-     * settings. Note: this uses this site's currency settings for deciding what is considered a
109
-     * "thousands separator" (usually the character "," )
110
-     * and what is a "decimal mark" (usually the character ".")
111
-     *
112
-     * @param int|string $money_value
113
-     * @return float
114
-     */
115
-    public static function convert_to_float_from_localized_money($money_value)
116
-    {
117
-        return EEH_Money::getCurrencyFormatter()->precisionRound(
118
-            EEH_Money::getCurrencyFormatter()->parseForLocale($money_value)
119
-        );
120
-    }
105
+	/**
106
+	 * This converts an incoming localized money value into a standard float item (to three decimal places)
107
+	 * Only use this if you know the $money_value follows your currency configuration's
108
+	 * settings. Note: this uses this site's currency settings for deciding what is considered a
109
+	 * "thousands separator" (usually the character "," )
110
+	 * and what is a "decimal mark" (usually the character ".")
111
+	 *
112
+	 * @param int|string $money_value
113
+	 * @return float
114
+	 */
115
+	public static function convert_to_float_from_localized_money($money_value)
116
+	{
117
+		return EEH_Money::getCurrencyFormatter()->precisionRound(
118
+			EEH_Money::getCurrencyFormatter()->parseForLocale($money_value)
119
+		);
120
+	}
121 121
 
122 122
 
123
-    /**
124
-     * For comparing floats. Default operator is '=', but see the $operator below for all options.
125
-     * This should be used to compare floats instead of normal '==' because floats
126
-     * are inherently imprecise, and so you can sometimes have two floats that appear to be identical
127
-     * but actually differ by 0.00000001.
128
-     *
129
-     * @see http://biostall.com/php-function-to-compare-floating-point-numbers
130
-     * @param float  $float1
131
-     * @param float  $float2
132
-     * @param string $operator The operator. Valid options are =, <=, <, >=, >, <>, eq, lt, lte, gt, gte, ne
133
-     * @return bool whether the equation is true or false
134
-     * @throws EE_Error
135
-     */
136
-    public static function compare_floats($float1, $float2, $operator = '=')
137
-    {
138
-        // Check numbers to 5 digits of precision
139
-        $epsilon = 0.00001;
140
-        $float1 = (float) $float1;
141
-        $float2 = (float) $float2;
142
-        switch ($operator) {
143
-            // equal
144
-            case "=":
145
-            case "==":
146
-            case "===":
147
-            case "eq":
148
-                if (abs($float1 - $float2) < $epsilon) {
149
-                    return true;
150
-                }
151
-                break;
152
-            // less than
153
-            case "<":
154
-            case "lt":
155
-                if (abs($float1 - $float2) < $epsilon) {
156
-                    return false;
157
-                } else {
158
-                    if ($float1 < $float2) {
159
-                        return true;
160
-                    }
161
-                }
162
-                break;
163
-            // less than or equal
164
-            case "<=":
165
-            case "lte":
166
-                if (
167
-                    EEH_Money::compare_floats($float1, $float2, '<')
168
-                    || EEH_Money::compare_floats($float1, $float2, '=')
169
-                ) {
170
-                    return true;
171
-                }
172
-                break;
173
-            // greater than
174
-            case ">":
175
-            case "gt":
176
-                if (abs($float1 - $float2) < $epsilon) {
177
-                    return false;
178
-                } else {
179
-                    if ($float1 > $float2) {
180
-                        return true;
181
-                    }
182
-                }
183
-                break;
184
-            // greater than or equal
185
-            case ">=":
186
-            case "gte":
187
-                if (
188
-                    EEH_Money::compare_floats($float1, $float2, '>')
189
-                    || EEH_Money::compare_floats($float1, $float2, '=')
190
-                ) {
191
-                    return true;
192
-                }
193
-                break;
194
-            case "<>":
195
-            case "!=":
196
-            case "ne":
197
-                if (abs($float1 - $float2) > $epsilon) {
198
-                    return true;
199
-                }
200
-                break;
201
-            default:
202
-                throw new EE_Error(
203
-                    sprintf(
204
-                        esc_html__(
205
-                            "Unknown operator %s in EEH_Money::compare_floats()",
206
-                            'event_espresso'
207
-                        ),
208
-                        $operator
209
-                    )
210
-                );
211
-        }
212
-        return false;
213
-    }
123
+	/**
124
+	 * For comparing floats. Default operator is '=', but see the $operator below for all options.
125
+	 * This should be used to compare floats instead of normal '==' because floats
126
+	 * are inherently imprecise, and so you can sometimes have two floats that appear to be identical
127
+	 * but actually differ by 0.00000001.
128
+	 *
129
+	 * @see http://biostall.com/php-function-to-compare-floating-point-numbers
130
+	 * @param float  $float1
131
+	 * @param float  $float2
132
+	 * @param string $operator The operator. Valid options are =, <=, <, >=, >, <>, eq, lt, lte, gt, gte, ne
133
+	 * @return bool whether the equation is true or false
134
+	 * @throws EE_Error
135
+	 */
136
+	public static function compare_floats($float1, $float2, $operator = '=')
137
+	{
138
+		// Check numbers to 5 digits of precision
139
+		$epsilon = 0.00001;
140
+		$float1 = (float) $float1;
141
+		$float2 = (float) $float2;
142
+		switch ($operator) {
143
+			// equal
144
+			case "=":
145
+			case "==":
146
+			case "===":
147
+			case "eq":
148
+				if (abs($float1 - $float2) < $epsilon) {
149
+					return true;
150
+				}
151
+				break;
152
+			// less than
153
+			case "<":
154
+			case "lt":
155
+				if (abs($float1 - $float2) < $epsilon) {
156
+					return false;
157
+				} else {
158
+					if ($float1 < $float2) {
159
+						return true;
160
+					}
161
+				}
162
+				break;
163
+			// less than or equal
164
+			case "<=":
165
+			case "lte":
166
+				if (
167
+					EEH_Money::compare_floats($float1, $float2, '<')
168
+					|| EEH_Money::compare_floats($float1, $float2, '=')
169
+				) {
170
+					return true;
171
+				}
172
+				break;
173
+			// greater than
174
+			case ">":
175
+			case "gt":
176
+				if (abs($float1 - $float2) < $epsilon) {
177
+					return false;
178
+				} else {
179
+					if ($float1 > $float2) {
180
+						return true;
181
+					}
182
+				}
183
+				break;
184
+			// greater than or equal
185
+			case ">=":
186
+			case "gte":
187
+				if (
188
+					EEH_Money::compare_floats($float1, $float2, '>')
189
+					|| EEH_Money::compare_floats($float1, $float2, '=')
190
+				) {
191
+					return true;
192
+				}
193
+				break;
194
+			case "<>":
195
+			case "!=":
196
+			case "ne":
197
+				if (abs($float1 - $float2) > $epsilon) {
198
+					return true;
199
+				}
200
+				break;
201
+			default:
202
+				throw new EE_Error(
203
+					sprintf(
204
+						esc_html__(
205
+							"Unknown operator %s in EEH_Money::compare_floats()",
206
+							'event_espresso'
207
+						),
208
+						$operator
209
+					)
210
+				);
211
+		}
212
+		return false;
213
+	}
214 214
 
215 215
 
216
-    /**
217
-     * This returns a localized format string suitable for jQplot.
218
-     *
219
-     * @param string $CNT_ISO  If this is provided, then will attempt to get the currency settings for the country.
220
-     *                         Otherwise will use currency settings for current active country on site.
221
-     * @return string
222
-     * @throws EE_Error
223
-     */
224
-    public static function get_format_for_jqplot($CNT_ISO = '')
225
-    {
226
-        // default format
227
-        $format          = 'f';
228
-        $locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
229
-        // first get the decimal place and number of places
230
-        $format = "%'" . $locale->currencyDecimalPoint() . $locale->decimalPrecision() . $format;
231
-        $spacer = $locale->currencySymbolSpaceB4Positive() ? ' ' : '';
232
-        // currency symbol on right side.
233
-        $format = $locale->currencySymbolB4Positive()
234
-            ? $locale->currencySymbol() . $spacer . $format
235
-            : $format . $spacer . $locale->currencySymbol();
236
-        return $format;
237
-    }
216
+	/**
217
+	 * This returns a localized format string suitable for jQplot.
218
+	 *
219
+	 * @param string $CNT_ISO  If this is provided, then will attempt to get the currency settings for the country.
220
+	 *                         Otherwise will use currency settings for current active country on site.
221
+	 * @return string
222
+	 * @throws EE_Error
223
+	 */
224
+	public static function get_format_for_jqplot($CNT_ISO = '')
225
+	{
226
+		// default format
227
+		$format          = 'f';
228
+		$locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
229
+		// first get the decimal place and number of places
230
+		$format = "%'" . $locale->currencyDecimalPoint() . $locale->decimalPrecision() . $format;
231
+		$spacer = $locale->currencySymbolSpaceB4Positive() ? ' ' : '';
232
+		// currency symbol on right side.
233
+		$format = $locale->currencySymbolB4Positive()
234
+			? $locale->currencySymbol() . $spacer . $format
235
+			: $format . $spacer . $locale->currencySymbol();
236
+		return $format;
237
+	}
238 238
 
239 239
 
240
-    /**
241
-     * This returns a localized format string suitable for usage with the Google Charts API format param.
242
-     *
243
-     * @param string $CNT_ISO  If this is provided, then will attempt to get the currency settings for the country.
244
-     *                         Otherwise will use currency settings for current active country on site.
245
-     *                         Note: GoogleCharts uses ICU pattern set
246
-     *                         (@see http://icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
247
-     * @return array
248
-     * @throws EE_Error
249
-     */
250
-    public static function get_format_for_google_charts($CNT_ISO = '')
251
-    {
252
-        $locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
253
-        $decimal_places_placeholder = str_pad('', $locale->decimalPrecision(), '0');
254
-        // first get the decimal place and number of places
255
-        $format = '#,##0.' . $decimal_places_placeholder;
256
-        // currency symbol on right side.
257
-        $format          = $locale->currencySymbolB4Positive()
258
-            ? $locale->currencySymbol() . $format
259
-            : $format
260
-              . $locale->currencySymbol();
261
-        $formatterObject = [
262
-            'decimalSymbol'  => $locale->currencyDecimalPoint(),
263
-            'groupingSymbol' => $locale->currencyThousandsSeparator(),
264
-            'fractionDigits' => $locale->decimalPrecision(),
265
-        ];
266
-        if ($locale->currencySymbolB4Positive()) {
267
-            $formatterObject['prefix'] = $locale->currencySymbol();
268
-        } else {
269
-            $formatterObject['suffix'] = $locale->currencySymbol();
270
-        }
271
-        return [
272
-            'format'          => $format,
273
-            'formatterObject' => $formatterObject,
274
-        ];
275
-    }
240
+	/**
241
+	 * This returns a localized format string suitable for usage with the Google Charts API format param.
242
+	 *
243
+	 * @param string $CNT_ISO  If this is provided, then will attempt to get the currency settings for the country.
244
+	 *                         Otherwise will use currency settings for current active country on site.
245
+	 *                         Note: GoogleCharts uses ICU pattern set
246
+	 *                         (@see http://icu-project.org/apiref/icu4c/classDecimalFormat.html#_details)
247
+	 * @return array
248
+	 * @throws EE_Error
249
+	 */
250
+	public static function get_format_for_google_charts($CNT_ISO = '')
251
+	{
252
+		$locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
253
+		$decimal_places_placeholder = str_pad('', $locale->decimalPrecision(), '0');
254
+		// first get the decimal place and number of places
255
+		$format = '#,##0.' . $decimal_places_placeholder;
256
+		// currency symbol on right side.
257
+		$format          = $locale->currencySymbolB4Positive()
258
+			? $locale->currencySymbol() . $format
259
+			: $format
260
+			  . $locale->currencySymbol();
261
+		$formatterObject = [
262
+			'decimalSymbol'  => $locale->currencyDecimalPoint(),
263
+			'groupingSymbol' => $locale->currencyThousandsSeparator(),
264
+			'fractionDigits' => $locale->decimalPrecision(),
265
+		];
266
+		if ($locale->currencySymbolB4Positive()) {
267
+			$formatterObject['prefix'] = $locale->currencySymbol();
268
+		} else {
269
+			$formatterObject['suffix'] = $locale->currencySymbol();
270
+		}
271
+		return [
272
+			'format'          => $format,
273
+			'formatterObject' => $formatterObject,
274
+		];
275
+	}
276 276
 
277 277
 
278
-    /**
279
-     * @param string $CNT_ISO
280
-     * @return EE_Currency_Config|null
281
-     * @throws EE_Error
282
-     */
283
-    public static function get_currency_config($CNT_ISO = '')
284
-    {
285
-        // if CNT_ISO passed lets try to get currency settings for it.
286
-        $currency_config = $CNT_ISO !== ''
287
-            ? new EE_Currency_Config($CNT_ISO)
288
-            : null;
289
-        // default currency settings for site if not set
290
-        if (! $currency_config instanceof EE_Currency_Config) {
291
-            $currency_config = EE_Registry::instance()->CFG->currency instanceof EE_Currency_Config
292
-                ? EE_Registry::instance()->CFG->currency
293
-                : new EE_Currency_Config();
294
-        }
295
-        return $currency_config;
296
-    }
278
+	/**
279
+	 * @param string $CNT_ISO
280
+	 * @return EE_Currency_Config|null
281
+	 * @throws EE_Error
282
+	 */
283
+	public static function get_currency_config($CNT_ISO = '')
284
+	{
285
+		// if CNT_ISO passed lets try to get currency settings for it.
286
+		$currency_config = $CNT_ISO !== ''
287
+			? new EE_Currency_Config($CNT_ISO)
288
+			: null;
289
+		// default currency settings for site if not set
290
+		if (! $currency_config instanceof EE_Currency_Config) {
291
+			$currency_config = EE_Registry::instance()->CFG->currency instanceof EE_Currency_Config
292
+				? EE_Registry::instance()->CFG->currency
293
+				: new EE_Currency_Config();
294
+		}
295
+		return $currency_config;
296
+	}
297 297
 
298 298
 
299
-    /**
300
-     * Rounds the number to a whole penny amount
301
-     *
302
-     * @param float  $amount
303
-     * @param string $currency_code
304
-     * @return float
305
-     * @throws EE_Error
306
-     */
307
-    public static function round_for_currency($amount, $currency_code = '')
308
-    {
309
-        $locale = EEH_Money::getLocaleForCountryISO($currency_code);
310
-        return EEH_Money::getCurrencyFormatter()->roundForLocale($amount, $locale);
311
-    }
299
+	/**
300
+	 * Rounds the number to a whole penny amount
301
+	 *
302
+	 * @param float  $amount
303
+	 * @param string $currency_code
304
+	 * @return float
305
+	 * @throws EE_Error
306
+	 */
307
+	public static function round_for_currency($amount, $currency_code = '')
308
+	{
309
+		$locale = EEH_Money::getLocaleForCountryISO($currency_code);
310
+		return EEH_Money::getCurrencyFormatter()->roundForLocale($amount, $locale);
311
+	}
312 312
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -27,7 +27,7 @@  discard block
 block discarded – undo
27 27
      */
28 28
     private static function getCurrencyFormatter()
29 29
     {
30
-        if (! EEH_Money::$currency_formatter instanceof CurrencyFormatter) {
30
+        if ( ! EEH_Money::$currency_formatter instanceof CurrencyFormatter) {
31 31
             EEH_Money::$currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
32 32
         }
33 33
         return EEH_Money::$currency_formatter;
@@ -224,15 +224,15 @@  discard block
 block discarded – undo
224 224
     public static function get_format_for_jqplot($CNT_ISO = '')
225 225
     {
226 226
         // default format
227
-        $format          = 'f';
227
+        $format = 'f';
228 228
         $locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
229 229
         // first get the decimal place and number of places
230
-        $format = "%'" . $locale->currencyDecimalPoint() . $locale->decimalPrecision() . $format;
230
+        $format = "%'".$locale->currencyDecimalPoint().$locale->decimalPrecision().$format;
231 231
         $spacer = $locale->currencySymbolSpaceB4Positive() ? ' ' : '';
232 232
         // currency symbol on right side.
233 233
         $format = $locale->currencySymbolB4Positive()
234
-            ? $locale->currencySymbol() . $spacer . $format
235
-            : $format . $spacer . $locale->currencySymbol();
234
+            ? $locale->currencySymbol().$spacer.$format
235
+            : $format.$spacer.$locale->currencySymbol();
236 236
         return $format;
237 237
     }
238 238
 
@@ -252,10 +252,10 @@  discard block
 block discarded – undo
252 252
         $locale = EEH_Money::getLocaleForCountryISO($CNT_ISO);
253 253
         $decimal_places_placeholder = str_pad('', $locale->decimalPrecision(), '0');
254 254
         // first get the decimal place and number of places
255
-        $format = '#,##0.' . $decimal_places_placeholder;
255
+        $format = '#,##0.'.$decimal_places_placeholder;
256 256
         // currency symbol on right side.
257
-        $format          = $locale->currencySymbolB4Positive()
258
-            ? $locale->currencySymbol() . $format
257
+        $format = $locale->currencySymbolB4Positive()
258
+            ? $locale->currencySymbol().$format
259 259
             : $format
260 260
               . $locale->currencySymbol();
261 261
         $formatterObject = [
@@ -287,7 +287,7 @@  discard block
 block discarded – undo
287 287
             ? new EE_Currency_Config($CNT_ISO)
288 288
             : null;
289 289
         // default currency settings for site if not set
290
-        if (! $currency_config instanceof EE_Currency_Config) {
290
+        if ( ! $currency_config instanceof EE_Currency_Config) {
291 291
             $currency_config = EE_Registry::instance()->CFG->currency instanceof EE_Currency_Config
292 292
                 ? EE_Registry::instance()->CFG->currency
293 293
                 : new EE_Currency_Config();
Please login to merge, or discard this patch.
core/helpers/EEH_Inflector.helper.php 1 patch
Indentation   +368 added lines, -368 removed lines patch added patch discarded remove patch
@@ -33,372 +33,372 @@
 block discarded – undo
33 33
  */
34 34
 class EEH_Inflector
35 35
 {
36
-    /**
37
-     * Just calls self::pluralize and strtolower on $word and returns it
38
-     *
39
-     * @param string $word
40
-     * @return string
41
-     */
42
-    public static function pluralize_and_lower($word)
43
-    {
44
-        return strtolower(self::pluralize($word));
45
-    }
46
-
47
-
48
-    /**
49
-     * @param string $word
50
-     * @return mixed
51
-     */
52
-    public static function singularize_and_upper($word)
53
-    {
54
-        return str_replace(' ', '_', self::humanize(self::singularize($word), 'all'));
55
-    }
56
-
57
-
58
-    /**
59
-     * Pluralizes English nouns.
60
-     *
61
-     * @access public
62
-     * @static
63
-     * @param string $word English noun to pluralize
64
-     * @return string Plural noun
65
-     */
66
-    public static function pluralize($word)
67
-    {
68
-        $plural = [
69
-            '/(quiz)$/i'               => '\1zes',
70
-            '/^(ox)$/i'                => '\1en',
71
-            '/([m|l])ouse$/i'          => '\1ice',
72
-            '/(matr|vert|ind)ix|ex$/i' => '\1ices',
73
-            '/(x|ch|ss|sh)$/i'         => '\1es',
74
-            '/([^aeiouy]|qu)ies$/i'    => '\1y',
75
-            '/([^aeiouy]|qu)y$/i'      => '\1ies',
76
-            '/(hive)$/i'               => '\1s',
77
-            '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
78
-            '/sis$/i'                  => 'ses',
79
-            '/([ti])um$/i'             => '\1a',
80
-            '/(buffal|tomat)o$/i'      => '\1oes',
81
-            '/(bu)s$/i'                => '\1ses',
82
-            '/(alias|status)/i'        => '\1es',
83
-            '/(octop|vir)us$/i'        => '\1i',
84
-            '/(ax|test)is$/i'          => '\1es',
85
-            '/s$/i'                    => 's',
86
-            '/$/'                      => 's',
87
-        ];
88
-
89
-        $uncountable = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'];
90
-
91
-        $irregular = [
92
-            'person' => 'people',
93
-            'man'    => 'men',
94
-            'child'  => 'children',
95
-            'sex'    => 'sexes',
96
-            'move'   => 'moves',
97
-        ];
98
-
99
-        $lowercased_word = strtolower($word);
100
-
101
-        foreach ($uncountable as $_uncountable) {
102
-            // even though the word "price" ends in "rice", it can be pluralized,
103
-            // so check the previous character isn't a letter
104
-            if (
105
-                substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable
106
-                && ! ctype_alpha($lowercased_word[ strlen($lowercased_word) - strlen($_uncountable) ])
107
-            ) {
108
-                return $word;
109
-            }
110
-        }
111
-
112
-        foreach ($irregular as $_plural => $_singular) {
113
-            if (preg_match('/(' . $_plural . ')$/i', $word, $arr)) {
114
-                return preg_replace('/(' . $_plural . ')$/i', substr($arr[0], 0, 1) . substr($_singular, 1), $word);
115
-            }
116
-        }
117
-
118
-        foreach ($plural as $rule => $replacement) {
119
-            if (preg_match($rule, $word)) {
120
-                return preg_replace($rule, $replacement, $word);
121
-            }
122
-        }
123
-        return false;
124
-    }
125
-
126
-
127
-    /**
128
-     * Singularizes English nouns.
129
-     *
130
-     * @access public
131
-     * @static
132
-     * @param string $word English noun to singularize
133
-     * @return string Singular noun.
134
-     */
135
-    public static function singularize($word)
136
-    {
137
-        $singular = [
138
-            '/(quiz)zes$/i'                                                    => '\1',
139
-            '/(matr)ices$/i'                                                   => '\1ix',
140
-            '/(vert|ind)ices$/i'                                               => '\1ex',
141
-            '/^(ox)en/i'                                                       => '\1',
142
-            '/(alias|status)es$/i'                                             => '\1',
143
-            '/([octop|vir])i$/i'                                               => '\1us',
144
-            '/(cris|ax|test)es$/i'                                             => '\1is',
145
-            '/(shoe)s$/i'                                                      => '\1',
146
-            '/(o)es$/i'                                                        => '\1',
147
-            '/(bus)es$/i'                                                      => '\1',
148
-            '/([m|l])ice$/i'                                                   => '\1ouse',
149
-            '/(x|ch|ss|sh)es$/i'                                               => '\1',
150
-            '/(m)ovies$/i'                                                     => '\1ovie',
151
-            '/(s)eries$/i'                                                     => '\1eries',
152
-            '/([^aeiouy]|qu)ies$/i'                                            => '\1y',
153
-            '/([lr])ves$/i'                                                    => '\1f',
154
-            '/(tive)s$/i'                                                      => '\1',
155
-            '/(hive)s$/i'                                                      => '\1',
156
-            '/([^f])ves$/i'                                                    => '\1fe',
157
-            '/(^analy)ses$/i'                                                  => '\1sis',
158
-            '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
159
-            '/([ti])a$/i'                                                      => '\1um',
160
-            '/(n)ews$/i'                                                       => '\1ews',
161
-            '/s$/i'                                                            => '',
162
-        ];
163
-
164
-        $uncountable = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'];
165
-
166
-        $irregular = [
167
-            'person' => 'people',
168
-            'man'    => 'men',
169
-            'child'  => 'children',
170
-            'sex'    => 'sexes',
171
-            'move'   => 'moves',
172
-        ];
173
-
174
-        $lowercased_word = strtolower($word);
175
-        foreach ($uncountable as $_uncountable) {
176
-            if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
177
-                return $word;
178
-            }
179
-        }
180
-
181
-        foreach ($irregular as $_plural => $_singular) {
182
-            if (preg_match('/(' . $_singular . ')$/i', $word, $arr)) {
183
-                return preg_replace('/(' . $_singular . ')$/i', substr($arr[0], 0, 1) . substr($_plural, 1), $word);
184
-            }
185
-        }
186
-
187
-        foreach ($singular as $rule => $replacement) {
188
-            if (preg_match($rule, $word)) {
189
-                return preg_replace($rule, $replacement, $word);
190
-            }
191
-        }
192
-
193
-        return $word;
194
-    }
195
-
196
-
197
-    /**
198
-     * Converts an underscored or CamelCase word into a English
199
-     * sentence.
200
-     *
201
-     * The titleize static function converts text like "WelcomePage",
202
-     * "welcome_page" or  "welcome page" to this "Welcome
203
-     * Page".
204
-     * If second parameter is set to 'first' it will only
205
-     * capitalize the first character of the title.
206
-     *
207
-     * @access public
208
-     * @static
209
-     * @param string $word      Word to format as tile
210
-     * @param string $uppercase If set to 'first' it will only uppercase the
211
-     *                          first character. Otherwise it will uppercase all
212
-     *                          the words in the title.
213
-     * @return string Text formatted as title
214
-     */
215
-    public static function titleize($word, $uppercase = '')
216
-    {
217
-        $uppercase = $uppercase === 'first' ? 'ucfirst' : 'ucwords';
218
-        return $uppercase(EEH_Inflector::humanize(EEH_Inflector::underscore($word)));
219
-    }
220
-
221
-
222
-    /**
223
-     * Returns given word as CamelCased
224
-     *
225
-     * Converts a word like "send_email" to "SendEmail". It
226
-     * will remove non alphanumeric character from the word, so
227
-     * "who's online" will be converted to "WhoSOnline"
228
-     *
229
-     * @access public
230
-     * @static
231
-     * @param string $word Word to convert to camel case
232
-     * @return string UpperCamelCasedWord
233
-     * @see    variablize
234
-     */
235
-    public static function camelize($word)
236
-    {
237
-        return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
238
-    }
239
-
240
-
241
-    /**
242
-     * Camelizes all but the first word. This is handy converting a method which followed EE4 legacy naming convention
243
-     * with the new PSR-based naming conventions
244
-     *
245
-     * @param $word
246
-     * @return string
247
-     */
248
-    public static function camelize_all_but_first($word)
249
-    {
250
-        return lcfirst(EEH_Inflector::camelize($word));
251
-    }
252
-
253
-
254
-    /**
255
-     * Converts a word "into_it_s_underscored_version"
256
-     *
257
-     * Convert any "CamelCased" or "ordinary Word" into an
258
-     * "underscored_word".
259
-     *
260
-     * This can be really useful for creating friendly URLs.
261
-     *
262
-     * @access public
263
-     * @static
264
-     * @param string $word Word to underscore
265
-     * @return string Underscored word
266
-     */
267
-    public static function underscore($word)
268
-    {
269
-        return strtolower(
270
-            preg_replace(
271
-                '/[^A-Z^a-z^0-9]+/',
272
-                '_',
273
-                preg_replace('/([a-zd])([A-Z])/', '1_2', preg_replace('/([A-Z]+)([A-Z][a-z])/', '1_2', $word))
274
-            )
275
-        );
276
-    }
277
-
278
-
279
-    /**
280
-     * Returns a human-readable string from $word
281
-     *
282
-     * Returns a human-readable string from $word, by replacing
283
-     * underscores with a space, and by upper-casing the initial
284
-     * character by default.
285
-     *
286
-     * If you need to uppercase all the words you just have to
287
-     * pass 'all' as a second parameter.
288
-     *
289
-     * @access public
290
-     * @static
291
-     * @param string $word      String to "humanize"
292
-     * @param string $uppercase If set to 'all' it will uppercase all the words
293
-     *                          instead of just the first one.
294
-     * @return string Human-readable word
295
-     */
296
-    public static function humanize($word, $uppercase = '')
297
-    {
298
-        // make special exceptions for acronyms
299
-        $word      = str_replace('wp_', 'WP_', $word);
300
-        $uppercase = $uppercase === 'all' ? 'ucwords' : 'ucfirst';
301
-        return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
302
-    }
303
-
304
-
305
-    /**
306
-     * Same as camelize but first char is underscored
307
-     *
308
-     * Converts a word like "send_email" to "sendEmail". It
309
-     * will remove non alphanumeric character from the word, so
310
-     * "who's online" will be converted to "whoSOnline"
311
-     *
312
-     * @access public
313
-     * @static
314
-     * @param string $word Word to lowerCamelCase
315
-     * @return string Returns a lowerCamelCasedWord
316
-     * @see    camelize
317
-     */
318
-    public static function variablize($word)
319
-    {
320
-        $word = EEH_Inflector::camelize($word);
321
-        return strtolower($word[0]) . substr($word, 1);
322
-    }
323
-
324
-
325
-    /**
326
-     * Converts a class name to its table name according to rails
327
-     * naming conventions.
328
-     *
329
-     * Converts "Person" to "people"
330
-     *
331
-     * @access public
332
-     * @static
333
-     * @param string $class_name Class name for getting related table_name.
334
-     * @return string plural_table_name
335
-     * @see    classify
336
-     */
337
-    public static function tableize($class_name)
338
-    {
339
-        return EEH_Inflector::pluralize(EEH_Inflector::underscore($class_name));
340
-    }
341
-
342
-
343
-    /**
344
-     * Converts a table name to its class name according to rails
345
-     * naming conventions.
346
-     *
347
-     * Converts "people" to "Person"
348
-     *
349
-     * @access public
350
-     * @static
351
-     * @param string $table_name Table name for getting related ClassName.
352
-     * @return string SingularClassName
353
-     * @see    tableize
354
-     */
355
-    public static function classify($table_name)
356
-    {
357
-        return EEH_Inflector::camelize(EEH_Inflector::singularize($table_name));
358
-    }
359
-
360
-
361
-    /**
362
-     * Converts number to its ordinal English form.
363
-     *
364
-     * This method converts 13 to 13th, 2 to 2nd ...
365
-     *
366
-     * @access public
367
-     * @static
368
-     * @param integer $number Number to get its ordinal value
369
-     * @return string Ordinal representation of given string.
370
-     */
371
-    public static function ordinalize($number)
372
-    {
373
-        if (in_array(($number % 100), range(11, 13))) {
374
-            return $number . 'th';
375
-        } else {
376
-            switch (($number % 10)) {
377
-                case 1:
378
-                    return $number . 'st';
379
-                    break;
380
-                case 2:
381
-                    return $number . 'nd';
382
-                    break;
383
-                case 3:
384
-                    return $number . 'rd';
385
-                default:
386
-                    return $number . 'th';
387
-                    break;
388
-            }
389
-        }
390
-    }
391
-
392
-
393
-    /**
394
-     * @param $string
395
-     * @return string
396
-     */
397
-    public static function add_indefinite_article($string)
398
-    {
399
-        if (strtolower($string) === 'null') {
400
-            return $string;
401
-        }
402
-        return (stripos('aeiou', $string[0]) !== false ? 'an ' : 'a ') . $string;
403
-    }
36
+	/**
37
+	 * Just calls self::pluralize and strtolower on $word and returns it
38
+	 *
39
+	 * @param string $word
40
+	 * @return string
41
+	 */
42
+	public static function pluralize_and_lower($word)
43
+	{
44
+		return strtolower(self::pluralize($word));
45
+	}
46
+
47
+
48
+	/**
49
+	 * @param string $word
50
+	 * @return mixed
51
+	 */
52
+	public static function singularize_and_upper($word)
53
+	{
54
+		return str_replace(' ', '_', self::humanize(self::singularize($word), 'all'));
55
+	}
56
+
57
+
58
+	/**
59
+	 * Pluralizes English nouns.
60
+	 *
61
+	 * @access public
62
+	 * @static
63
+	 * @param string $word English noun to pluralize
64
+	 * @return string Plural noun
65
+	 */
66
+	public static function pluralize($word)
67
+	{
68
+		$plural = [
69
+			'/(quiz)$/i'               => '\1zes',
70
+			'/^(ox)$/i'                => '\1en',
71
+			'/([m|l])ouse$/i'          => '\1ice',
72
+			'/(matr|vert|ind)ix|ex$/i' => '\1ices',
73
+			'/(x|ch|ss|sh)$/i'         => '\1es',
74
+			'/([^aeiouy]|qu)ies$/i'    => '\1y',
75
+			'/([^aeiouy]|qu)y$/i'      => '\1ies',
76
+			'/(hive)$/i'               => '\1s',
77
+			'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
78
+			'/sis$/i'                  => 'ses',
79
+			'/([ti])um$/i'             => '\1a',
80
+			'/(buffal|tomat)o$/i'      => '\1oes',
81
+			'/(bu)s$/i'                => '\1ses',
82
+			'/(alias|status)/i'        => '\1es',
83
+			'/(octop|vir)us$/i'        => '\1i',
84
+			'/(ax|test)is$/i'          => '\1es',
85
+			'/s$/i'                    => 's',
86
+			'/$/'                      => 's',
87
+		];
88
+
89
+		$uncountable = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'];
90
+
91
+		$irregular = [
92
+			'person' => 'people',
93
+			'man'    => 'men',
94
+			'child'  => 'children',
95
+			'sex'    => 'sexes',
96
+			'move'   => 'moves',
97
+		];
98
+
99
+		$lowercased_word = strtolower($word);
100
+
101
+		foreach ($uncountable as $_uncountable) {
102
+			// even though the word "price" ends in "rice", it can be pluralized,
103
+			// so check the previous character isn't a letter
104
+			if (
105
+				substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable
106
+				&& ! ctype_alpha($lowercased_word[ strlen($lowercased_word) - strlen($_uncountable) ])
107
+			) {
108
+				return $word;
109
+			}
110
+		}
111
+
112
+		foreach ($irregular as $_plural => $_singular) {
113
+			if (preg_match('/(' . $_plural . ')$/i', $word, $arr)) {
114
+				return preg_replace('/(' . $_plural . ')$/i', substr($arr[0], 0, 1) . substr($_singular, 1), $word);
115
+			}
116
+		}
117
+
118
+		foreach ($plural as $rule => $replacement) {
119
+			if (preg_match($rule, $word)) {
120
+				return preg_replace($rule, $replacement, $word);
121
+			}
122
+		}
123
+		return false;
124
+	}
125
+
126
+
127
+	/**
128
+	 * Singularizes English nouns.
129
+	 *
130
+	 * @access public
131
+	 * @static
132
+	 * @param string $word English noun to singularize
133
+	 * @return string Singular noun.
134
+	 */
135
+	public static function singularize($word)
136
+	{
137
+		$singular = [
138
+			'/(quiz)zes$/i'                                                    => '\1',
139
+			'/(matr)ices$/i'                                                   => '\1ix',
140
+			'/(vert|ind)ices$/i'                                               => '\1ex',
141
+			'/^(ox)en/i'                                                       => '\1',
142
+			'/(alias|status)es$/i'                                             => '\1',
143
+			'/([octop|vir])i$/i'                                               => '\1us',
144
+			'/(cris|ax|test)es$/i'                                             => '\1is',
145
+			'/(shoe)s$/i'                                                      => '\1',
146
+			'/(o)es$/i'                                                        => '\1',
147
+			'/(bus)es$/i'                                                      => '\1',
148
+			'/([m|l])ice$/i'                                                   => '\1ouse',
149
+			'/(x|ch|ss|sh)es$/i'                                               => '\1',
150
+			'/(m)ovies$/i'                                                     => '\1ovie',
151
+			'/(s)eries$/i'                                                     => '\1eries',
152
+			'/([^aeiouy]|qu)ies$/i'                                            => '\1y',
153
+			'/([lr])ves$/i'                                                    => '\1f',
154
+			'/(tive)s$/i'                                                      => '\1',
155
+			'/(hive)s$/i'                                                      => '\1',
156
+			'/([^f])ves$/i'                                                    => '\1fe',
157
+			'/(^analy)ses$/i'                                                  => '\1sis',
158
+			'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
159
+			'/([ti])a$/i'                                                      => '\1um',
160
+			'/(n)ews$/i'                                                       => '\1ews',
161
+			'/s$/i'                                                            => '',
162
+		];
163
+
164
+		$uncountable = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'];
165
+
166
+		$irregular = [
167
+			'person' => 'people',
168
+			'man'    => 'men',
169
+			'child'  => 'children',
170
+			'sex'    => 'sexes',
171
+			'move'   => 'moves',
172
+		];
173
+
174
+		$lowercased_word = strtolower($word);
175
+		foreach ($uncountable as $_uncountable) {
176
+			if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
177
+				return $word;
178
+			}
179
+		}
180
+
181
+		foreach ($irregular as $_plural => $_singular) {
182
+			if (preg_match('/(' . $_singular . ')$/i', $word, $arr)) {
183
+				return preg_replace('/(' . $_singular . ')$/i', substr($arr[0], 0, 1) . substr($_plural, 1), $word);
184
+			}
185
+		}
186
+
187
+		foreach ($singular as $rule => $replacement) {
188
+			if (preg_match($rule, $word)) {
189
+				return preg_replace($rule, $replacement, $word);
190
+			}
191
+		}
192
+
193
+		return $word;
194
+	}
195
+
196
+
197
+	/**
198
+	 * Converts an underscored or CamelCase word into a English
199
+	 * sentence.
200
+	 *
201
+	 * The titleize static function converts text like "WelcomePage",
202
+	 * "welcome_page" or  "welcome page" to this "Welcome
203
+	 * Page".
204
+	 * If second parameter is set to 'first' it will only
205
+	 * capitalize the first character of the title.
206
+	 *
207
+	 * @access public
208
+	 * @static
209
+	 * @param string $word      Word to format as tile
210
+	 * @param string $uppercase If set to 'first' it will only uppercase the
211
+	 *                          first character. Otherwise it will uppercase all
212
+	 *                          the words in the title.
213
+	 * @return string Text formatted as title
214
+	 */
215
+	public static function titleize($word, $uppercase = '')
216
+	{
217
+		$uppercase = $uppercase === 'first' ? 'ucfirst' : 'ucwords';
218
+		return $uppercase(EEH_Inflector::humanize(EEH_Inflector::underscore($word)));
219
+	}
220
+
221
+
222
+	/**
223
+	 * Returns given word as CamelCased
224
+	 *
225
+	 * Converts a word like "send_email" to "SendEmail". It
226
+	 * will remove non alphanumeric character from the word, so
227
+	 * "who's online" will be converted to "WhoSOnline"
228
+	 *
229
+	 * @access public
230
+	 * @static
231
+	 * @param string $word Word to convert to camel case
232
+	 * @return string UpperCamelCasedWord
233
+	 * @see    variablize
234
+	 */
235
+	public static function camelize($word)
236
+	{
237
+		return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
238
+	}
239
+
240
+
241
+	/**
242
+	 * Camelizes all but the first word. This is handy converting a method which followed EE4 legacy naming convention
243
+	 * with the new PSR-based naming conventions
244
+	 *
245
+	 * @param $word
246
+	 * @return string
247
+	 */
248
+	public static function camelize_all_but_first($word)
249
+	{
250
+		return lcfirst(EEH_Inflector::camelize($word));
251
+	}
252
+
253
+
254
+	/**
255
+	 * Converts a word "into_it_s_underscored_version"
256
+	 *
257
+	 * Convert any "CamelCased" or "ordinary Word" into an
258
+	 * "underscored_word".
259
+	 *
260
+	 * This can be really useful for creating friendly URLs.
261
+	 *
262
+	 * @access public
263
+	 * @static
264
+	 * @param string $word Word to underscore
265
+	 * @return string Underscored word
266
+	 */
267
+	public static function underscore($word)
268
+	{
269
+		return strtolower(
270
+			preg_replace(
271
+				'/[^A-Z^a-z^0-9]+/',
272
+				'_',
273
+				preg_replace('/([a-zd])([A-Z])/', '1_2', preg_replace('/([A-Z]+)([A-Z][a-z])/', '1_2', $word))
274
+			)
275
+		);
276
+	}
277
+
278
+
279
+	/**
280
+	 * Returns a human-readable string from $word
281
+	 *
282
+	 * Returns a human-readable string from $word, by replacing
283
+	 * underscores with a space, and by upper-casing the initial
284
+	 * character by default.
285
+	 *
286
+	 * If you need to uppercase all the words you just have to
287
+	 * pass 'all' as a second parameter.
288
+	 *
289
+	 * @access public
290
+	 * @static
291
+	 * @param string $word      String to "humanize"
292
+	 * @param string $uppercase If set to 'all' it will uppercase all the words
293
+	 *                          instead of just the first one.
294
+	 * @return string Human-readable word
295
+	 */
296
+	public static function humanize($word, $uppercase = '')
297
+	{
298
+		// make special exceptions for acronyms
299
+		$word      = str_replace('wp_', 'WP_', $word);
300
+		$uppercase = $uppercase === 'all' ? 'ucwords' : 'ucfirst';
301
+		return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
302
+	}
303
+
304
+
305
+	/**
306
+	 * Same as camelize but first char is underscored
307
+	 *
308
+	 * Converts a word like "send_email" to "sendEmail". It
309
+	 * will remove non alphanumeric character from the word, so
310
+	 * "who's online" will be converted to "whoSOnline"
311
+	 *
312
+	 * @access public
313
+	 * @static
314
+	 * @param string $word Word to lowerCamelCase
315
+	 * @return string Returns a lowerCamelCasedWord
316
+	 * @see    camelize
317
+	 */
318
+	public static function variablize($word)
319
+	{
320
+		$word = EEH_Inflector::camelize($word);
321
+		return strtolower($word[0]) . substr($word, 1);
322
+	}
323
+
324
+
325
+	/**
326
+	 * Converts a class name to its table name according to rails
327
+	 * naming conventions.
328
+	 *
329
+	 * Converts "Person" to "people"
330
+	 *
331
+	 * @access public
332
+	 * @static
333
+	 * @param string $class_name Class name for getting related table_name.
334
+	 * @return string plural_table_name
335
+	 * @see    classify
336
+	 */
337
+	public static function tableize($class_name)
338
+	{
339
+		return EEH_Inflector::pluralize(EEH_Inflector::underscore($class_name));
340
+	}
341
+
342
+
343
+	/**
344
+	 * Converts a table name to its class name according to rails
345
+	 * naming conventions.
346
+	 *
347
+	 * Converts "people" to "Person"
348
+	 *
349
+	 * @access public
350
+	 * @static
351
+	 * @param string $table_name Table name for getting related ClassName.
352
+	 * @return string SingularClassName
353
+	 * @see    tableize
354
+	 */
355
+	public static function classify($table_name)
356
+	{
357
+		return EEH_Inflector::camelize(EEH_Inflector::singularize($table_name));
358
+	}
359
+
360
+
361
+	/**
362
+	 * Converts number to its ordinal English form.
363
+	 *
364
+	 * This method converts 13 to 13th, 2 to 2nd ...
365
+	 *
366
+	 * @access public
367
+	 * @static
368
+	 * @param integer $number Number to get its ordinal value
369
+	 * @return string Ordinal representation of given string.
370
+	 */
371
+	public static function ordinalize($number)
372
+	{
373
+		if (in_array(($number % 100), range(11, 13))) {
374
+			return $number . 'th';
375
+		} else {
376
+			switch (($number % 10)) {
377
+				case 1:
378
+					return $number . 'st';
379
+					break;
380
+				case 2:
381
+					return $number . 'nd';
382
+					break;
383
+				case 3:
384
+					return $number . 'rd';
385
+				default:
386
+					return $number . 'th';
387
+					break;
388
+			}
389
+		}
390
+	}
391
+
392
+
393
+	/**
394
+	 * @param $string
395
+	 * @return string
396
+	 */
397
+	public static function add_indefinite_article($string)
398
+	{
399
+		if (strtolower($string) === 'null') {
400
+			return $string;
401
+		}
402
+		return (stripos('aeiou', $string[0]) !== false ? 'an ' : 'a ') . $string;
403
+	}
404 404
 }
Please login to merge, or discard this patch.
core/helpers/EEH_File.helper.php 2 patches
Indentation   +741 added lines, -741 removed lines patch added patch discarded remove patch
@@ -24,745 +24,745 @@
 block discarded – undo
24 24
  */
25 25
 class EEH_File extends EEH_Base implements EEHI_File
26 26
 {
27
-    /**
28
-     * @var string $_credentials_form
29
-     */
30
-    private static $_credentials_form;
31
-
32
-    protected static $_wp_filesystem_direct;
33
-
34
-
35
-    /**
36
-     * @param string|null $filepath the filepath we want to work in. If its in the
37
-     *                              wp uploads directory, we'll want to just use the filesystem directly.
38
-     *                              If not provided, we have to assume its not in the uploads directory
39
-     * @return WP_Filesystem_Base
40
-     * @throws EE_Error if filesystem credentials are required
41
-     */
42
-    private static function _get_wp_filesystem($filepath = null)
43
-    {
44
-        if (
45
-            apply_filters(
46
-                'FHEE__EEH_File___get_wp_filesystem__allow_using_filesystem_direct',
47
-                $filepath && EEH_File::is_in_uploads_folder($filepath),
48
-                $filepath
49
-            )
50
-        ) {
51
-            if (! EEH_File::$_wp_filesystem_direct instanceof WP_Filesystem_Direct) {
52
-                require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
53
-                $method                    = 'direct';
54
-                $wp_filesystem_direct_file =
55
-                    apply_filters(
56
-                        'filesystem_method_file',
57
-                        ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php',
58
-                        $method
59
-                    );
60
-                // check constants defined, just like in wp-admin/includes/file.php's WP_Filesystem()
61
-                if (! defined('FS_CHMOD_DIR')) {
62
-                    define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0777 | 0755));
63
-                }
64
-                if (! defined('FS_CHMOD_FILE')) {
65
-                    define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0777 | 0644));
66
-                }
67
-                require_once($wp_filesystem_direct_file);
68
-                EEH_File::$_wp_filesystem_direct = new WP_Filesystem_Direct([]);
69
-            }
70
-            return EEH_File::$_wp_filesystem_direct;
71
-        }
72
-        global $wp_filesystem;
73
-        // no filesystem setup ???
74
-        if (! $wp_filesystem instanceof WP_Filesystem_Base) {
75
-            // if some eager beaver's just trying to get in there too early...
76
-            // let them do it, because we are one of those eager beavers! :P
77
-            /**
78
-             * more explanations are probably merited. http://codex.wordpress.org/Filesystem_API#Initializing_WP_Filesystem_Base
79
-             * says WP_Filesystem should be used after 'wp_loaded', but currently EE's activation process
80
-             * is setup to mostly happen on 'init', and refactoring to have it happen on
81
-             * 'wp_loaded' is too much work on a BETA milestone.
82
-             * So this fix is expected to work if the WP files are owned by the server user,
83
-             * but probably not if the user needs to enter their FTP credentials to modify files
84
-             * and there may be troubles if the WP files are owned by a different user
85
-             * than the server user. But both of these issues should exist in 4.4 and earlier too
86
-             */
87
-            if (false && ! did_action('wp_loaded')) {
88
-                $msg = esc_html__(
89
-                    'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
90
-                    'event_espresso'
91
-                );
92
-                if (WP_DEBUG) {
93
-                    $msg .= '<br />';
94
-                    $msg .= esc_html__(
95
-                        'The WP Filesystem can not be accessed until after the "wp_loaded" hook has run, so it\'s best not to attempt access until the "admin_init" hookpoint.',
96
-                        'event_espresso'
97
-                    );
98
-                }
99
-                throw new EE_Error($msg);
100
-            } else {
101
-                // should be loaded if we are past the wp_loaded hook...
102
-                if (! function_exists('WP_Filesystem')) {
103
-                    require_once(ABSPATH . 'wp-admin/includes/file.php');
104
-                    require_once(ABSPATH . 'wp-admin/includes/template.php');
105
-                }
106
-                // turn on output buffering so that we can capture the credentials form
107
-                ob_start();
108
-                $credentials = request_filesystem_credentials('');
109
-                // store credentials form for the time being
110
-                EEH_File::$_credentials_form = ob_get_clean();
111
-                // basically check for direct or previously configured access
112
-                if (! WP_Filesystem($credentials)) {
113
-                    // if credentials do NOT exist
114
-                    if ($credentials === false) {
115
-                        add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
116
-                        throw new EE_Error(
117
-                            esc_html__(
118
-                                'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
119
-                                'event_espresso'
120
-                            )
121
-                        );
122
-                    } elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
123
-                        add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
124
-                        throw new EE_Error(
125
-                            sprintf(
126
-                                esc_html__('WP Filesystem Error: $1%s', 'event_espresso'),
127
-                                $wp_filesystem->errors->get_error_message()
128
-                            )
129
-                        );
130
-                    }
131
-                }
132
-            }
133
-        }
134
-        return $wp_filesystem;
135
-    }
136
-
137
-
138
-    /**
139
-     * display_request_filesystem_credentials_form
140
-     */
141
-    public static function display_request_filesystem_credentials_form()
142
-    {
143
-        if (! empty(EEH_File::$_credentials_form)) {
144
-            echo '<div class="updated espresso-notices-attention"><p>' . EEH_File::$_credentials_form . '</p></div>';
145
-        }
146
-    }
147
-
148
-
149
-    /**
150
-     *    verify_filepath_and_permissions
151
-     *    checks that a file is readable and has sufficient file permissions set to access
152
-     *
153
-     * @access public
154
-     * @param string $full_file_path - full server path to the folder or file
155
-     * @param string $file_name      - name of file if checking a file
156
-     * @param string $file_ext       - file extension (ie: "php") if checking a file
157
-     * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
158
-     * @return bool
159
-     * @throws EE_Error if filesystem credentials are required
160
-     */
161
-    public static function verify_filepath_and_permissions(
162
-        $full_file_path = '',
163
-        $file_name = '',
164
-        $file_ext = '',
165
-        $type_of_file = ''
166
-    ) {
167
-        // load WP_Filesystem and set file permissions
168
-        $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
169
-        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
170
-        if (! $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
171
-            $file_name = ! empty($type_of_file) ? $file_name . ' ' . $type_of_file : $file_name;
172
-            $file_name .= ! empty($file_ext) ? ' file' : ' folder';
173
-            $msg       = sprintf(
174
-                esc_html__(
175
-                    'The requested %1$s could not be found or is not readable, possibly due to an incorrect filepath, or incorrect file permissions.%2$s',
176
-                    'event_espresso'
177
-                ),
178
-                $file_name,
179
-                '<br />'
180
-            );
181
-            if (EEH_File::exists($full_file_path)) {
182
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, $type_of_file);
183
-            } else {
184
-                // no file permissions means the file was not found
185
-                $msg .= sprintf(
186
-                    esc_html__('Please ensure the following path is correct: "%s".', 'event_espresso'),
187
-                    $full_file_path
188
-                );
189
-            }
190
-            if (defined('WP_DEBUG') && WP_DEBUG) {
191
-                throw new EE_Error($msg . '||' . $msg);
192
-            }
193
-            return false;
194
-        }
195
-        return true;
196
-    }
197
-
198
-
199
-    /**
200
-     * _permissions_error_for_unreadable_filepath - attempts to determine why permissions are set incorrectly for a
201
-     * file or folder
202
-     *
203
-     * @access private
204
-     * @param string $full_file_path - full server path to the folder or file
205
-     * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
206
-     * @return string
207
-     * @throws EE_Error if filesystem credentials are required
208
-     */
209
-    private static function _permissions_error_for_unreadable_filepath($full_file_path = '', $type_of_file = '')
210
-    {
211
-        // load WP_Filesystem and set file permissions
212
-        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
213
-        // check file permissions
214
-        $perms = $wp_filesystem->getchmod(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
215
-        if ($perms) {
216
-            // file permissions exist, but way be set incorrectly
217
-            $type_of_file = ! empty($type_of_file) ? $type_of_file . ' ' : '';
218
-            $type_of_file .= ! empty($type_of_file) ? 'file' : 'folder';
219
-            return sprintf(
220
-                esc_html__(
221
-                    'File permissions for the requested %1$s are currently set at "%2$s". The recommended permissions are 644 for files and 755 for folders.',
222
-                    'event_espresso'
223
-                ),
224
-                $type_of_file,
225
-                $perms
226
-            );
227
-        }
228
-        // file exists but file permissions could not be read ?!?!
229
-        return sprintf(
230
-            esc_html__(
231
-                'Please ensure that the server and/or PHP configuration allows the current process to access the following file: "%s".',
232
-                'event_espresso'
233
-            ),
234
-            $full_file_path
235
-        );
236
-    }
237
-
238
-
239
-    /**
240
-     * ensure_folder_exists_and_is_writable
241
-     * ensures that a folder exists and is writable, will attempt to create folder if it does not exist
242
-     * Also ensures all the parent folders exist, and if not tries to create them.
243
-     * Also, if this function creates the folder, adds a .htaccess file and index.html file
244
-     *
245
-     * @param string $folder
246
-     * @return bool false if folder isn't writable; true if it exists and is writeable,
247
-     * @throws EE_Error if the folder exists and is writeable, but for some reason we
248
-     * can't write to it
249
-     */
250
-    public static function ensure_folder_exists_and_is_writable($folder = '')
251
-    {
252
-        if (empty($folder)) {
253
-            return false;
254
-        }
255
-        // remove ending /
256
-        $folder        = EEH_File::standardise_directory_separators(rtrim($folder, '/\\'));
257
-        $parent_folder = EEH_File::get_parent_folder($folder);
258
-        // add / to folder
259
-        $folder        = EEH_File::end_with_directory_separator($folder);
260
-        $wp_filesystem = EEH_File::_get_wp_filesystem($folder);
261
-        if (! $wp_filesystem->is_dir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
262
-            // ok so it doesn't exist. Does its parent? Can we write to it?
263
-            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
264
-                return false;
265
-            }
266
-            if (! EEH_File::verify_is_writable($parent_folder)) {
267
-                return false;
268
-            } else {
269
-                if (! $wp_filesystem->mkdir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
270
-                    if (defined('WP_DEBUG') && WP_DEBUG) {
271
-                        $msg = sprintf(esc_html__('"%s" could not be created.', 'event_espresso'), $folder);
272
-                        $msg .= EEH_File::_permissions_error_for_unreadable_filepath($folder);
273
-                        throw new EE_Error($msg);
274
-                    }
275
-                    return false;
276
-                }
277
-                EEH_File::add_index_file($folder);
278
-            }
279
-        } elseif (! EEH_File::verify_is_writable($folder)) {
280
-            return false;
281
-        }
282
-        return true;
283
-    }
284
-
285
-
286
-    /**
287
-     * verify_is_writable - checks if a file or folder is writable
288
-     *
289
-     * @param string $full_path      - full server path to file or folder
290
-     * @param string $file_or_folder - whether checking a file or folder
291
-     * @return bool
292
-     * @throws EE_Error if filesystem credentials are required
293
-     */
294
-    public static function verify_is_writable($full_path = '', $file_or_folder = 'folder')
295
-    {
296
-        // load WP_Filesystem and set file permissions
297
-        $wp_filesystem = EEH_File::_get_wp_filesystem($full_path);
298
-        $full_path     = EEH_File::standardise_directory_separators($full_path);
299
-        if (! $wp_filesystem->is_writable(EEH_File::convert_local_filepath_to_remote_filepath($full_path))) {
300
-            if (defined('WP_DEBUG') && WP_DEBUG) {
301
-                $msg = sprintf(
302
-                    esc_html__('The "%1$s" %2$s is not writable.', 'event_espresso'),
303
-                    $full_path,
304
-                    $file_or_folder
305
-                );
306
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_path);
307
-                throw new EE_Error($msg);
308
-            }
309
-            return false;
310
-        }
311
-        return true;
312
-    }
313
-
314
-
315
-    /**
316
-     * ensure_file_exists_and_is_writable
317
-     * ensures that a file exists and is writable, will attempt to create file if it does not exist.
318
-     * Also ensures all the parent folders exist, and if not tries to create them.
319
-     *
320
-     * @param string $full_file_path
321
-     * @return bool
322
-     * @throws EE_Error if filesystem credentials are required
323
-     */
324
-    public static function ensure_file_exists_and_is_writable($full_file_path = '')
325
-    {
326
-        // load WP_Filesystem and set file permissions
327
-        $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
328
-        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
329
-        $parent_folder  = EEH_File::get_parent_folder($full_file_path);
330
-        if (! EEH_File::exists($full_file_path)) {
331
-            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
332
-                return false;
333
-            }
334
-            if (! $wp_filesystem->touch(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
335
-                if (defined('WP_DEBUG') && WP_DEBUG) {
336
-                    $msg =
337
-                        sprintf(esc_html__('The "%s" file could not be created.', 'event_espresso'), $full_file_path);
338
-                    $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
339
-                    throw new EE_Error($msg);
340
-                }
341
-                return false;
342
-            }
343
-        }
344
-        if (! EEH_File::verify_is_writable($full_file_path, 'file')) {
345
-            return false;
346
-        }
347
-        return true;
348
-    }
349
-
350
-
351
-    /**
352
-     * Gets the parent folder. If provided with file, gets the folder that contains it.
353
-     * If provided a folder, gets its parent folder.
354
-     *
355
-     * @param string $file_or_folder_path
356
-     * @return string parent folder, ENDING with a directory separator
357
-     */
358
-    public static function get_parent_folder($file_or_folder_path)
359
-    {
360
-        // find the last /, ignoring a / on the very end
361
-        // eg if given "/var/something/somewhere/", we want to get "somewhere"'s
362
-        // parent folder, "/var/something/"
363
-        $ds = strlen($file_or_folder_path) > 1
364
-            ? strrpos($file_or_folder_path, '/', -2)
365
-            : strlen($file_or_folder_path);
366
-        return substr($file_or_folder_path, 0, $ds + 1);
367
-    }
368
-
369
-    // public static function ensure_folder_exists_recursively( $folder ) {
370
-    //
371
-    // }
372
-
373
-
374
-    /**
375
-     * get_file_contents
376
-     *
377
-     * @param string $full_file_path
378
-     * @return string
379
-     * @throws EE_Error if filesystem credentials are required
380
-     */
381
-    public static function get_file_contents($full_file_path = '')
382
-    {
383
-        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
384
-        if (
385
-            EEH_File::verify_filepath_and_permissions(
386
-                $full_file_path,
387
-                EEH_File::get_filename_from_filepath($full_file_path),
388
-                EEH_File::get_file_extension($full_file_path)
389
-            )
390
-        ) {
391
-            // load WP_Filesystem and set file permissions
392
-            $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
393
-            return $wp_filesystem->get_contents(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
394
-        }
395
-        return '';
396
-    }
397
-
398
-
399
-    /**
400
-     * write_file
401
-     *
402
-     * @param string $full_file_path
403
-     * @param string $file_contents - the content to be written to the file
404
-     * @param string $file_type
405
-     * @return bool
406
-     * @throws EE_Error if filesystem credentials are required
407
-     */
408
-    public static function write_to_file($full_file_path = '', $file_contents = '', $file_type = '')
409
-    {
410
-        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
411
-        $file_type      = ! empty($file_type) ? rtrim($file_type, ' ') . ' ' : '';
412
-        $folder         = EEH_File::remove_filename_from_filepath($full_file_path);
413
-        if (! EEH_File::verify_is_writable($folder)) {
414
-            if (defined('WP_DEBUG') && WP_DEBUG) {
415
-                $msg = sprintf(
416
-                    esc_html__('The %1$sfile located at "%2$s" is not writable.', 'event_espresso'),
417
-                    $file_type,
418
-                    $full_file_path
419
-                );
420
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
421
-                throw new EE_Error($msg);
422
-            }
423
-            return false;
424
-        }
425
-        // load WP_Filesystem and set file permissions
426
-        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
427
-        // write the file
428
-        if (
429
-            ! $wp_filesystem->put_contents(
430
-                EEH_File::convert_local_filepath_to_remote_filepath($full_file_path),
431
-                $file_contents
432
-            )
433
-        ) {
434
-            if (defined('WP_DEBUG') && WP_DEBUG) {
435
-                $msg = sprintf(
436
-                    esc_html__('The %1$sfile located at "%2$s" could not be written to.', 'event_espresso'),
437
-                    $file_type,
438
-                    $full_file_path
439
-                );
440
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, 'f');
441
-                throw new EE_Error($msg);
442
-            }
443
-            return false;
444
-        }
445
-        return true;
446
-    }
447
-
448
-
449
-    /**
450
-     * Wrapper for WP_Filesystem_Base::delete
451
-     *
452
-     * @param string         $filepath
453
-     * @param boolean        $recursive
454
-     * @param boolean|string $type 'd' for directory, 'f' for file
455
-     * @return boolean
456
-     * @throws EE_Error if filesystem credentials are required
457
-     */
458
-    public static function delete($filepath, $recursive = false, $type = false)
459
-    {
460
-        $wp_filesystem = EEH_File::_get_wp_filesystem();
461
-        return $wp_filesystem->delete($filepath, $recursive, $type);
462
-    }
463
-
464
-
465
-    /**
466
-     * exists
467
-     * checks if a file exists using the WP filesystem
468
-     *
469
-     * @param string $full_file_path
470
-     * @return bool
471
-     * @throws EE_Error if filesystem credentials are required
472
-     */
473
-    public static function exists($full_file_path = '')
474
-    {
475
-        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
476
-        return $wp_filesystem->exists(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
477
-    }
478
-
479
-
480
-    /**
481
-     * is_readable
482
-     * checks if a file is_readable using the WP filesystem
483
-     *
484
-     * @param string $full_file_path
485
-     * @return bool
486
-     * @throws EE_Error if filesystem credentials are required
487
-     */
488
-    public static function is_readable($full_file_path = '')
489
-    {
490
-        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
491
-        return $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
492
-    }
493
-
494
-
495
-    /**
496
-     * remove_filename_from_filepath
497
-     * given a full path to a file including the filename itself, this removes  the filename and returns the path, up
498
-     * to, but NOT including the filename OR slash
499
-     *
500
-     * @param string $full_file_path
501
-     * @return string
502
-     */
503
-    public static function remove_filename_from_filepath($full_file_path = '')
504
-    {
505
-        return pathinfo($full_file_path, PATHINFO_DIRNAME);
506
-    }
507
-
508
-
509
-    /**
510
-     * get_filename_from_filepath. Arguably the same as basename()
511
-     *
512
-     * @param string $full_file_path
513
-     * @return string
514
-     */
515
-    public static function get_filename_from_filepath($full_file_path = '')
516
-    {
517
-        return pathinfo($full_file_path, PATHINFO_BASENAME);
518
-    }
519
-
520
-
521
-    /**
522
-     * get_file_extension
523
-     *
524
-     * @param string $full_file_path
525
-     * @return string
526
-     */
527
-    public static function get_file_extension($full_file_path = '')
528
-    {
529
-        return pathinfo($full_file_path, PATHINFO_EXTENSION);
530
-    }
531
-
532
-
533
-    /**
534
-     * add_htaccess_deny_from_all so the webserver cannot access this folder
535
-     *
536
-     * @param string $folder
537
-     * @return bool
538
-     * @throws EE_Error if filesystem credentials are required
539
-     */
540
-    public static function add_htaccess_deny_from_all($folder = '')
541
-    {
542
-        $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
543
-        if (! EEH_File::exists($folder . '.htaccess')) {
544
-            if (! EEH_File::write_to_file($folder . '.htaccess', 'deny from all', '.htaccess')) {
545
-                return false;
546
-            }
547
-        }
548
-        return true;
549
-    }
550
-
551
-
552
-    /**
553
-     * Adds an index file to this folder, so folks can't list all the file's contents
554
-     *
555
-     * @param string $folder
556
-     * @return boolean
557
-     * @throws EE_Error if filesystem credentials are required
558
-     */
559
-    public static function add_index_file($folder)
560
-    {
561
-        $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
562
-        if (! EEH_File::exists($folder . 'index.php')) {
563
-            if (
564
-                ! EEH_File::write_to_file(
565
-                    $folder . 'index.php',
566
-                    'You are not permitted to read from this folder',
567
-                    '.php'
568
-                )
569
-            ) {
570
-                return false;
571
-            }
572
-        }
573
-        return true;
574
-    }
575
-
576
-
577
-    /**
578
-     * Given that the file in $file_path has the normal name, (ie, CLASSNAME.whatever.php),
579
-     * extract that classname.
580
-     *
581
-     * @param string $file_path
582
-     * @return string
583
-     */
584
-    public static function get_classname_from_filepath_with_standard_filename($file_path)
585
-    {
586
-        // extract file from path
587
-        $filename = basename($file_path);
588
-        // now remove the first period and everything after
589
-        $pos_of_first_period = strpos($filename, '.');
590
-        return substr($filename, 0, $pos_of_first_period);
591
-    }
592
-
593
-
594
-    /**
595
-     * standardise_directory_separators
596
-     *  convert all directory separators in a file path.
597
-     *
598
-     * @param string $file_path
599
-     * @return string
600
-     */
601
-    public static function standardise_directory_separators($file_path)
602
-    {
603
-        return str_replace(['\\', '/'], '/', $file_path);
604
-    }
605
-
606
-
607
-    /**
608
-     * end_with_directory_separator
609
-     *  ensures that file path ends with '/'
610
-     *
611
-     * @param string $file_path
612
-     * @return string
613
-     */
614
-    public static function end_with_directory_separator($file_path)
615
-    {
616
-        return rtrim($file_path, '/\\') . '/';
617
-    }
618
-
619
-
620
-    /**
621
-     * shorthand for both EEH_FIle::end_with_directory_separator AND EEH_File::standardise_directory_separators
622
-     *
623
-     * @param $file_path
624
-     * @return string
625
-     */
626
-    public static function standardise_and_end_with_directory_separator($file_path)
627
-    {
628
-        return self::end_with_directory_separator(self::standardise_directory_separators($file_path));
629
-    }
630
-
631
-
632
-    /**
633
-     * takes the folder name (with or without trailing slash) and finds the files it in,
634
-     * and what the class's name inside of each should be.
635
-     *
636
-     * @param array   $folder_paths
637
-     * @param boolean $index_numerically if TRUE, the returned array will be indexed numerically;
638
-     *                                   if FALSE (Default), returned array will be indexed by the filenames minus
639
-     *                                   extensions. Set it TRUE if you know there are files in the directory with the
640
-     *                                   same name but different extensions
641
-     * @return array if $index_numerically == TRUE keys are numeric ,
642
-     *                                   if $index_numerically == FALSE (Default) keys are what the class names SHOULD
643
-     *                                   be; and values are their filepaths
644
-     */
645
-    public static function get_contents_of_folders($folder_paths = [], $index_numerically = false)
646
-    {
647
-        $class_to_folder_path = [];
648
-        foreach ($folder_paths as $folder_path) {
649
-            $folder_path = self::standardise_and_end_with_directory_separator($folder_path);
650
-            // load WP_Filesystem and set file permissions
651
-            $files_in_folder      = glob($folder_path . '*.php');
652
-            $class_to_folder_path = [];
653
-            if ($files_in_folder) {
654
-                foreach ($files_in_folder as $file_path) {
655
-                    // only add files, not folders
656
-                    if (! is_dir($file_path)) {
657
-                        if ($index_numerically) {
658
-                            $class_to_folder_path[] = $file_path;
659
-                        } else {
660
-                            $classname                          =
661
-                                self::get_classname_from_filepath_with_standard_filename($file_path);
662
-                            $class_to_folder_path[ $classname ] = $file_path;
663
-                        }
664
-                    }
665
-                }
666
-            }
667
-        }
668
-        return $class_to_folder_path;
669
-    }
670
-
671
-
672
-    /**
673
-     * Copies a file. Mostly a wrapper of WP_Filesystem::copy
674
-     *
675
-     * @param string  $source_file
676
-     * @param string  $destination_file
677
-     * @param boolean $overwrite
678
-     * @return boolean success
679
-     * @throws EE_Error if filesystem credentials are required
680
-     */
681
-    public static function copy($source_file, $destination_file, $overwrite = false)
682
-    {
683
-        $full_source_path = EEH_File::standardise_directory_separators($source_file);
684
-        if (! EEH_File::exists($full_source_path)) {
685
-            if (defined('WP_DEBUG') && WP_DEBUG) {
686
-                $msg = sprintf(
687
-                    esc_html__('The file located at "%2$s" is not readable or doesn\'t exist.', 'event_espresso'),
688
-                    $full_source_path
689
-                );
690
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_source_path);
691
-                throw new EE_Error($msg);
692
-            }
693
-            return false;
694
-        }
695
-
696
-        $full_dest_path = EEH_File::standardise_directory_separators($destination_file);
697
-        $folder         = EEH_File::remove_filename_from_filepath($full_dest_path);
698
-        EEH_File::ensure_folder_exists_and_is_writable($folder);
699
-        if (! EEH_File::verify_is_writable($folder)) {
700
-            if (defined('WP_DEBUG') && WP_DEBUG) {
701
-                $msg = sprintf(
702
-                    esc_html__('The file located at "%2$s" is not writable.', 'event_espresso'),
703
-                    $full_dest_path
704
-                );
705
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_dest_path);
706
-                throw new EE_Error($msg);
707
-            }
708
-            return false;
709
-        }
710
-
711
-        // load WP_Filesystem and set file permissions
712
-        $wp_filesystem = EEH_File::_get_wp_filesystem($destination_file);
713
-        // write the file
714
-        if (
715
-            ! $wp_filesystem->copy(
716
-                EEH_File::convert_local_filepath_to_remote_filepath($full_source_path),
717
-                EEH_File::convert_local_filepath_to_remote_filepath($full_dest_path),
718
-                $overwrite
719
-            )
720
-        ) {
721
-            if (defined('WP_DEBUG') && WP_DEBUG) {
722
-                $msg =
723
-                    sprintf(
724
-                        esc_html__(
725
-                            'Attempted writing to file %1$s, but could not, probably because of permissions issues',
726
-                            'event_espresso'
727
-                        ),
728
-                        $full_source_path
729
-                    );
730
-                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_source_path, 'f');
731
-                throw new EE_Error($msg);
732
-            }
733
-            return false;
734
-        }
735
-        return true;
736
-    }
737
-
738
-
739
-    /**
740
-     * Reports whether or not the filepath is in the EE uploads folder or not
741
-     *
742
-     * @param string $filepath
743
-     * @return boolean
744
-     */
745
-    public static function is_in_uploads_folder($filepath)
746
-    {
747
-        $uploads = wp_upload_dir();
748
-        return strpos($filepath, $uploads['basedir']) === 0;
749
-    }
750
-
751
-
752
-    /**
753
-     * Given a "local" filepath (what you probably thought was the only filepath),
754
-     * converts it into a "remote" filepath (the filepath the currently-in-use
755
-     * $wp_filesystem needs to use access the folder or file).
756
-     * See http://wordpress.stackexchange.com/questions/124900/using-wp-filesystem-in-plugins
757
-     *
758
-     * @param string $local_filepath             the filepath to the folder/file locally
759
-     * @return string the remote filepath (eg the filepath the filesystem method, eg
760
-     *                                           ftp or ssh, will use to access the folder
761
-     * @throws EE_Error if filesystem credentials are required
762
-     */
763
-    public static function convert_local_filepath_to_remote_filepath($local_filepath)
764
-    {
765
-        $wp_filesystem = EEH_File::_get_wp_filesystem($local_filepath);
766
-        return str_replace(WP_CONTENT_DIR . '/', $wp_filesystem->wp_content_dir(), $local_filepath);
767
-    }
27
+	/**
28
+	 * @var string $_credentials_form
29
+	 */
30
+	private static $_credentials_form;
31
+
32
+	protected static $_wp_filesystem_direct;
33
+
34
+
35
+	/**
36
+	 * @param string|null $filepath the filepath we want to work in. If its in the
37
+	 *                              wp uploads directory, we'll want to just use the filesystem directly.
38
+	 *                              If not provided, we have to assume its not in the uploads directory
39
+	 * @return WP_Filesystem_Base
40
+	 * @throws EE_Error if filesystem credentials are required
41
+	 */
42
+	private static function _get_wp_filesystem($filepath = null)
43
+	{
44
+		if (
45
+			apply_filters(
46
+				'FHEE__EEH_File___get_wp_filesystem__allow_using_filesystem_direct',
47
+				$filepath && EEH_File::is_in_uploads_folder($filepath),
48
+				$filepath
49
+			)
50
+		) {
51
+			if (! EEH_File::$_wp_filesystem_direct instanceof WP_Filesystem_Direct) {
52
+				require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
53
+				$method                    = 'direct';
54
+				$wp_filesystem_direct_file =
55
+					apply_filters(
56
+						'filesystem_method_file',
57
+						ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php',
58
+						$method
59
+					);
60
+				// check constants defined, just like in wp-admin/includes/file.php's WP_Filesystem()
61
+				if (! defined('FS_CHMOD_DIR')) {
62
+					define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0777 | 0755));
63
+				}
64
+				if (! defined('FS_CHMOD_FILE')) {
65
+					define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0777 | 0644));
66
+				}
67
+				require_once($wp_filesystem_direct_file);
68
+				EEH_File::$_wp_filesystem_direct = new WP_Filesystem_Direct([]);
69
+			}
70
+			return EEH_File::$_wp_filesystem_direct;
71
+		}
72
+		global $wp_filesystem;
73
+		// no filesystem setup ???
74
+		if (! $wp_filesystem instanceof WP_Filesystem_Base) {
75
+			// if some eager beaver's just trying to get in there too early...
76
+			// let them do it, because we are one of those eager beavers! :P
77
+			/**
78
+			 * more explanations are probably merited. http://codex.wordpress.org/Filesystem_API#Initializing_WP_Filesystem_Base
79
+			 * says WP_Filesystem should be used after 'wp_loaded', but currently EE's activation process
80
+			 * is setup to mostly happen on 'init', and refactoring to have it happen on
81
+			 * 'wp_loaded' is too much work on a BETA milestone.
82
+			 * So this fix is expected to work if the WP files are owned by the server user,
83
+			 * but probably not if the user needs to enter their FTP credentials to modify files
84
+			 * and there may be troubles if the WP files are owned by a different user
85
+			 * than the server user. But both of these issues should exist in 4.4 and earlier too
86
+			 */
87
+			if (false && ! did_action('wp_loaded')) {
88
+				$msg = esc_html__(
89
+					'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
90
+					'event_espresso'
91
+				);
92
+				if (WP_DEBUG) {
93
+					$msg .= '<br />';
94
+					$msg .= esc_html__(
95
+						'The WP Filesystem can not be accessed until after the "wp_loaded" hook has run, so it\'s best not to attempt access until the "admin_init" hookpoint.',
96
+						'event_espresso'
97
+					);
98
+				}
99
+				throw new EE_Error($msg);
100
+			} else {
101
+				// should be loaded if we are past the wp_loaded hook...
102
+				if (! function_exists('WP_Filesystem')) {
103
+					require_once(ABSPATH . 'wp-admin/includes/file.php');
104
+					require_once(ABSPATH . 'wp-admin/includes/template.php');
105
+				}
106
+				// turn on output buffering so that we can capture the credentials form
107
+				ob_start();
108
+				$credentials = request_filesystem_credentials('');
109
+				// store credentials form for the time being
110
+				EEH_File::$_credentials_form = ob_get_clean();
111
+				// basically check for direct or previously configured access
112
+				if (! WP_Filesystem($credentials)) {
113
+					// if credentials do NOT exist
114
+					if ($credentials === false) {
115
+						add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
116
+						throw new EE_Error(
117
+							esc_html__(
118
+								'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
119
+								'event_espresso'
120
+							)
121
+						);
122
+					} elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
123
+						add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
124
+						throw new EE_Error(
125
+							sprintf(
126
+								esc_html__('WP Filesystem Error: $1%s', 'event_espresso'),
127
+								$wp_filesystem->errors->get_error_message()
128
+							)
129
+						);
130
+					}
131
+				}
132
+			}
133
+		}
134
+		return $wp_filesystem;
135
+	}
136
+
137
+
138
+	/**
139
+	 * display_request_filesystem_credentials_form
140
+	 */
141
+	public static function display_request_filesystem_credentials_form()
142
+	{
143
+		if (! empty(EEH_File::$_credentials_form)) {
144
+			echo '<div class="updated espresso-notices-attention"><p>' . EEH_File::$_credentials_form . '</p></div>';
145
+		}
146
+	}
147
+
148
+
149
+	/**
150
+	 *    verify_filepath_and_permissions
151
+	 *    checks that a file is readable and has sufficient file permissions set to access
152
+	 *
153
+	 * @access public
154
+	 * @param string $full_file_path - full server path to the folder or file
155
+	 * @param string $file_name      - name of file if checking a file
156
+	 * @param string $file_ext       - file extension (ie: "php") if checking a file
157
+	 * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
158
+	 * @return bool
159
+	 * @throws EE_Error if filesystem credentials are required
160
+	 */
161
+	public static function verify_filepath_and_permissions(
162
+		$full_file_path = '',
163
+		$file_name = '',
164
+		$file_ext = '',
165
+		$type_of_file = ''
166
+	) {
167
+		// load WP_Filesystem and set file permissions
168
+		$wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
169
+		$full_file_path = EEH_File::standardise_directory_separators($full_file_path);
170
+		if (! $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
171
+			$file_name = ! empty($type_of_file) ? $file_name . ' ' . $type_of_file : $file_name;
172
+			$file_name .= ! empty($file_ext) ? ' file' : ' folder';
173
+			$msg       = sprintf(
174
+				esc_html__(
175
+					'The requested %1$s could not be found or is not readable, possibly due to an incorrect filepath, or incorrect file permissions.%2$s',
176
+					'event_espresso'
177
+				),
178
+				$file_name,
179
+				'<br />'
180
+			);
181
+			if (EEH_File::exists($full_file_path)) {
182
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, $type_of_file);
183
+			} else {
184
+				// no file permissions means the file was not found
185
+				$msg .= sprintf(
186
+					esc_html__('Please ensure the following path is correct: "%s".', 'event_espresso'),
187
+					$full_file_path
188
+				);
189
+			}
190
+			if (defined('WP_DEBUG') && WP_DEBUG) {
191
+				throw new EE_Error($msg . '||' . $msg);
192
+			}
193
+			return false;
194
+		}
195
+		return true;
196
+	}
197
+
198
+
199
+	/**
200
+	 * _permissions_error_for_unreadable_filepath - attempts to determine why permissions are set incorrectly for a
201
+	 * file or folder
202
+	 *
203
+	 * @access private
204
+	 * @param string $full_file_path - full server path to the folder or file
205
+	 * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
206
+	 * @return string
207
+	 * @throws EE_Error if filesystem credentials are required
208
+	 */
209
+	private static function _permissions_error_for_unreadable_filepath($full_file_path = '', $type_of_file = '')
210
+	{
211
+		// load WP_Filesystem and set file permissions
212
+		$wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
213
+		// check file permissions
214
+		$perms = $wp_filesystem->getchmod(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
215
+		if ($perms) {
216
+			// file permissions exist, but way be set incorrectly
217
+			$type_of_file = ! empty($type_of_file) ? $type_of_file . ' ' : '';
218
+			$type_of_file .= ! empty($type_of_file) ? 'file' : 'folder';
219
+			return sprintf(
220
+				esc_html__(
221
+					'File permissions for the requested %1$s are currently set at "%2$s". The recommended permissions are 644 for files and 755 for folders.',
222
+					'event_espresso'
223
+				),
224
+				$type_of_file,
225
+				$perms
226
+			);
227
+		}
228
+		// file exists but file permissions could not be read ?!?!
229
+		return sprintf(
230
+			esc_html__(
231
+				'Please ensure that the server and/or PHP configuration allows the current process to access the following file: "%s".',
232
+				'event_espresso'
233
+			),
234
+			$full_file_path
235
+		);
236
+	}
237
+
238
+
239
+	/**
240
+	 * ensure_folder_exists_and_is_writable
241
+	 * ensures that a folder exists and is writable, will attempt to create folder if it does not exist
242
+	 * Also ensures all the parent folders exist, and if not tries to create them.
243
+	 * Also, if this function creates the folder, adds a .htaccess file and index.html file
244
+	 *
245
+	 * @param string $folder
246
+	 * @return bool false if folder isn't writable; true if it exists and is writeable,
247
+	 * @throws EE_Error if the folder exists and is writeable, but for some reason we
248
+	 * can't write to it
249
+	 */
250
+	public static function ensure_folder_exists_and_is_writable($folder = '')
251
+	{
252
+		if (empty($folder)) {
253
+			return false;
254
+		}
255
+		// remove ending /
256
+		$folder        = EEH_File::standardise_directory_separators(rtrim($folder, '/\\'));
257
+		$parent_folder = EEH_File::get_parent_folder($folder);
258
+		// add / to folder
259
+		$folder        = EEH_File::end_with_directory_separator($folder);
260
+		$wp_filesystem = EEH_File::_get_wp_filesystem($folder);
261
+		if (! $wp_filesystem->is_dir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
262
+			// ok so it doesn't exist. Does its parent? Can we write to it?
263
+			if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
264
+				return false;
265
+			}
266
+			if (! EEH_File::verify_is_writable($parent_folder)) {
267
+				return false;
268
+			} else {
269
+				if (! $wp_filesystem->mkdir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
270
+					if (defined('WP_DEBUG') && WP_DEBUG) {
271
+						$msg = sprintf(esc_html__('"%s" could not be created.', 'event_espresso'), $folder);
272
+						$msg .= EEH_File::_permissions_error_for_unreadable_filepath($folder);
273
+						throw new EE_Error($msg);
274
+					}
275
+					return false;
276
+				}
277
+				EEH_File::add_index_file($folder);
278
+			}
279
+		} elseif (! EEH_File::verify_is_writable($folder)) {
280
+			return false;
281
+		}
282
+		return true;
283
+	}
284
+
285
+
286
+	/**
287
+	 * verify_is_writable - checks if a file or folder is writable
288
+	 *
289
+	 * @param string $full_path      - full server path to file or folder
290
+	 * @param string $file_or_folder - whether checking a file or folder
291
+	 * @return bool
292
+	 * @throws EE_Error if filesystem credentials are required
293
+	 */
294
+	public static function verify_is_writable($full_path = '', $file_or_folder = 'folder')
295
+	{
296
+		// load WP_Filesystem and set file permissions
297
+		$wp_filesystem = EEH_File::_get_wp_filesystem($full_path);
298
+		$full_path     = EEH_File::standardise_directory_separators($full_path);
299
+		if (! $wp_filesystem->is_writable(EEH_File::convert_local_filepath_to_remote_filepath($full_path))) {
300
+			if (defined('WP_DEBUG') && WP_DEBUG) {
301
+				$msg = sprintf(
302
+					esc_html__('The "%1$s" %2$s is not writable.', 'event_espresso'),
303
+					$full_path,
304
+					$file_or_folder
305
+				);
306
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_path);
307
+				throw new EE_Error($msg);
308
+			}
309
+			return false;
310
+		}
311
+		return true;
312
+	}
313
+
314
+
315
+	/**
316
+	 * ensure_file_exists_and_is_writable
317
+	 * ensures that a file exists and is writable, will attempt to create file if it does not exist.
318
+	 * Also ensures all the parent folders exist, and if not tries to create them.
319
+	 *
320
+	 * @param string $full_file_path
321
+	 * @return bool
322
+	 * @throws EE_Error if filesystem credentials are required
323
+	 */
324
+	public static function ensure_file_exists_and_is_writable($full_file_path = '')
325
+	{
326
+		// load WP_Filesystem and set file permissions
327
+		$wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
328
+		$full_file_path = EEH_File::standardise_directory_separators($full_file_path);
329
+		$parent_folder  = EEH_File::get_parent_folder($full_file_path);
330
+		if (! EEH_File::exists($full_file_path)) {
331
+			if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
332
+				return false;
333
+			}
334
+			if (! $wp_filesystem->touch(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
335
+				if (defined('WP_DEBUG') && WP_DEBUG) {
336
+					$msg =
337
+						sprintf(esc_html__('The "%s" file could not be created.', 'event_espresso'), $full_file_path);
338
+					$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
339
+					throw new EE_Error($msg);
340
+				}
341
+				return false;
342
+			}
343
+		}
344
+		if (! EEH_File::verify_is_writable($full_file_path, 'file')) {
345
+			return false;
346
+		}
347
+		return true;
348
+	}
349
+
350
+
351
+	/**
352
+	 * Gets the parent folder. If provided with file, gets the folder that contains it.
353
+	 * If provided a folder, gets its parent folder.
354
+	 *
355
+	 * @param string $file_or_folder_path
356
+	 * @return string parent folder, ENDING with a directory separator
357
+	 */
358
+	public static function get_parent_folder($file_or_folder_path)
359
+	{
360
+		// find the last /, ignoring a / on the very end
361
+		// eg if given "/var/something/somewhere/", we want to get "somewhere"'s
362
+		// parent folder, "/var/something/"
363
+		$ds = strlen($file_or_folder_path) > 1
364
+			? strrpos($file_or_folder_path, '/', -2)
365
+			: strlen($file_or_folder_path);
366
+		return substr($file_or_folder_path, 0, $ds + 1);
367
+	}
368
+
369
+	// public static function ensure_folder_exists_recursively( $folder ) {
370
+	//
371
+	// }
372
+
373
+
374
+	/**
375
+	 * get_file_contents
376
+	 *
377
+	 * @param string $full_file_path
378
+	 * @return string
379
+	 * @throws EE_Error if filesystem credentials are required
380
+	 */
381
+	public static function get_file_contents($full_file_path = '')
382
+	{
383
+		$full_file_path = EEH_File::standardise_directory_separators($full_file_path);
384
+		if (
385
+			EEH_File::verify_filepath_and_permissions(
386
+				$full_file_path,
387
+				EEH_File::get_filename_from_filepath($full_file_path),
388
+				EEH_File::get_file_extension($full_file_path)
389
+			)
390
+		) {
391
+			// load WP_Filesystem and set file permissions
392
+			$wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
393
+			return $wp_filesystem->get_contents(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
394
+		}
395
+		return '';
396
+	}
397
+
398
+
399
+	/**
400
+	 * write_file
401
+	 *
402
+	 * @param string $full_file_path
403
+	 * @param string $file_contents - the content to be written to the file
404
+	 * @param string $file_type
405
+	 * @return bool
406
+	 * @throws EE_Error if filesystem credentials are required
407
+	 */
408
+	public static function write_to_file($full_file_path = '', $file_contents = '', $file_type = '')
409
+	{
410
+		$full_file_path = EEH_File::standardise_directory_separators($full_file_path);
411
+		$file_type      = ! empty($file_type) ? rtrim($file_type, ' ') . ' ' : '';
412
+		$folder         = EEH_File::remove_filename_from_filepath($full_file_path);
413
+		if (! EEH_File::verify_is_writable($folder)) {
414
+			if (defined('WP_DEBUG') && WP_DEBUG) {
415
+				$msg = sprintf(
416
+					esc_html__('The %1$sfile located at "%2$s" is not writable.', 'event_espresso'),
417
+					$file_type,
418
+					$full_file_path
419
+				);
420
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
421
+				throw new EE_Error($msg);
422
+			}
423
+			return false;
424
+		}
425
+		// load WP_Filesystem and set file permissions
426
+		$wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
427
+		// write the file
428
+		if (
429
+			! $wp_filesystem->put_contents(
430
+				EEH_File::convert_local_filepath_to_remote_filepath($full_file_path),
431
+				$file_contents
432
+			)
433
+		) {
434
+			if (defined('WP_DEBUG') && WP_DEBUG) {
435
+				$msg = sprintf(
436
+					esc_html__('The %1$sfile located at "%2$s" could not be written to.', 'event_espresso'),
437
+					$file_type,
438
+					$full_file_path
439
+				);
440
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, 'f');
441
+				throw new EE_Error($msg);
442
+			}
443
+			return false;
444
+		}
445
+		return true;
446
+	}
447
+
448
+
449
+	/**
450
+	 * Wrapper for WP_Filesystem_Base::delete
451
+	 *
452
+	 * @param string         $filepath
453
+	 * @param boolean        $recursive
454
+	 * @param boolean|string $type 'd' for directory, 'f' for file
455
+	 * @return boolean
456
+	 * @throws EE_Error if filesystem credentials are required
457
+	 */
458
+	public static function delete($filepath, $recursive = false, $type = false)
459
+	{
460
+		$wp_filesystem = EEH_File::_get_wp_filesystem();
461
+		return $wp_filesystem->delete($filepath, $recursive, $type);
462
+	}
463
+
464
+
465
+	/**
466
+	 * exists
467
+	 * checks if a file exists using the WP filesystem
468
+	 *
469
+	 * @param string $full_file_path
470
+	 * @return bool
471
+	 * @throws EE_Error if filesystem credentials are required
472
+	 */
473
+	public static function exists($full_file_path = '')
474
+	{
475
+		$wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
476
+		return $wp_filesystem->exists(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
477
+	}
478
+
479
+
480
+	/**
481
+	 * is_readable
482
+	 * checks if a file is_readable using the WP filesystem
483
+	 *
484
+	 * @param string $full_file_path
485
+	 * @return bool
486
+	 * @throws EE_Error if filesystem credentials are required
487
+	 */
488
+	public static function is_readable($full_file_path = '')
489
+	{
490
+		$wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
491
+		return $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
492
+	}
493
+
494
+
495
+	/**
496
+	 * remove_filename_from_filepath
497
+	 * given a full path to a file including the filename itself, this removes  the filename and returns the path, up
498
+	 * to, but NOT including the filename OR slash
499
+	 *
500
+	 * @param string $full_file_path
501
+	 * @return string
502
+	 */
503
+	public static function remove_filename_from_filepath($full_file_path = '')
504
+	{
505
+		return pathinfo($full_file_path, PATHINFO_DIRNAME);
506
+	}
507
+
508
+
509
+	/**
510
+	 * get_filename_from_filepath. Arguably the same as basename()
511
+	 *
512
+	 * @param string $full_file_path
513
+	 * @return string
514
+	 */
515
+	public static function get_filename_from_filepath($full_file_path = '')
516
+	{
517
+		return pathinfo($full_file_path, PATHINFO_BASENAME);
518
+	}
519
+
520
+
521
+	/**
522
+	 * get_file_extension
523
+	 *
524
+	 * @param string $full_file_path
525
+	 * @return string
526
+	 */
527
+	public static function get_file_extension($full_file_path = '')
528
+	{
529
+		return pathinfo($full_file_path, PATHINFO_EXTENSION);
530
+	}
531
+
532
+
533
+	/**
534
+	 * add_htaccess_deny_from_all so the webserver cannot access this folder
535
+	 *
536
+	 * @param string $folder
537
+	 * @return bool
538
+	 * @throws EE_Error if filesystem credentials are required
539
+	 */
540
+	public static function add_htaccess_deny_from_all($folder = '')
541
+	{
542
+		$folder = EEH_File::standardise_and_end_with_directory_separator($folder);
543
+		if (! EEH_File::exists($folder . '.htaccess')) {
544
+			if (! EEH_File::write_to_file($folder . '.htaccess', 'deny from all', '.htaccess')) {
545
+				return false;
546
+			}
547
+		}
548
+		return true;
549
+	}
550
+
551
+
552
+	/**
553
+	 * Adds an index file to this folder, so folks can't list all the file's contents
554
+	 *
555
+	 * @param string $folder
556
+	 * @return boolean
557
+	 * @throws EE_Error if filesystem credentials are required
558
+	 */
559
+	public static function add_index_file($folder)
560
+	{
561
+		$folder = EEH_File::standardise_and_end_with_directory_separator($folder);
562
+		if (! EEH_File::exists($folder . 'index.php')) {
563
+			if (
564
+				! EEH_File::write_to_file(
565
+					$folder . 'index.php',
566
+					'You are not permitted to read from this folder',
567
+					'.php'
568
+				)
569
+			) {
570
+				return false;
571
+			}
572
+		}
573
+		return true;
574
+	}
575
+
576
+
577
+	/**
578
+	 * Given that the file in $file_path has the normal name, (ie, CLASSNAME.whatever.php),
579
+	 * extract that classname.
580
+	 *
581
+	 * @param string $file_path
582
+	 * @return string
583
+	 */
584
+	public static function get_classname_from_filepath_with_standard_filename($file_path)
585
+	{
586
+		// extract file from path
587
+		$filename = basename($file_path);
588
+		// now remove the first period and everything after
589
+		$pos_of_first_period = strpos($filename, '.');
590
+		return substr($filename, 0, $pos_of_first_period);
591
+	}
592
+
593
+
594
+	/**
595
+	 * standardise_directory_separators
596
+	 *  convert all directory separators in a file path.
597
+	 *
598
+	 * @param string $file_path
599
+	 * @return string
600
+	 */
601
+	public static function standardise_directory_separators($file_path)
602
+	{
603
+		return str_replace(['\\', '/'], '/', $file_path);
604
+	}
605
+
606
+
607
+	/**
608
+	 * end_with_directory_separator
609
+	 *  ensures that file path ends with '/'
610
+	 *
611
+	 * @param string $file_path
612
+	 * @return string
613
+	 */
614
+	public static function end_with_directory_separator($file_path)
615
+	{
616
+		return rtrim($file_path, '/\\') . '/';
617
+	}
618
+
619
+
620
+	/**
621
+	 * shorthand for both EEH_FIle::end_with_directory_separator AND EEH_File::standardise_directory_separators
622
+	 *
623
+	 * @param $file_path
624
+	 * @return string
625
+	 */
626
+	public static function standardise_and_end_with_directory_separator($file_path)
627
+	{
628
+		return self::end_with_directory_separator(self::standardise_directory_separators($file_path));
629
+	}
630
+
631
+
632
+	/**
633
+	 * takes the folder name (with or without trailing slash) and finds the files it in,
634
+	 * and what the class's name inside of each should be.
635
+	 *
636
+	 * @param array   $folder_paths
637
+	 * @param boolean $index_numerically if TRUE, the returned array will be indexed numerically;
638
+	 *                                   if FALSE (Default), returned array will be indexed by the filenames minus
639
+	 *                                   extensions. Set it TRUE if you know there are files in the directory with the
640
+	 *                                   same name but different extensions
641
+	 * @return array if $index_numerically == TRUE keys are numeric ,
642
+	 *                                   if $index_numerically == FALSE (Default) keys are what the class names SHOULD
643
+	 *                                   be; and values are their filepaths
644
+	 */
645
+	public static function get_contents_of_folders($folder_paths = [], $index_numerically = false)
646
+	{
647
+		$class_to_folder_path = [];
648
+		foreach ($folder_paths as $folder_path) {
649
+			$folder_path = self::standardise_and_end_with_directory_separator($folder_path);
650
+			// load WP_Filesystem and set file permissions
651
+			$files_in_folder      = glob($folder_path . '*.php');
652
+			$class_to_folder_path = [];
653
+			if ($files_in_folder) {
654
+				foreach ($files_in_folder as $file_path) {
655
+					// only add files, not folders
656
+					if (! is_dir($file_path)) {
657
+						if ($index_numerically) {
658
+							$class_to_folder_path[] = $file_path;
659
+						} else {
660
+							$classname                          =
661
+								self::get_classname_from_filepath_with_standard_filename($file_path);
662
+							$class_to_folder_path[ $classname ] = $file_path;
663
+						}
664
+					}
665
+				}
666
+			}
667
+		}
668
+		return $class_to_folder_path;
669
+	}
670
+
671
+
672
+	/**
673
+	 * Copies a file. Mostly a wrapper of WP_Filesystem::copy
674
+	 *
675
+	 * @param string  $source_file
676
+	 * @param string  $destination_file
677
+	 * @param boolean $overwrite
678
+	 * @return boolean success
679
+	 * @throws EE_Error if filesystem credentials are required
680
+	 */
681
+	public static function copy($source_file, $destination_file, $overwrite = false)
682
+	{
683
+		$full_source_path = EEH_File::standardise_directory_separators($source_file);
684
+		if (! EEH_File::exists($full_source_path)) {
685
+			if (defined('WP_DEBUG') && WP_DEBUG) {
686
+				$msg = sprintf(
687
+					esc_html__('The file located at "%2$s" is not readable or doesn\'t exist.', 'event_espresso'),
688
+					$full_source_path
689
+				);
690
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_source_path);
691
+				throw new EE_Error($msg);
692
+			}
693
+			return false;
694
+		}
695
+
696
+		$full_dest_path = EEH_File::standardise_directory_separators($destination_file);
697
+		$folder         = EEH_File::remove_filename_from_filepath($full_dest_path);
698
+		EEH_File::ensure_folder_exists_and_is_writable($folder);
699
+		if (! EEH_File::verify_is_writable($folder)) {
700
+			if (defined('WP_DEBUG') && WP_DEBUG) {
701
+				$msg = sprintf(
702
+					esc_html__('The file located at "%2$s" is not writable.', 'event_espresso'),
703
+					$full_dest_path
704
+				);
705
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_dest_path);
706
+				throw new EE_Error($msg);
707
+			}
708
+			return false;
709
+		}
710
+
711
+		// load WP_Filesystem and set file permissions
712
+		$wp_filesystem = EEH_File::_get_wp_filesystem($destination_file);
713
+		// write the file
714
+		if (
715
+			! $wp_filesystem->copy(
716
+				EEH_File::convert_local_filepath_to_remote_filepath($full_source_path),
717
+				EEH_File::convert_local_filepath_to_remote_filepath($full_dest_path),
718
+				$overwrite
719
+			)
720
+		) {
721
+			if (defined('WP_DEBUG') && WP_DEBUG) {
722
+				$msg =
723
+					sprintf(
724
+						esc_html__(
725
+							'Attempted writing to file %1$s, but could not, probably because of permissions issues',
726
+							'event_espresso'
727
+						),
728
+						$full_source_path
729
+					);
730
+				$msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_source_path, 'f');
731
+				throw new EE_Error($msg);
732
+			}
733
+			return false;
734
+		}
735
+		return true;
736
+	}
737
+
738
+
739
+	/**
740
+	 * Reports whether or not the filepath is in the EE uploads folder or not
741
+	 *
742
+	 * @param string $filepath
743
+	 * @return boolean
744
+	 */
745
+	public static function is_in_uploads_folder($filepath)
746
+	{
747
+		$uploads = wp_upload_dir();
748
+		return strpos($filepath, $uploads['basedir']) === 0;
749
+	}
750
+
751
+
752
+	/**
753
+	 * Given a "local" filepath (what you probably thought was the only filepath),
754
+	 * converts it into a "remote" filepath (the filepath the currently-in-use
755
+	 * $wp_filesystem needs to use access the folder or file).
756
+	 * See http://wordpress.stackexchange.com/questions/124900/using-wp-filesystem-in-plugins
757
+	 *
758
+	 * @param string $local_filepath             the filepath to the folder/file locally
759
+	 * @return string the remote filepath (eg the filepath the filesystem method, eg
760
+	 *                                           ftp or ssh, will use to access the folder
761
+	 * @throws EE_Error if filesystem credentials are required
762
+	 */
763
+	public static function convert_local_filepath_to_remote_filepath($local_filepath)
764
+	{
765
+		$wp_filesystem = EEH_File::_get_wp_filesystem($local_filepath);
766
+		return str_replace(WP_CONTENT_DIR . '/', $wp_filesystem->wp_content_dir(), $local_filepath);
767
+	}
768 768
 }
Please login to merge, or discard this patch.
Spacing   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -48,21 +48,21 @@  discard block
 block discarded – undo
48 48
                 $filepath
49 49
             )
50 50
         ) {
51
-            if (! EEH_File::$_wp_filesystem_direct instanceof WP_Filesystem_Direct) {
52
-                require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
51
+            if ( ! EEH_File::$_wp_filesystem_direct instanceof WP_Filesystem_Direct) {
52
+                require_once(ABSPATH.'wp-admin/includes/class-wp-filesystem-base.php');
53 53
                 $method                    = 'direct';
54 54
                 $wp_filesystem_direct_file =
55 55
                     apply_filters(
56 56
                         'filesystem_method_file',
57
-                        ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php',
57
+                        ABSPATH.'wp-admin/includes/class-wp-filesystem-'.$method.'.php',
58 58
                         $method
59 59
                     );
60 60
                 // check constants defined, just like in wp-admin/includes/file.php's WP_Filesystem()
61
-                if (! defined('FS_CHMOD_DIR')) {
61
+                if ( ! defined('FS_CHMOD_DIR')) {
62 62
                     define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0777 | 0755));
63 63
                 }
64
-                if (! defined('FS_CHMOD_FILE')) {
65
-                    define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0777 | 0644));
64
+                if ( ! defined('FS_CHMOD_FILE')) {
65
+                    define('FS_CHMOD_FILE', (fileperms(ABSPATH.'index.php') & 0777 | 0644));
66 66
                 }
67 67
                 require_once($wp_filesystem_direct_file);
68 68
                 EEH_File::$_wp_filesystem_direct = new WP_Filesystem_Direct([]);
@@ -71,7 +71,7 @@  discard block
 block discarded – undo
71 71
         }
72 72
         global $wp_filesystem;
73 73
         // no filesystem setup ???
74
-        if (! $wp_filesystem instanceof WP_Filesystem_Base) {
74
+        if ( ! $wp_filesystem instanceof WP_Filesystem_Base) {
75 75
             // if some eager beaver's just trying to get in there too early...
76 76
             // let them do it, because we are one of those eager beavers! :P
77 77
             /**
@@ -99,9 +99,9 @@  discard block
 block discarded – undo
99 99
                 throw new EE_Error($msg);
100 100
             } else {
101 101
                 // should be loaded if we are past the wp_loaded hook...
102
-                if (! function_exists('WP_Filesystem')) {
103
-                    require_once(ABSPATH . 'wp-admin/includes/file.php');
104
-                    require_once(ABSPATH . 'wp-admin/includes/template.php');
102
+                if ( ! function_exists('WP_Filesystem')) {
103
+                    require_once(ABSPATH.'wp-admin/includes/file.php');
104
+                    require_once(ABSPATH.'wp-admin/includes/template.php');
105 105
                 }
106 106
                 // turn on output buffering so that we can capture the credentials form
107 107
                 ob_start();
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
                 // store credentials form for the time being
110 110
                 EEH_File::$_credentials_form = ob_get_clean();
111 111
                 // basically check for direct or previously configured access
112
-                if (! WP_Filesystem($credentials)) {
112
+                if ( ! WP_Filesystem($credentials)) {
113 113
                     // if credentials do NOT exist
114 114
                     if ($credentials === false) {
115 115
                         add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
@@ -140,8 +140,8 @@  discard block
 block discarded – undo
140 140
      */
141 141
     public static function display_request_filesystem_credentials_form()
142 142
     {
143
-        if (! empty(EEH_File::$_credentials_form)) {
144
-            echo '<div class="updated espresso-notices-attention"><p>' . EEH_File::$_credentials_form . '</p></div>';
143
+        if ( ! empty(EEH_File::$_credentials_form)) {
144
+            echo '<div class="updated espresso-notices-attention"><p>'.EEH_File::$_credentials_form.'</p></div>';
145 145
         }
146 146
     }
147 147
 
@@ -167,8 +167,8 @@  discard block
 block discarded – undo
167 167
         // load WP_Filesystem and set file permissions
168 168
         $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
169 169
         $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
170
-        if (! $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
171
-            $file_name = ! empty($type_of_file) ? $file_name . ' ' . $type_of_file : $file_name;
170
+        if ( ! $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
171
+            $file_name = ! empty($type_of_file) ? $file_name.' '.$type_of_file : $file_name;
172 172
             $file_name .= ! empty($file_ext) ? ' file' : ' folder';
173 173
             $msg       = sprintf(
174 174
                 esc_html__(
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
                 );
189 189
             }
190 190
             if (defined('WP_DEBUG') && WP_DEBUG) {
191
-                throw new EE_Error($msg . '||' . $msg);
191
+                throw new EE_Error($msg.'||'.$msg);
192 192
             }
193 193
             return false;
194 194
         }
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
         $perms = $wp_filesystem->getchmod(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
215 215
         if ($perms) {
216 216
             // file permissions exist, but way be set incorrectly
217
-            $type_of_file = ! empty($type_of_file) ? $type_of_file . ' ' : '';
217
+            $type_of_file = ! empty($type_of_file) ? $type_of_file.' ' : '';
218 218
             $type_of_file .= ! empty($type_of_file) ? 'file' : 'folder';
219 219
             return sprintf(
220 220
                 esc_html__(
@@ -258,15 +258,15 @@  discard block
 block discarded – undo
258 258
         // add / to folder
259 259
         $folder        = EEH_File::end_with_directory_separator($folder);
260 260
         $wp_filesystem = EEH_File::_get_wp_filesystem($folder);
261
-        if (! $wp_filesystem->is_dir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
261
+        if ( ! $wp_filesystem->is_dir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
262 262
             // ok so it doesn't exist. Does its parent? Can we write to it?
263
-            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
263
+            if ( ! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
264 264
                 return false;
265 265
             }
266
-            if (! EEH_File::verify_is_writable($parent_folder)) {
266
+            if ( ! EEH_File::verify_is_writable($parent_folder)) {
267 267
                 return false;
268 268
             } else {
269
-                if (! $wp_filesystem->mkdir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
269
+                if ( ! $wp_filesystem->mkdir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
270 270
                     if (defined('WP_DEBUG') && WP_DEBUG) {
271 271
                         $msg = sprintf(esc_html__('"%s" could not be created.', 'event_espresso'), $folder);
272 272
                         $msg .= EEH_File::_permissions_error_for_unreadable_filepath($folder);
@@ -276,7 +276,7 @@  discard block
 block discarded – undo
276 276
                 }
277 277
                 EEH_File::add_index_file($folder);
278 278
             }
279
-        } elseif (! EEH_File::verify_is_writable($folder)) {
279
+        } elseif ( ! EEH_File::verify_is_writable($folder)) {
280 280
             return false;
281 281
         }
282 282
         return true;
@@ -296,7 +296,7 @@  discard block
 block discarded – undo
296 296
         // load WP_Filesystem and set file permissions
297 297
         $wp_filesystem = EEH_File::_get_wp_filesystem($full_path);
298 298
         $full_path     = EEH_File::standardise_directory_separators($full_path);
299
-        if (! $wp_filesystem->is_writable(EEH_File::convert_local_filepath_to_remote_filepath($full_path))) {
299
+        if ( ! $wp_filesystem->is_writable(EEH_File::convert_local_filepath_to_remote_filepath($full_path))) {
300 300
             if (defined('WP_DEBUG') && WP_DEBUG) {
301 301
                 $msg = sprintf(
302 302
                     esc_html__('The "%1$s" %2$s is not writable.', 'event_espresso'),
@@ -327,11 +327,11 @@  discard block
 block discarded – undo
327 327
         $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
328 328
         $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
329 329
         $parent_folder  = EEH_File::get_parent_folder($full_file_path);
330
-        if (! EEH_File::exists($full_file_path)) {
331
-            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
330
+        if ( ! EEH_File::exists($full_file_path)) {
331
+            if ( ! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
332 332
                 return false;
333 333
             }
334
-            if (! $wp_filesystem->touch(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
334
+            if ( ! $wp_filesystem->touch(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
335 335
                 if (defined('WP_DEBUG') && WP_DEBUG) {
336 336
                     $msg =
337 337
                         sprintf(esc_html__('The "%s" file could not be created.', 'event_espresso'), $full_file_path);
@@ -341,7 +341,7 @@  discard block
 block discarded – undo
341 341
                 return false;
342 342
             }
343 343
         }
344
-        if (! EEH_File::verify_is_writable($full_file_path, 'file')) {
344
+        if ( ! EEH_File::verify_is_writable($full_file_path, 'file')) {
345 345
             return false;
346 346
         }
347 347
         return true;
@@ -408,9 +408,9 @@  discard block
 block discarded – undo
408 408
     public static function write_to_file($full_file_path = '', $file_contents = '', $file_type = '')
409 409
     {
410 410
         $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
411
-        $file_type      = ! empty($file_type) ? rtrim($file_type, ' ') . ' ' : '';
411
+        $file_type      = ! empty($file_type) ? rtrim($file_type, ' ').' ' : '';
412 412
         $folder         = EEH_File::remove_filename_from_filepath($full_file_path);
413
-        if (! EEH_File::verify_is_writable($folder)) {
413
+        if ( ! EEH_File::verify_is_writable($folder)) {
414 414
             if (defined('WP_DEBUG') && WP_DEBUG) {
415 415
                 $msg = sprintf(
416 416
                     esc_html__('The %1$sfile located at "%2$s" is not writable.', 'event_espresso'),
@@ -540,8 +540,8 @@  discard block
 block discarded – undo
540 540
     public static function add_htaccess_deny_from_all($folder = '')
541 541
     {
542 542
         $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
543
-        if (! EEH_File::exists($folder . '.htaccess')) {
544
-            if (! EEH_File::write_to_file($folder . '.htaccess', 'deny from all', '.htaccess')) {
543
+        if ( ! EEH_File::exists($folder.'.htaccess')) {
544
+            if ( ! EEH_File::write_to_file($folder.'.htaccess', 'deny from all', '.htaccess')) {
545 545
                 return false;
546 546
             }
547 547
         }
@@ -559,10 +559,10 @@  discard block
 block discarded – undo
559 559
     public static function add_index_file($folder)
560 560
     {
561 561
         $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
562
-        if (! EEH_File::exists($folder . 'index.php')) {
562
+        if ( ! EEH_File::exists($folder.'index.php')) {
563 563
             if (
564 564
                 ! EEH_File::write_to_file(
565
-                    $folder . 'index.php',
565
+                    $folder.'index.php',
566 566
                     'You are not permitted to read from this folder',
567 567
                     '.php'
568 568
                 )
@@ -613,7 +613,7 @@  discard block
 block discarded – undo
613 613
      */
614 614
     public static function end_with_directory_separator($file_path)
615 615
     {
616
-        return rtrim($file_path, '/\\') . '/';
616
+        return rtrim($file_path, '/\\').'/';
617 617
     }
618 618
 
619 619
 
@@ -648,18 +648,18 @@  discard block
 block discarded – undo
648 648
         foreach ($folder_paths as $folder_path) {
649 649
             $folder_path = self::standardise_and_end_with_directory_separator($folder_path);
650 650
             // load WP_Filesystem and set file permissions
651
-            $files_in_folder      = glob($folder_path . '*.php');
651
+            $files_in_folder      = glob($folder_path.'*.php');
652 652
             $class_to_folder_path = [];
653 653
             if ($files_in_folder) {
654 654
                 foreach ($files_in_folder as $file_path) {
655 655
                     // only add files, not folders
656
-                    if (! is_dir($file_path)) {
656
+                    if ( ! is_dir($file_path)) {
657 657
                         if ($index_numerically) {
658 658
                             $class_to_folder_path[] = $file_path;
659 659
                         } else {
660 660
                             $classname                          =
661 661
                                 self::get_classname_from_filepath_with_standard_filename($file_path);
662
-                            $class_to_folder_path[ $classname ] = $file_path;
662
+                            $class_to_folder_path[$classname] = $file_path;
663 663
                         }
664 664
                     }
665 665
                 }
@@ -681,7 +681,7 @@  discard block
 block discarded – undo
681 681
     public static function copy($source_file, $destination_file, $overwrite = false)
682 682
     {
683 683
         $full_source_path = EEH_File::standardise_directory_separators($source_file);
684
-        if (! EEH_File::exists($full_source_path)) {
684
+        if ( ! EEH_File::exists($full_source_path)) {
685 685
             if (defined('WP_DEBUG') && WP_DEBUG) {
686 686
                 $msg = sprintf(
687 687
                     esc_html__('The file located at "%2$s" is not readable or doesn\'t exist.', 'event_espresso'),
@@ -696,7 +696,7 @@  discard block
 block discarded – undo
696 696
         $full_dest_path = EEH_File::standardise_directory_separators($destination_file);
697 697
         $folder         = EEH_File::remove_filename_from_filepath($full_dest_path);
698 698
         EEH_File::ensure_folder_exists_and_is_writable($folder);
699
-        if (! EEH_File::verify_is_writable($folder)) {
699
+        if ( ! EEH_File::verify_is_writable($folder)) {
700 700
             if (defined('WP_DEBUG') && WP_DEBUG) {
701 701
                 $msg = sprintf(
702 702
                     esc_html__('The file located at "%2$s" is not writable.', 'event_espresso'),
@@ -763,6 +763,6 @@  discard block
 block discarded – undo
763 763
     public static function convert_local_filepath_to_remote_filepath($local_filepath)
764 764
     {
765 765
         $wp_filesystem = EEH_File::_get_wp_filesystem($local_filepath);
766
-        return str_replace(WP_CONTENT_DIR . '/', $wp_filesystem->wp_content_dir(), $local_filepath);
766
+        return str_replace(WP_CONTENT_DIR.'/', $wp_filesystem->wp_content_dir(), $local_filepath);
767 767
     }
768 768
 }
Please login to merge, or discard this patch.