Completed
Branch BUG/3560-ticket-taxes (8c52f5)
by
unknown
02:57 queued 24s
created
core/services/calculators/LineItemCalculator.php 2 patches
Indentation   +752 added lines, -752 removed lines patch added patch discarded remove patch
@@ -21,756 +21,756 @@
 block discarded – undo
21 21
 class LineItemCalculator
22 22
 {
23 23
 
24
-    /**
25
-     * @var DecimalValues
26
-     */
27
-    protected $decimal_values;
28
-
29
-    /**
30
-     * @var array
31
-     */
32
-    protected $default_query_params = [
33
-        ['LIN_type' => ['!=', EEM_Line_Item::type_cancellation]]
34
-    ];
35
-
36
-    private $lb = "\n"; // \n <br />
37
-
38
-
39
-    /**
40
-     * @param DecimalValues $decimal_values
41
-     */
42
-    public function __construct(DecimalValues $decimal_values)
43
-    {
44
-        $this->decimal_values = $decimal_values;
45
-    }
46
-
47
-
48
-    /**
49
-     * Gets the final total on this item, taking taxes into account.
50
-     * Has the side-effect of setting the sub-total as it was just calculated.
51
-     * If this is used on a grand-total line item, also updates the transaction's
52
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
53
-     * want to change a persistable transaction with info from a non-persistent line item)
54
-     *
55
-     * @param EE_Line_Item $line_item
56
-     * @param bool         $update_txn_status
57
-     * @return float
58
-     * @throws EE_Error
59
-     * @throws ReflectionException
60
-     */
61
-    public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
62
-    {
63
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
64
-        $ticket_line_items = EEH_Line_Item::get_ticket_line_items($line_item);
65
-        if (empty($ticket_line_items)) {
66
-            return 0;
67
-        }
68
-        [$total, $pretax_total] = $this->recalculateLineItemTotals($line_item);
69
-        // echo "{$this->lb}{$this->lb} = PRETAX GRAND TOTAL: {$pretax_total}";
70
-        // echo "{$this->lb} = GRAND TOTAL: {$total}{$this->lb}";
71
-        // EEH_Line_Item::visualize($line_item);
72
-        $total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
73
-        // no negative totals plz
74
-        $grand_total  = max($pretax_total + $total_tax, 0);
75
-        $this->updatePreTaxTotal($line_item, $pretax_total, true);
76
-        $grand_total  = $this->updateTotal($line_item, $grand_total, true);
77
-        $this->updateTransaction($line_item, $grand_total, $update_txn_status);
78
-        // die();
79
-        return $grand_total;
80
-    }
81
-
82
-
83
-    /**
84
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
85
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
86
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
87
-     * when this is called on the grand total
88
-     *
89
-     * @param EE_Line_Item $line_item
90
-     * @param float        $total
91
-     * @param float        $pretax_total
92
-     * @return array
93
-     * @throws EE_Error
94
-     * @throws ReflectionException
95
-     */
96
-    public function recalculateLineItemTotals(
97
-        EE_Line_Item $line_item,
98
-        float $total = 0,
99
-        float $pretax_total = 0
100
-    ): array {
101
-        // echo "{$this->lb}recalculate LineItem Totals: {$line_item->type()} -> {$line_item->name()}";
102
-        switch ($line_item->type()) {
103
-            case EEM_Line_Item::type_total:
104
-            case EEM_Line_Item::type_sub_total:
105
-                [$total, $pretax_total] = $this->recalculateSubTotal($line_item);
106
-                break;
107
-
108
-            case EEM_Line_Item::type_line_item:
109
-                [$total, $pretax_total] = $this->recalculateLineItem($line_item, $total, $pretax_total);
110
-                break;
111
-
112
-            case EEM_Line_Item::type_sub_line_item:
113
-                // sub line items operate on the total and update both the total AND the pre-tax total
114
-                [$total, $pretax_total] = $this->recalculateSubLineItem($line_item, $total, $pretax_total);
115
-                break;
116
-
117
-            case EEM_Line_Item::type_sub_tax:
118
-                // sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
119
-                [$total, $pretax_total] = $this->recalculateSubTax($line_item, $pretax_total);
120
-                break;
121
-
122
-            case EEM_Line_Item::type_tax_sub_total:
123
-            case EEM_Line_Item::type_tax:
124
-            case EEM_Line_Item::type_cancellation:
125
-                // completely ignore tax totals, tax sub-totals, and cancelled line items
126
-                // when calculating the pre-tax-total
127
-                $total = $pretax_total = 0;
128
-                break;
129
-        }
130
-        return [$total, $pretax_total];
131
-    }
132
-
133
-
134
-    /**
135
-     * @param EE_Line_Item $line_item
136
-     * @return array
137
-     * @throws EE_Error
138
-     * @throws ReflectionException
139
-     */
140
-    private function recalculateSubTotal(EE_Line_Item $line_item): array
141
-    {
142
-        // echo "{$this->lb}  recalculate SubTotal: {$line_item->name()}";
143
-        // reset the total and pretax total to zero since we are recalculating them
144
-        $total = $pretax_total = 0;
145
-        if ($line_item->is_total()) {
146
-            // if this is the grand total line item
147
-            // then first update ALL of the line item quantities (if need be)
148
-            $this->updateLineItemQuantities($line_item);
149
-        }
150
-        // recursively loop through children and recalculate their totals
151
-        $children = $line_item->children($this->default_query_params);
152
-        if (empty($children)) {
153
-            return [$total, $pretax_total];
154
-        }
155
-        foreach ($children as $child_line_item) {
156
-            [$child_total, $child_pretax_total] = $this->recalculateLineItemTotals(
157
-                $child_line_item,
158
-                $total,
159
-                $pretax_total
160
-            );
161
-            $total += $child_total;
162
-            $pretax_total += $child_pretax_total;
163
-            // echo "{$this->lb}   = running sub total: {$total} ({$child_line_item->name()})";
164
-            // echo "{$this->lb}   = running pretax sub total: {$pretax_total} ({$child_line_item->name()})";
165
-        }
166
-        // update the unit price and pretax total
167
-        $this->updateUnitPrice($line_item, $pretax_total);
168
-        $pretax_total = $this->updatePreTaxTotal($line_item, $pretax_total, true);
169
-        // for the actual pre-tax sub total line item, we want to save the pretax value for everything
170
-        if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
171
-            $this->updateTotal($line_item, $pretax_total, true);
172
-        } elseif (! $line_item->is_total()) {
173
-            // we don't update the total for the total line item, because that will need to include taxes
174
-            $total = $this->updateTotal($line_item, $total, true);
175
-        }
176
-        // echo "{$this->lb}   = pretax sub total: {$pretax_total} ({$line_item->name()})";
177
-        // echo "{$this->lb}   = sub total: {$total} ({$line_item->name()})";
178
-        return [$total, $pretax_total];
179
-    }
180
-
181
-
182
-    /**
183
-     * @param EE_Line_Item $line_item
184
-     * @param float        $total
185
-     * @param float        $pretax_total
186
-     * @return array
187
-     * @throws EE_Error
188
-     * @throws ReflectionException
189
-     */
190
-    private function recalculateLineItem(
191
-        EE_Line_Item $line_item,
192
-        float $total = 0,
193
-        float $pretax_total = 0
194
-    ): array {
195
-        // echo "{$this->lb}    recalculate LineItem: {$line_item->name()}";
196
-        if ($line_item->is_percent()) {
197
-            $total = $this->calculatePercentage($total, $line_item->percent());
198
-            $pretax_total = $this->calculatePercentage($pretax_total, $line_item->percent());
199
-            // echo "{$this->lb}     = % pretax total: {$pretax_total} ({$line_item->name()})";
200
-            // echo "{$this->lb}     = % total: {$total} ({$line_item->name()})";
201
-        } else {
202
-            // recursively loop through children and recalculate their totals
203
-            $children = $line_item->children($this->default_query_params);
204
-            if (! empty($children)) {
205
-                // reset the total and pretax total to zero since we are recalculating them
206
-                $total = $pretax_total = 0;
207
-                foreach ($children as $child_line_item) {
208
-                    [$child_total, $child_pretax_total] = $this->recalculateLineItemTotals(
209
-                        $child_line_item,
210
-                        $total,
211
-                        $pretax_total
212
-                    );
213
-                    $total        += $child_total;
214
-                    $pretax_total += $child_pretax_total;
215
-                    // echo "{$this->lb}     = running total: {$total} ({$child_line_item->name()})";
216
-                    // echo "{$this->lb}     = running pretax total: {$pretax_total} ({$child_line_item->name()})";
217
-                }
218
-                // echo "{$this->lb}     = child pretax total: {$pretax_total} ({$line_item->name()})";
219
-                // echo "{$this->lb}     = child total: {$total} ({$line_item->name()})";
220
-            } else {
221
-                // no child line items, so recalculate the total from the unit price and quantity
222
-                // and set the pretax total to match since their are obviously no sub-taxes
223
-                $pretax_total = $total = $this->calculateTotalForQuantity($line_item);
224
-                // echo "{$this->lb}     = li pretax total: {$pretax_total} ({$line_item->name()})";
225
-                // echo "{$this->lb}     = li total: {$total} ({$line_item->name()})";
226
-            }
227
-        }
228
-        $total  = $this->updateTotal($line_item, $total, true);
229
-        $pretax_total = $this->updatePreTaxTotal($line_item, $pretax_total, true);
230
-
231
-        // need to also adjust unit price too if the pretax total or quantity has been updated
232
-        $this->updateUnitPrice($line_item, $pretax_total);
233
-        return [$total, $pretax_total];
234
-    }
235
-
236
-
237
-    /**
238
-     * @param EE_Line_Item $sub_line_item
239
-     * @param float|int    $total
240
-     * @param float|int    $pretax_total
241
-     * @return float[]
242
-     * @throws EE_Error
243
-     * @throws ReflectionException
244
-     */
245
-    private function recalculateSubLineItem(EE_Line_Item $sub_line_item, float $total = 0, float $pretax_total = 0): array
246
-    {
247
-        // echo "{$this->lb}      recalculate SubLineItem: {$sub_line_item->name()}";
248
-        if ($sub_line_item->is_percent()) {
249
-            $new_total = $this->calculatePercentage($total, $sub_line_item->percent());
250
-            $new_pretax_total = $this->calculatePercentage($pretax_total, $sub_line_item->percent());
251
-            // echo "{$this->lb}       = sub li percent: {$sub_line_item->percent()}";
252
-            // echo "{$this->lb}       = sub li total: {$total}";
253
-            // echo "{$this->lb}       = sub li pretax_total: {$pretax_total}";
254
-        } else {
255
-            $new_total = $new_pretax_total = $this->calculateTotalForQuantity($sub_line_item);
256
-            // echo "{$this->lb}       = sub li unit price: {$sub_line_item->unit_price()}";
257
-            // echo "{$this->lb}       = sub li quantity: {$sub_line_item->quantity()}";
258
-            // echo "{$this->lb}       = sub li new_total: {$new_total}";
259
-        }
260
-        $total = $this->updateTotal($sub_line_item, $new_total);
261
-        $pretax_total = $this->updatePreTaxTotal($sub_line_item, $new_pretax_total);
262
-        // need to also adjust unit price too if the pretax total or quantity has been updated
263
-        $this->updateUnitPrice($sub_line_item, $pretax_total);
264
-        // echo "{$this->lb}       = sub li final pretax total: {$pretax_total}";
265
-        // echo "{$this->lb}       = sub li final total: {$total}";
266
-        return [$total, $pretax_total];
267
-    }
268
-
269
-
270
-    /**
271
-     * @param EE_Line_Item $sub_line_item
272
-     * @param float|int    $pretax_total
273
-     * @return float[]
274
-     * @throws EE_Error
275
-     * @throws ReflectionException
276
-     */
277
-    private function recalculateSubTax(EE_Line_Item $sub_line_item, float $pretax_total = 0): array
278
-    {
279
-        // echo "{$this->lb}      recalculate SubTax: {$sub_line_item->name()}";
280
-        // echo "{$this->lb}       = sub li percent: {$sub_line_item->percent()}";
281
-        $total_tax = $this->calculatePercentage($pretax_total, $sub_line_item->percent());
282
-        $total_tax = $this->updateTotal($sub_line_item, $total_tax);
283
-        // echo "{$this->lb}       = sub li total tax: {$total_tax}";
284
-        return [$total_tax, 0];
285
-    }
286
-
287
-
288
-    /**
289
-     * recursively loops through the entire line item tree updating line item quantities accordingly.
290
-     * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
291
-     *
292
-     * @param EE_Line_Item $line_item
293
-     * @param int          $quantity
294
-     * @return int
295
-     * @throws EE_Error
296
-     * @throws ReflectionException
297
-     */
298
-    private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
299
-    {
300
-        switch ($line_item->type()) {
301
-            case EEM_Line_Item::type_total:
302
-            case EEM_Line_Item::type_sub_total:
303
-            case EEM_Line_Item::type_tax_sub_total:
304
-                // first, loop through children and set their quantities
305
-                $count = 0;
306
-                $children = $line_item->children($this->default_query_params);
307
-                foreach ($children as $child_line_item) {
308
-                    $count += $this->updateLineItemQuantities($child_line_item);
309
-                }
310
-                // totals and subtotals should have a quantity of 1
311
-                // unless their children have all been removed, in which case we can set them to 0
312
-                $quantity = $count > 0 ? 1 : 0;
313
-                $this->updateQuantity($line_item, $quantity);
314
-                return $quantity;
315
-
316
-            case EEM_Line_Item::type_line_item:
317
-                // line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
318
-                // but if this is a percentage based line item, then ensure its quantity is 1
319
-                if ($line_item->is_percent()) {
320
-                    $this->updateQuantity($line_item, 1);
321
-                }
322
-                // and we also need to loop through all of the sub items and ensure those quantities match this parent.
323
-                $children = $line_item->children($this->default_query_params);
324
-                $quantity = $line_item->quantity();
325
-                foreach ($children as $child_line_item) {
326
-                    $this->updateLineItemQuantities($child_line_item, $quantity);
327
-                }
328
-                // percentage line items should not increment their parent's count, so they return 0
329
-                return ! $line_item->is_percent() ? $quantity : 0;
330
-
331
-            case EEM_Line_Item::type_sub_line_item:
332
-                // percentage based items need their quantity set to 1,
333
-                // all others use the incoming value from the parent line item
334
-                $quantity = $line_item->is_percent() ? 1 : $quantity;
335
-                $this->updateQuantity($line_item, $quantity);
336
-                // percentage line items should not increment their parent's count, so they return 0
337
-                return ! $line_item->is_percent() ? $quantity : 0;
338
-
339
-            case EEM_Line_Item::type_tax:
340
-            case EEM_Line_Item::type_sub_tax:
341
-                // taxes should have a quantity of 1
342
-                $this->updateQuantity($line_item, 1);
343
-                return 1;
344
-
345
-            case EEM_Line_Item::type_cancellation:
346
-                // cancellations will be ignored for all calculations
347
-                // because their parent quantities should have already been adjusted when they were added
348
-                // so assume that things are already set correctly
349
-                return 0;
350
-        }
351
-        return 0;
352
-    }
353
-
354
-
355
-    /**
356
-     * @param float $total
357
-     * @param float $percent
358
-     * @param bool  $round
359
-     * @return float
360
-     */
361
-    private function calculatePercentage(float $total, float $percent, bool $round = false): float
362
-    {
363
-        $amount = $total * $percent / 100;
364
-        // echo "{$this->lb}     = calculate %: {$total} * {$percent}% = {$amount}";
365
-        return $this->decimal_values->roundDecimalValue($amount, $round);
366
-    }
367
-
368
-
369
-    /**
370
-     * @param EE_Line_Item $line_item
371
-     * @return float
372
-     * @throws EE_Error
373
-     * @throws ReflectionException
374
-     */
375
-    private function calculateTotalForQuantity(EE_Line_Item $line_item): float
376
-    {
377
-        $total = $line_item->unit_price() * $line_item->quantity();
378
-        return $this->decimal_values->roundDecimalValue($total);
379
-    }
380
-
381
-
382
-    /**
383
-     * @param EE_Line_Item $line_item
384
-     * @param float        $percent
385
-     * @throws EE_Error
386
-     * @throws ReflectionException
387
-     */
388
-    private function updatePercent(EE_Line_Item $line_item, float $percent)
389
-    {
390
-        // update and save new percent only if incoming value does not match existing value
391
-        if ($line_item->percent() !== $percent) {
392
-            $line_item->set_percent($percent);
393
-            $line_item->maybe_save();
394
-        }
395
-    }
396
-
397
-
398
-    /**
399
-     * @param EE_Line_Item $line_item
400
-     * @param float        $pretax_total
401
-     * @param bool         $round
402
-     * @return float
403
-     * @throws EE_Error
404
-     * @throws ReflectionException
405
-     */
406
-    private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
407
-    {
408
-        $pretax_total = $this->decimal_values->roundDecimalValue($pretax_total, $round);
409
-        // update and save new total only if incoming value does not match existing value
410
-        if ($line_item->preTaxTotal() !== $pretax_total) {
411
-            $line_item->setPreTaxTotal($pretax_total);
412
-            $line_item->maybe_save();
413
-        }
414
-        return $pretax_total;
415
-    }
416
-
417
-
418
-    /**
419
-     * @param EE_Line_Item $line_item
420
-     * @param int          $quantity
421
-     * @throws EE_Error
422
-     * @throws ReflectionException
423
-     */
424
-    private function updateQuantity(EE_Line_Item $line_item, int $quantity)
425
-    {
426
-        // update and save new quantity only if incoming value does not match existing value
427
-        if ($line_item->quantity() !== $quantity) {
428
-            $line_item->set_quantity($quantity);
429
-            $line_item->maybe_save();
430
-        }
431
-    }
432
-
433
-
434
-    /**
435
-     * @param EE_Line_Item $line_item
436
-     * @param float        $total
437
-     * @param bool         $round
438
-     * @return float
439
-     * @throws EE_Error
440
-     * @throws ReflectionException
441
-     */
442
-    private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
443
-    {
444
-        $total = $this->decimal_values->roundDecimalValue($total, $round);
445
-        // update and save new total only if incoming value does not match existing value
446
-        if ($line_item->total() !== $total) {
447
-            $line_item->set_total($total);
448
-            $line_item->maybe_save();
449
-        }
450
-        return $total;
451
-    }
452
-
453
-
454
-    /**
455
-     * @param EE_Line_Item $line_item
456
-     * @param float        $total
457
-     * @param bool         $update_status
458
-     * @return void
459
-     * @throws EE_Error
460
-     * @throws ReflectionException
461
-     */
462
-    private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
463
-    {
464
-        // only update the related transaction's total
465
-        // if we intend to save this line item and its a grand total
466
-        if ($line_item->allow_persist()) {
467
-            $transaction = $line_item->transaction();
468
-            if ($transaction instanceof EE_Transaction) {
469
-                $transaction->set_total($total);
470
-                if ($update_status) {
471
-                    // don't save the TXN because that will be done below
472
-                    // and the following method only saves if the status changes
473
-                    $transaction->update_status_based_on_total_paid(false);
474
-                }
475
-                if ($transaction->ID()) {
476
-                    $transaction->save();
477
-                }
478
-            }
479
-        }
480
-    }
481
-
482
-
483
-    /**
484
-     * @param EE_Line_Item $line_item
485
-     * @param float        $pretax_total
486
-     * @return void
487
-     * @throws EE_Error
488
-     * @throws ReflectionException
489
-     */
490
-    private function updateUnitPrice(EE_Line_Item $line_item, float $pretax_total)
491
-    {
492
-        $quantity = $line_item->quantity();
493
-        // don't divide by zero else you'll create a singularity and implode the interweb
494
-        // we also don't set unit prices for percentage based line items
495
-        if ($quantity === 0 || $line_item->is_percent()) {
496
-            return;
497
-        }
498
-        $new_unit_price = $pretax_total / $quantity;
499
-        $new_unit_price = $this->decimal_values->roundDecimalValue($new_unit_price);
500
-        // update and save new total only if incoming value does not match existing value
501
-        if ($line_item->unit_price() !== $new_unit_price) {
502
-            $line_item->set_unit_price($new_unit_price);
503
-            $line_item->maybe_save();
504
-        }
505
-    }
506
-
507
-
508
-    /**
509
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
510
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
511
-     * and tax sub-total if already in the DB
512
-     *
513
-     * @param EE_Line_Item $total_line_item
514
-     * @return float
515
-     * @throws EE_Error
516
-     * @throws ReflectionException
517
-     */
518
-    public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
519
-    {
520
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
521
-        // calculate the total taxable amount for globally applied taxes
522
-        $taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
523
-        $global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
524
-        $non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
525
-        $all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
526
-        $this->recalculateTaxSubTotal($total_line_item);
527
-        return $all_tax_total;
528
-    }
529
-
530
-
531
-    /**
532
-     * @param EE_Line_Item $total_line_item
533
-     * @param float        $taxable_total
534
-     * @return float
535
-     * @throws EE_Error
536
-     * @throws ReflectionException
537
-     */
538
-    private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
539
-    {
540
-        $this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
541
-        $total_tax = 0;
542
-        // loop through all global taxes all taxes
543
-        $global_taxes = $total_line_item->tax_descendants();
544
-        foreach ($global_taxes as $tax) {
545
-            $tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
546
-            $tax_total = $this->updateTotal($tax, $tax_total, true);
547
-            $total_tax += $tax_total;
548
-        }
549
-        return $this->decimal_values->roundDecimalValue($total_tax, true);
550
-    }
551
-
552
-
553
-    /**
554
-     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
555
-     *
556
-     * @param EE_Line_Item $line_item
557
-     * @return void
558
-     * @throws EE_Error
559
-     * @throws ReflectionException
560
-     */
561
-    private function recalculateTaxSubTotal(EE_Line_Item $line_item)
562
-    {
563
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
564
-        foreach ($line_item->children() as $maybe_tax_subtotal) {
565
-            if (
566
-                $this->validateLineItemAndType($maybe_tax_subtotal)
567
-                && $maybe_tax_subtotal->is_tax_sub_total()
568
-            ) {
569
-                $total         = 0;
570
-                $total_percent = 0;
571
-                // simply loop through all its children (which should be taxes) and sum their total
572
-                foreach ($maybe_tax_subtotal->children() as $child_tax) {
573
-                    if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
574
-                        $total         += $child_tax->total();
575
-                        $total_percent += $child_tax->percent();
576
-                    }
577
-                }
578
-                $this->updateTotal($maybe_tax_subtotal, $total, true);
579
-                $this->updatePercent($maybe_tax_subtotal, $total_percent);
580
-            }
581
-        }
582
-    }
583
-
584
-
585
-    /**
586
-     * returns an array of tax details like:
587
-     *  [
588
-     *      'GST_7' => [
589
-     *          'name'  => 'GST',
590
-     *          'rate'  => float(7),
591
-     *          'total' => float(4.9),
592
-     *      ]
593
-     *  ]
594
-     *
595
-     * @param EE_Line_Item $total_line_item
596
-     * @param array        $non_global_taxes
597
-     * @param float        $line_item_total
598
-     * @return array
599
-     * @throws EE_Error
600
-     * @throws ReflectionException
601
-     */
602
-    private function calculateNonGlobalTaxes(
603
-        EE_Line_Item $total_line_item,
604
-        array $non_global_taxes = [],
605
-        float $line_item_total = 0
606
-    ): array {
607
-        foreach ($total_line_item->children() as $line_item) {
608
-            if ($this->validateLineItemAndType($line_item)) {
609
-                if ($line_item->is_sub_total()) {
610
-                    $non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
611
-                } elseif ($line_item->is_line_item()) {
612
-                    $non_global_taxes = $this->calculateNonGlobalTaxes(
613
-                        $line_item,
614
-                        $non_global_taxes,
615
-                        $line_item->pretaxTotal()
616
-                    );
617
-                } elseif ($line_item->isSubTax()) {
618
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
619
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
620
-                        $non_global_taxes[ $tax_ID ] = [
621
-                            'name'  => $line_item->name(),
622
-                            'rate'  => $line_item->percent(),
623
-                            'total' => 0,
624
-                            'obj'   => $line_item->OBJ_type(),
625
-                            'objID' => $line_item->OBJ_ID(),
626
-                        ];
627
-                    }
628
-                    $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
629
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
630
-                }
631
-            }
632
-        }
633
-        return $non_global_taxes;
634
-    }
635
-
636
-
637
-    /**
638
-     * @param EE_Line_Item $total_line_item
639
-     * @param float        $tax_total
640
-     * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
641
-     * @return float
642
-     * @throws EE_Error
643
-     * @throws ReflectionException
644
-     */
645
-    private function applyNonGlobalTaxes(
646
-        EE_Line_Item $total_line_item,
647
-        float $tax_total,
648
-        array $non_global_taxes
649
-    ): float {
650
-        $global_taxes   = $total_line_item->tax_descendants();
651
-        $taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
652
-        foreach ($non_global_taxes as $non_global_tax) {
653
-            $found = false;
654
-            foreach ($global_taxes as $global_tax) {
655
-                if (
656
-                    $this->validateLineItemAndType($global_tax)
657
-                    && $non_global_tax['obj'] === $global_tax->OBJ_type()
658
-                    && $non_global_tax['objID'] === $global_tax->OBJ_ID()
659
-                ) {
660
-                    $found = true;
661
-                    $new_total = $global_tax->total() + $non_global_tax['total'];
662
-                    // add non global tax to matching global tax AND the tax total
663
-                    $global_tax->set_total($new_total);
664
-                    $global_tax->maybe_save();
665
-                    $tax_total += $non_global_tax['total'];
666
-                }
667
-            }
668
-            if (! $found) {
669
-                // add a new line item for this non global tax
670
-                $taxes_subtotal->add_child_line_item(
671
-                    EE_Line_Item::new_instance(
672
-                        [
673
-                            'LIN_name'       => $non_global_tax['name'],
674
-                            'LIN_percent'    => $non_global_tax['rate'],
675
-                            'LIN_is_taxable' => false,
676
-                            'LIN_total'      => $non_global_tax['total'],
677
-                            'LIN_type'       => EEM_Line_Item::type_tax,
678
-                            'OBJ_type'       => $non_global_tax['obj'],
679
-                            'OBJ_ID'         => $non_global_tax['objID'],
680
-                        ]
681
-                    )
682
-                );
683
-                $tax_total += $non_global_tax['total'];
684
-            }
685
-        }
686
-        return $this->decimal_values->roundDecimalValue($tax_total, true);
687
-    }
688
-
689
-
690
-    /**
691
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
692
-     * recalculate_taxes_and_total
693
-     *
694
-     * @param EE_Line_Item $line_item
695
-     * @return float
696
-     * @throws EE_Error
697
-     * @throws ReflectionException
698
-     */
699
-    public function getTotalTax(EE_Line_Item $line_item): float
700
-    {
701
-        $this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
702
-        $this->recalculateTaxSubTotal($line_item);
703
-        $total = 0;
704
-        foreach ($line_item->tax_descendants() as $tax_line_item) {
705
-            if ($this->validateLineItemAndType($tax_line_item)) {
706
-                $total += $tax_line_item->total();
707
-            }
708
-        }
709
-        return $this->decimal_values->roundDecimalValue($total, true);
710
-    }
711
-
712
-
713
-    /**
714
-     * Returns the amount taxable among this line item's children (or if it has no children,
715
-     * how much of it is taxable). Does not recalculate totals or subtotals.
716
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
717
-     * but there is a "Taxable" discount), returns 0.
718
-     *
719
-     * @param EE_Line_Item|null $line_item
720
-     * @return float
721
-     * @throws EE_Error
722
-     * @throws ReflectionException
723
-     */
724
-    public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
725
-    {
726
-        $total      = 0;
727
-        $child_line_items = $line_item->children($this->default_query_params);
728
-        foreach ($child_line_items as $child_line_item) {
729
-            $this->validateLineItemAndType($child_line_item);
730
-            if ($child_line_item->is_sub_total()) {
731
-                $total += $this->taxableAmountForGlobalTaxes($child_line_item);
732
-            } elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
733
-                // if it's a percent item, only take into account
734
-                // the percentage that's taxable (the taxable total so far)
735
-                if ($child_line_item->is_percent()) {
736
-                    $total += $this->calculatePercentage($total, $child_line_item->percent(), true);
737
-                } else {
738
-                    // pretax total will be equal to the total for line items with globally applied taxes
739
-                    $pretax_total = $this->calculateTotalForQuantity($child_line_item);
740
-                    $total += $this->updatePreTaxTotal($child_line_item, $pretax_total);
741
-                }
742
-            }
743
-        }
744
-        return max($total, 0);
745
-    }
746
-
747
-
748
-    /**
749
-     * @param EE_Line_Item|null $line_item
750
-     * @param string|null       $type
751
-     * @return bool
752
-     * @throws EE_Error
753
-     * @throws ReflectionException
754
-     */
755
-    private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
756
-    {
757
-        if (! $line_item instanceof EE_Line_Item) {
758
-            throw new DomainException(
759
-                esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
760
-            );
761
-        }
762
-        if ($type && $line_item->type() !== $type) {
763
-            throw new DomainException(
764
-                sprintf(
765
-                    esc_html__(
766
-                        'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
767
-                        'event_espresso'
768
-                    ),
769
-                    $line_item->type(),
770
-                    $type
771
-                )
772
-            );
773
-        }
774
-        return true;
775
-    }
24
+	/**
25
+	 * @var DecimalValues
26
+	 */
27
+	protected $decimal_values;
28
+
29
+	/**
30
+	 * @var array
31
+	 */
32
+	protected $default_query_params = [
33
+		['LIN_type' => ['!=', EEM_Line_Item::type_cancellation]]
34
+	];
35
+
36
+	private $lb = "\n"; // \n <br />
37
+
38
+
39
+	/**
40
+	 * @param DecimalValues $decimal_values
41
+	 */
42
+	public function __construct(DecimalValues $decimal_values)
43
+	{
44
+		$this->decimal_values = $decimal_values;
45
+	}
46
+
47
+
48
+	/**
49
+	 * Gets the final total on this item, taking taxes into account.
50
+	 * Has the side-effect of setting the sub-total as it was just calculated.
51
+	 * If this is used on a grand-total line item, also updates the transaction's
52
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
53
+	 * want to change a persistable transaction with info from a non-persistent line item)
54
+	 *
55
+	 * @param EE_Line_Item $line_item
56
+	 * @param bool         $update_txn_status
57
+	 * @return float
58
+	 * @throws EE_Error
59
+	 * @throws ReflectionException
60
+	 */
61
+	public function recalculateTotalIncludingTaxes(EE_Line_Item $line_item, bool $update_txn_status = false): float
62
+	{
63
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
64
+		$ticket_line_items = EEH_Line_Item::get_ticket_line_items($line_item);
65
+		if (empty($ticket_line_items)) {
66
+			return 0;
67
+		}
68
+		[$total, $pretax_total] = $this->recalculateLineItemTotals($line_item);
69
+		// echo "{$this->lb}{$this->lb} = PRETAX GRAND TOTAL: {$pretax_total}";
70
+		// echo "{$this->lb} = GRAND TOTAL: {$total}{$this->lb}";
71
+		// EEH_Line_Item::visualize($line_item);
72
+		$total_tax = $this->recalculateTaxesAndTaxTotal($line_item);
73
+		// no negative totals plz
74
+		$grand_total  = max($pretax_total + $total_tax, 0);
75
+		$this->updatePreTaxTotal($line_item, $pretax_total, true);
76
+		$grand_total  = $this->updateTotal($line_item, $grand_total, true);
77
+		$this->updateTransaction($line_item, $grand_total, $update_txn_status);
78
+		// die();
79
+		return $grand_total;
80
+	}
81
+
82
+
83
+	/**
84
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
85
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
86
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
87
+	 * when this is called on the grand total
88
+	 *
89
+	 * @param EE_Line_Item $line_item
90
+	 * @param float        $total
91
+	 * @param float        $pretax_total
92
+	 * @return array
93
+	 * @throws EE_Error
94
+	 * @throws ReflectionException
95
+	 */
96
+	public function recalculateLineItemTotals(
97
+		EE_Line_Item $line_item,
98
+		float $total = 0,
99
+		float $pretax_total = 0
100
+	): array {
101
+		// echo "{$this->lb}recalculate LineItem Totals: {$line_item->type()} -> {$line_item->name()}";
102
+		switch ($line_item->type()) {
103
+			case EEM_Line_Item::type_total:
104
+			case EEM_Line_Item::type_sub_total:
105
+				[$total, $pretax_total] = $this->recalculateSubTotal($line_item);
106
+				break;
107
+
108
+			case EEM_Line_Item::type_line_item:
109
+				[$total, $pretax_total] = $this->recalculateLineItem($line_item, $total, $pretax_total);
110
+				break;
111
+
112
+			case EEM_Line_Item::type_sub_line_item:
113
+				// sub line items operate on the total and update both the total AND the pre-tax total
114
+				[$total, $pretax_total] = $this->recalculateSubLineItem($line_item, $total, $pretax_total);
115
+				break;
116
+
117
+			case EEM_Line_Item::type_sub_tax:
118
+				// sub line item taxes ONLY operate on the pre-tax total and ONLY update the total
119
+				[$total, $pretax_total] = $this->recalculateSubTax($line_item, $pretax_total);
120
+				break;
121
+
122
+			case EEM_Line_Item::type_tax_sub_total:
123
+			case EEM_Line_Item::type_tax:
124
+			case EEM_Line_Item::type_cancellation:
125
+				// completely ignore tax totals, tax sub-totals, and cancelled line items
126
+				// when calculating the pre-tax-total
127
+				$total = $pretax_total = 0;
128
+				break;
129
+		}
130
+		return [$total, $pretax_total];
131
+	}
132
+
133
+
134
+	/**
135
+	 * @param EE_Line_Item $line_item
136
+	 * @return array
137
+	 * @throws EE_Error
138
+	 * @throws ReflectionException
139
+	 */
140
+	private function recalculateSubTotal(EE_Line_Item $line_item): array
141
+	{
142
+		// echo "{$this->lb}  recalculate SubTotal: {$line_item->name()}";
143
+		// reset the total and pretax total to zero since we are recalculating them
144
+		$total = $pretax_total = 0;
145
+		if ($line_item->is_total()) {
146
+			// if this is the grand total line item
147
+			// then first update ALL of the line item quantities (if need be)
148
+			$this->updateLineItemQuantities($line_item);
149
+		}
150
+		// recursively loop through children and recalculate their totals
151
+		$children = $line_item->children($this->default_query_params);
152
+		if (empty($children)) {
153
+			return [$total, $pretax_total];
154
+		}
155
+		foreach ($children as $child_line_item) {
156
+			[$child_total, $child_pretax_total] = $this->recalculateLineItemTotals(
157
+				$child_line_item,
158
+				$total,
159
+				$pretax_total
160
+			);
161
+			$total += $child_total;
162
+			$pretax_total += $child_pretax_total;
163
+			// echo "{$this->lb}   = running sub total: {$total} ({$child_line_item->name()})";
164
+			// echo "{$this->lb}   = running pretax sub total: {$pretax_total} ({$child_line_item->name()})";
165
+		}
166
+		// update the unit price and pretax total
167
+		$this->updateUnitPrice($line_item, $pretax_total);
168
+		$pretax_total = $this->updatePreTaxTotal($line_item, $pretax_total, true);
169
+		// for the actual pre-tax sub total line item, we want to save the pretax value for everything
170
+		if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
171
+			$this->updateTotal($line_item, $pretax_total, true);
172
+		} elseif (! $line_item->is_total()) {
173
+			// we don't update the total for the total line item, because that will need to include taxes
174
+			$total = $this->updateTotal($line_item, $total, true);
175
+		}
176
+		// echo "{$this->lb}   = pretax sub total: {$pretax_total} ({$line_item->name()})";
177
+		// echo "{$this->lb}   = sub total: {$total} ({$line_item->name()})";
178
+		return [$total, $pretax_total];
179
+	}
180
+
181
+
182
+	/**
183
+	 * @param EE_Line_Item $line_item
184
+	 * @param float        $total
185
+	 * @param float        $pretax_total
186
+	 * @return array
187
+	 * @throws EE_Error
188
+	 * @throws ReflectionException
189
+	 */
190
+	private function recalculateLineItem(
191
+		EE_Line_Item $line_item,
192
+		float $total = 0,
193
+		float $pretax_total = 0
194
+	): array {
195
+		// echo "{$this->lb}    recalculate LineItem: {$line_item->name()}";
196
+		if ($line_item->is_percent()) {
197
+			$total = $this->calculatePercentage($total, $line_item->percent());
198
+			$pretax_total = $this->calculatePercentage($pretax_total, $line_item->percent());
199
+			// echo "{$this->lb}     = % pretax total: {$pretax_total} ({$line_item->name()})";
200
+			// echo "{$this->lb}     = % total: {$total} ({$line_item->name()})";
201
+		} else {
202
+			// recursively loop through children and recalculate their totals
203
+			$children = $line_item->children($this->default_query_params);
204
+			if (! empty($children)) {
205
+				// reset the total and pretax total to zero since we are recalculating them
206
+				$total = $pretax_total = 0;
207
+				foreach ($children as $child_line_item) {
208
+					[$child_total, $child_pretax_total] = $this->recalculateLineItemTotals(
209
+						$child_line_item,
210
+						$total,
211
+						$pretax_total
212
+					);
213
+					$total        += $child_total;
214
+					$pretax_total += $child_pretax_total;
215
+					// echo "{$this->lb}     = running total: {$total} ({$child_line_item->name()})";
216
+					// echo "{$this->lb}     = running pretax total: {$pretax_total} ({$child_line_item->name()})";
217
+				}
218
+				// echo "{$this->lb}     = child pretax total: {$pretax_total} ({$line_item->name()})";
219
+				// echo "{$this->lb}     = child total: {$total} ({$line_item->name()})";
220
+			} else {
221
+				// no child line items, so recalculate the total from the unit price and quantity
222
+				// and set the pretax total to match since their are obviously no sub-taxes
223
+				$pretax_total = $total = $this->calculateTotalForQuantity($line_item);
224
+				// echo "{$this->lb}     = li pretax total: {$pretax_total} ({$line_item->name()})";
225
+				// echo "{$this->lb}     = li total: {$total} ({$line_item->name()})";
226
+			}
227
+		}
228
+		$total  = $this->updateTotal($line_item, $total, true);
229
+		$pretax_total = $this->updatePreTaxTotal($line_item, $pretax_total, true);
230
+
231
+		// need to also adjust unit price too if the pretax total or quantity has been updated
232
+		$this->updateUnitPrice($line_item, $pretax_total);
233
+		return [$total, $pretax_total];
234
+	}
235
+
236
+
237
+	/**
238
+	 * @param EE_Line_Item $sub_line_item
239
+	 * @param float|int    $total
240
+	 * @param float|int    $pretax_total
241
+	 * @return float[]
242
+	 * @throws EE_Error
243
+	 * @throws ReflectionException
244
+	 */
245
+	private function recalculateSubLineItem(EE_Line_Item $sub_line_item, float $total = 0, float $pretax_total = 0): array
246
+	{
247
+		// echo "{$this->lb}      recalculate SubLineItem: {$sub_line_item->name()}";
248
+		if ($sub_line_item->is_percent()) {
249
+			$new_total = $this->calculatePercentage($total, $sub_line_item->percent());
250
+			$new_pretax_total = $this->calculatePercentage($pretax_total, $sub_line_item->percent());
251
+			// echo "{$this->lb}       = sub li percent: {$sub_line_item->percent()}";
252
+			// echo "{$this->lb}       = sub li total: {$total}";
253
+			// echo "{$this->lb}       = sub li pretax_total: {$pretax_total}";
254
+		} else {
255
+			$new_total = $new_pretax_total = $this->calculateTotalForQuantity($sub_line_item);
256
+			// echo "{$this->lb}       = sub li unit price: {$sub_line_item->unit_price()}";
257
+			// echo "{$this->lb}       = sub li quantity: {$sub_line_item->quantity()}";
258
+			// echo "{$this->lb}       = sub li new_total: {$new_total}";
259
+		}
260
+		$total = $this->updateTotal($sub_line_item, $new_total);
261
+		$pretax_total = $this->updatePreTaxTotal($sub_line_item, $new_pretax_total);
262
+		// need to also adjust unit price too if the pretax total or quantity has been updated
263
+		$this->updateUnitPrice($sub_line_item, $pretax_total);
264
+		// echo "{$this->lb}       = sub li final pretax total: {$pretax_total}";
265
+		// echo "{$this->lb}       = sub li final total: {$total}";
266
+		return [$total, $pretax_total];
267
+	}
268
+
269
+
270
+	/**
271
+	 * @param EE_Line_Item $sub_line_item
272
+	 * @param float|int    $pretax_total
273
+	 * @return float[]
274
+	 * @throws EE_Error
275
+	 * @throws ReflectionException
276
+	 */
277
+	private function recalculateSubTax(EE_Line_Item $sub_line_item, float $pretax_total = 0): array
278
+	{
279
+		// echo "{$this->lb}      recalculate SubTax: {$sub_line_item->name()}";
280
+		// echo "{$this->lb}       = sub li percent: {$sub_line_item->percent()}";
281
+		$total_tax = $this->calculatePercentage($pretax_total, $sub_line_item->percent());
282
+		$total_tax = $this->updateTotal($sub_line_item, $total_tax);
283
+		// echo "{$this->lb}       = sub li total tax: {$total_tax}";
284
+		return [$total_tax, 0];
285
+	}
286
+
287
+
288
+	/**
289
+	 * recursively loops through the entire line item tree updating line item quantities accordingly.
290
+	 * this needs to be done prior to running any other calculations for reasons that are hopefully obvious :p
291
+	 *
292
+	 * @param EE_Line_Item $line_item
293
+	 * @param int          $quantity
294
+	 * @return int
295
+	 * @throws EE_Error
296
+	 * @throws ReflectionException
297
+	 */
298
+	private function updateLineItemQuantities(EE_Line_Item $line_item, int $quantity = 1): int
299
+	{
300
+		switch ($line_item->type()) {
301
+			case EEM_Line_Item::type_total:
302
+			case EEM_Line_Item::type_sub_total:
303
+			case EEM_Line_Item::type_tax_sub_total:
304
+				// first, loop through children and set their quantities
305
+				$count = 0;
306
+				$children = $line_item->children($this->default_query_params);
307
+				foreach ($children as $child_line_item) {
308
+					$count += $this->updateLineItemQuantities($child_line_item);
309
+				}
310
+				// totals and subtotals should have a quantity of 1
311
+				// unless their children have all been removed, in which case we can set them to 0
312
+				$quantity = $count > 0 ? 1 : 0;
313
+				$this->updateQuantity($line_item, $quantity);
314
+				return $quantity;
315
+
316
+			case EEM_Line_Item::type_line_item:
317
+				// line items should ALREADY have accurate quantities set, if not, then somebody done goofed!
318
+				// but if this is a percentage based line item, then ensure its quantity is 1
319
+				if ($line_item->is_percent()) {
320
+					$this->updateQuantity($line_item, 1);
321
+				}
322
+				// and we also need to loop through all of the sub items and ensure those quantities match this parent.
323
+				$children = $line_item->children($this->default_query_params);
324
+				$quantity = $line_item->quantity();
325
+				foreach ($children as $child_line_item) {
326
+					$this->updateLineItemQuantities($child_line_item, $quantity);
327
+				}
328
+				// percentage line items should not increment their parent's count, so they return 0
329
+				return ! $line_item->is_percent() ? $quantity : 0;
330
+
331
+			case EEM_Line_Item::type_sub_line_item:
332
+				// percentage based items need their quantity set to 1,
333
+				// all others use the incoming value from the parent line item
334
+				$quantity = $line_item->is_percent() ? 1 : $quantity;
335
+				$this->updateQuantity($line_item, $quantity);
336
+				// percentage line items should not increment their parent's count, so they return 0
337
+				return ! $line_item->is_percent() ? $quantity : 0;
338
+
339
+			case EEM_Line_Item::type_tax:
340
+			case EEM_Line_Item::type_sub_tax:
341
+				// taxes should have a quantity of 1
342
+				$this->updateQuantity($line_item, 1);
343
+				return 1;
344
+
345
+			case EEM_Line_Item::type_cancellation:
346
+				// cancellations will be ignored for all calculations
347
+				// because their parent quantities should have already been adjusted when they were added
348
+				// so assume that things are already set correctly
349
+				return 0;
350
+		}
351
+		return 0;
352
+	}
353
+
354
+
355
+	/**
356
+	 * @param float $total
357
+	 * @param float $percent
358
+	 * @param bool  $round
359
+	 * @return float
360
+	 */
361
+	private function calculatePercentage(float $total, float $percent, bool $round = false): float
362
+	{
363
+		$amount = $total * $percent / 100;
364
+		// echo "{$this->lb}     = calculate %: {$total} * {$percent}% = {$amount}";
365
+		return $this->decimal_values->roundDecimalValue($amount, $round);
366
+	}
367
+
368
+
369
+	/**
370
+	 * @param EE_Line_Item $line_item
371
+	 * @return float
372
+	 * @throws EE_Error
373
+	 * @throws ReflectionException
374
+	 */
375
+	private function calculateTotalForQuantity(EE_Line_Item $line_item): float
376
+	{
377
+		$total = $line_item->unit_price() * $line_item->quantity();
378
+		return $this->decimal_values->roundDecimalValue($total);
379
+	}
380
+
381
+
382
+	/**
383
+	 * @param EE_Line_Item $line_item
384
+	 * @param float        $percent
385
+	 * @throws EE_Error
386
+	 * @throws ReflectionException
387
+	 */
388
+	private function updatePercent(EE_Line_Item $line_item, float $percent)
389
+	{
390
+		// update and save new percent only if incoming value does not match existing value
391
+		if ($line_item->percent() !== $percent) {
392
+			$line_item->set_percent($percent);
393
+			$line_item->maybe_save();
394
+		}
395
+	}
396
+
397
+
398
+	/**
399
+	 * @param EE_Line_Item $line_item
400
+	 * @param float        $pretax_total
401
+	 * @param bool         $round
402
+	 * @return float
403
+	 * @throws EE_Error
404
+	 * @throws ReflectionException
405
+	 */
406
+	private function updatePreTaxTotal(EE_Line_Item $line_item, float $pretax_total, bool $round = false): float
407
+	{
408
+		$pretax_total = $this->decimal_values->roundDecimalValue($pretax_total, $round);
409
+		// update and save new total only if incoming value does not match existing value
410
+		if ($line_item->preTaxTotal() !== $pretax_total) {
411
+			$line_item->setPreTaxTotal($pretax_total);
412
+			$line_item->maybe_save();
413
+		}
414
+		return $pretax_total;
415
+	}
416
+
417
+
418
+	/**
419
+	 * @param EE_Line_Item $line_item
420
+	 * @param int          $quantity
421
+	 * @throws EE_Error
422
+	 * @throws ReflectionException
423
+	 */
424
+	private function updateQuantity(EE_Line_Item $line_item, int $quantity)
425
+	{
426
+		// update and save new quantity only if incoming value does not match existing value
427
+		if ($line_item->quantity() !== $quantity) {
428
+			$line_item->set_quantity($quantity);
429
+			$line_item->maybe_save();
430
+		}
431
+	}
432
+
433
+
434
+	/**
435
+	 * @param EE_Line_Item $line_item
436
+	 * @param float        $total
437
+	 * @param bool         $round
438
+	 * @return float
439
+	 * @throws EE_Error
440
+	 * @throws ReflectionException
441
+	 */
442
+	private function updateTotal(EE_Line_Item $line_item, float $total, bool $round = false): float
443
+	{
444
+		$total = $this->decimal_values->roundDecimalValue($total, $round);
445
+		// update and save new total only if incoming value does not match existing value
446
+		if ($line_item->total() !== $total) {
447
+			$line_item->set_total($total);
448
+			$line_item->maybe_save();
449
+		}
450
+		return $total;
451
+	}
452
+
453
+
454
+	/**
455
+	 * @param EE_Line_Item $line_item
456
+	 * @param float        $total
457
+	 * @param bool         $update_status
458
+	 * @return void
459
+	 * @throws EE_Error
460
+	 * @throws ReflectionException
461
+	 */
462
+	private function updateTransaction(EE_Line_Item $line_item, float $total, bool $update_status)
463
+	{
464
+		// only update the related transaction's total
465
+		// if we intend to save this line item and its a grand total
466
+		if ($line_item->allow_persist()) {
467
+			$transaction = $line_item->transaction();
468
+			if ($transaction instanceof EE_Transaction) {
469
+				$transaction->set_total($total);
470
+				if ($update_status) {
471
+					// don't save the TXN because that will be done below
472
+					// and the following method only saves if the status changes
473
+					$transaction->update_status_based_on_total_paid(false);
474
+				}
475
+				if ($transaction->ID()) {
476
+					$transaction->save();
477
+				}
478
+			}
479
+		}
480
+	}
481
+
482
+
483
+	/**
484
+	 * @param EE_Line_Item $line_item
485
+	 * @param float        $pretax_total
486
+	 * @return void
487
+	 * @throws EE_Error
488
+	 * @throws ReflectionException
489
+	 */
490
+	private function updateUnitPrice(EE_Line_Item $line_item, float $pretax_total)
491
+	{
492
+		$quantity = $line_item->quantity();
493
+		// don't divide by zero else you'll create a singularity and implode the interweb
494
+		// we also don't set unit prices for percentage based line items
495
+		if ($quantity === 0 || $line_item->is_percent()) {
496
+			return;
497
+		}
498
+		$new_unit_price = $pretax_total / $quantity;
499
+		$new_unit_price = $this->decimal_values->roundDecimalValue($new_unit_price);
500
+		// update and save new total only if incoming value does not match existing value
501
+		if ($line_item->unit_price() !== $new_unit_price) {
502
+			$line_item->set_unit_price($new_unit_price);
503
+			$line_item->maybe_save();
504
+		}
505
+	}
506
+
507
+
508
+	/**
509
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
510
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
511
+	 * and tax sub-total if already in the DB
512
+	 *
513
+	 * @param EE_Line_Item $total_line_item
514
+	 * @return float
515
+	 * @throws EE_Error
516
+	 * @throws ReflectionException
517
+	 */
518
+	public function recalculateTaxesAndTaxTotal(EE_Line_Item $total_line_item): float
519
+	{
520
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
521
+		// calculate the total taxable amount for globally applied taxes
522
+		$taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
523
+		$global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
524
+		$non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
525
+		$all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
526
+		$this->recalculateTaxSubTotal($total_line_item);
527
+		return $all_tax_total;
528
+	}
529
+
530
+
531
+	/**
532
+	 * @param EE_Line_Item $total_line_item
533
+	 * @param float        $taxable_total
534
+	 * @return float
535
+	 * @throws EE_Error
536
+	 * @throws ReflectionException
537
+	 */
538
+	private function applyGlobalTaxes(EE_Line_Item $total_line_item, float $taxable_total): float
539
+	{
540
+		$this->validateLineItemAndType($total_line_item, EEM_Line_Item::type_total);
541
+		$total_tax = 0;
542
+		// loop through all global taxes all taxes
543
+		$global_taxes = $total_line_item->tax_descendants();
544
+		foreach ($global_taxes as $tax) {
545
+			$tax_total = $this->calculatePercentage($taxable_total, $tax->percent());
546
+			$tax_total = $this->updateTotal($tax, $tax_total, true);
547
+			$total_tax += $tax_total;
548
+		}
549
+		return $this->decimal_values->roundDecimalValue($total_tax, true);
550
+	}
551
+
552
+
553
+	/**
554
+	 * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
555
+	 *
556
+	 * @param EE_Line_Item $line_item
557
+	 * @return void
558
+	 * @throws EE_Error
559
+	 * @throws ReflectionException
560
+	 */
561
+	private function recalculateTaxSubTotal(EE_Line_Item $line_item)
562
+	{
563
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
564
+		foreach ($line_item->children() as $maybe_tax_subtotal) {
565
+			if (
566
+				$this->validateLineItemAndType($maybe_tax_subtotal)
567
+				&& $maybe_tax_subtotal->is_tax_sub_total()
568
+			) {
569
+				$total         = 0;
570
+				$total_percent = 0;
571
+				// simply loop through all its children (which should be taxes) and sum their total
572
+				foreach ($maybe_tax_subtotal->children() as $child_tax) {
573
+					if ($this->validateLineItemAndType($child_tax) && $child_tax->isGlobalTax()) {
574
+						$total         += $child_tax->total();
575
+						$total_percent += $child_tax->percent();
576
+					}
577
+				}
578
+				$this->updateTotal($maybe_tax_subtotal, $total, true);
579
+				$this->updatePercent($maybe_tax_subtotal, $total_percent);
580
+			}
581
+		}
582
+	}
583
+
584
+
585
+	/**
586
+	 * returns an array of tax details like:
587
+	 *  [
588
+	 *      'GST_7' => [
589
+	 *          'name'  => 'GST',
590
+	 *          'rate'  => float(7),
591
+	 *          'total' => float(4.9),
592
+	 *      ]
593
+	 *  ]
594
+	 *
595
+	 * @param EE_Line_Item $total_line_item
596
+	 * @param array        $non_global_taxes
597
+	 * @param float        $line_item_total
598
+	 * @return array
599
+	 * @throws EE_Error
600
+	 * @throws ReflectionException
601
+	 */
602
+	private function calculateNonGlobalTaxes(
603
+		EE_Line_Item $total_line_item,
604
+		array $non_global_taxes = [],
605
+		float $line_item_total = 0
606
+	): array {
607
+		foreach ($total_line_item->children() as $line_item) {
608
+			if ($this->validateLineItemAndType($line_item)) {
609
+				if ($line_item->is_sub_total()) {
610
+					$non_global_taxes = $this->calculateNonGlobalTaxes($line_item, $non_global_taxes);
611
+				} elseif ($line_item->is_line_item()) {
612
+					$non_global_taxes = $this->calculateNonGlobalTaxes(
613
+						$line_item,
614
+						$non_global_taxes,
615
+						$line_item->pretaxTotal()
616
+					);
617
+				} elseif ($line_item->isSubTax()) {
618
+					$tax_ID = $line_item->name() . '_' . $line_item->percent();
619
+					if (! isset($non_global_taxes[ $tax_ID ])) {
620
+						$non_global_taxes[ $tax_ID ] = [
621
+							'name'  => $line_item->name(),
622
+							'rate'  => $line_item->percent(),
623
+							'total' => 0,
624
+							'obj'   => $line_item->OBJ_type(),
625
+							'objID' => $line_item->OBJ_ID(),
626
+						];
627
+					}
628
+					$tax = $this->calculatePercentage($line_item_total, $line_item->percent());
629
+					$non_global_taxes[ $tax_ID ]['total'] += $tax;
630
+				}
631
+			}
632
+		}
633
+		return $non_global_taxes;
634
+	}
635
+
636
+
637
+	/**
638
+	 * @param EE_Line_Item $total_line_item
639
+	 * @param float        $tax_total
640
+	 * @param array        $non_global_taxes array of tax details generated by calculateNonGlobalTaxes()
641
+	 * @return float
642
+	 * @throws EE_Error
643
+	 * @throws ReflectionException
644
+	 */
645
+	private function applyNonGlobalTaxes(
646
+		EE_Line_Item $total_line_item,
647
+		float $tax_total,
648
+		array $non_global_taxes
649
+	): float {
650
+		$global_taxes   = $total_line_item->tax_descendants();
651
+		$taxes_subtotal = EEH_Line_Item::get_taxes_subtotal($total_line_item);
652
+		foreach ($non_global_taxes as $non_global_tax) {
653
+			$found = false;
654
+			foreach ($global_taxes as $global_tax) {
655
+				if (
656
+					$this->validateLineItemAndType($global_tax)
657
+					&& $non_global_tax['obj'] === $global_tax->OBJ_type()
658
+					&& $non_global_tax['objID'] === $global_tax->OBJ_ID()
659
+				) {
660
+					$found = true;
661
+					$new_total = $global_tax->total() + $non_global_tax['total'];
662
+					// add non global tax to matching global tax AND the tax total
663
+					$global_tax->set_total($new_total);
664
+					$global_tax->maybe_save();
665
+					$tax_total += $non_global_tax['total'];
666
+				}
667
+			}
668
+			if (! $found) {
669
+				// add a new line item for this non global tax
670
+				$taxes_subtotal->add_child_line_item(
671
+					EE_Line_Item::new_instance(
672
+						[
673
+							'LIN_name'       => $non_global_tax['name'],
674
+							'LIN_percent'    => $non_global_tax['rate'],
675
+							'LIN_is_taxable' => false,
676
+							'LIN_total'      => $non_global_tax['total'],
677
+							'LIN_type'       => EEM_Line_Item::type_tax,
678
+							'OBJ_type'       => $non_global_tax['obj'],
679
+							'OBJ_ID'         => $non_global_tax['objID'],
680
+						]
681
+					)
682
+				);
683
+				$tax_total += $non_global_tax['total'];
684
+			}
685
+		}
686
+		return $this->decimal_values->roundDecimalValue($tax_total, true);
687
+	}
688
+
689
+
690
+	/**
691
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
692
+	 * recalculate_taxes_and_total
693
+	 *
694
+	 * @param EE_Line_Item $line_item
695
+	 * @return float
696
+	 * @throws EE_Error
697
+	 * @throws ReflectionException
698
+	 */
699
+	public function getTotalTax(EE_Line_Item $line_item): float
700
+	{
701
+		$this->validateLineItemAndType($line_item, EEM_Line_Item::type_total);
702
+		$this->recalculateTaxSubTotal($line_item);
703
+		$total = 0;
704
+		foreach ($line_item->tax_descendants() as $tax_line_item) {
705
+			if ($this->validateLineItemAndType($tax_line_item)) {
706
+				$total += $tax_line_item->total();
707
+			}
708
+		}
709
+		return $this->decimal_values->roundDecimalValue($total, true);
710
+	}
711
+
712
+
713
+	/**
714
+	 * Returns the amount taxable among this line item's children (or if it has no children,
715
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
716
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
717
+	 * but there is a "Taxable" discount), returns 0.
718
+	 *
719
+	 * @param EE_Line_Item|null $line_item
720
+	 * @return float
721
+	 * @throws EE_Error
722
+	 * @throws ReflectionException
723
+	 */
724
+	public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
725
+	{
726
+		$total      = 0;
727
+		$child_line_items = $line_item->children($this->default_query_params);
728
+		foreach ($child_line_items as $child_line_item) {
729
+			$this->validateLineItemAndType($child_line_item);
730
+			if ($child_line_item->is_sub_total()) {
731
+				$total += $this->taxableAmountForGlobalTaxes($child_line_item);
732
+			} elseif ($child_line_item->is_line_item() && $child_line_item->is_taxable()) {
733
+				// if it's a percent item, only take into account
734
+				// the percentage that's taxable (the taxable total so far)
735
+				if ($child_line_item->is_percent()) {
736
+					$total += $this->calculatePercentage($total, $child_line_item->percent(), true);
737
+				} else {
738
+					// pretax total will be equal to the total for line items with globally applied taxes
739
+					$pretax_total = $this->calculateTotalForQuantity($child_line_item);
740
+					$total += $this->updatePreTaxTotal($child_line_item, $pretax_total);
741
+				}
742
+			}
743
+		}
744
+		return max($total, 0);
745
+	}
746
+
747
+
748
+	/**
749
+	 * @param EE_Line_Item|null $line_item
750
+	 * @param string|null       $type
751
+	 * @return bool
752
+	 * @throws EE_Error
753
+	 * @throws ReflectionException
754
+	 */
755
+	private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
756
+	{
757
+		if (! $line_item instanceof EE_Line_Item) {
758
+			throw new DomainException(
759
+				esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
760
+			);
761
+		}
762
+		if ($type && $line_item->type() !== $type) {
763
+			throw new DomainException(
764
+				sprintf(
765
+					esc_html__(
766
+						'Invalid Line Item type supplied. Received "%1$s" but expected "%2$s".',
767
+						'event_espresso'
768
+					),
769
+					$line_item->type(),
770
+					$type
771
+				)
772
+			);
773
+		}
774
+		return true;
775
+	}
776 776
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
         // for the actual pre-tax sub total line item, we want to save the pretax value for everything
170 170
         if ($line_item->is_sub_total() && $line_item->name() === esc_html__('Pre-Tax Subtotal', 'event_espresso')) {
171 171
             $this->updateTotal($line_item, $pretax_total, true);
172
-        } elseif (! $line_item->is_total()) {
172
+        } elseif ( ! $line_item->is_total()) {
173 173
             // we don't update the total for the total line item, because that will need to include taxes
174 174
             $total = $this->updateTotal($line_item, $total, true);
175 175
         }
@@ -201,7 +201,7 @@  discard block
 block discarded – undo
201 201
         } else {
202 202
             // recursively loop through children and recalculate their totals
203 203
             $children = $line_item->children($this->default_query_params);
204
-            if (! empty($children)) {
204
+            if ( ! empty($children)) {
205 205
                 // reset the total and pretax total to zero since we are recalculating them
206 206
                 $total = $pretax_total = 0;
207 207
                 foreach ($children as $child_line_item) {
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
                 // echo "{$this->lb}     = li total: {$total} ({$line_item->name()})";
226 226
             }
227 227
         }
228
-        $total  = $this->updateTotal($line_item, $total, true);
228
+        $total = $this->updateTotal($line_item, $total, true);
229 229
         $pretax_total = $this->updatePreTaxTotal($line_item, $pretax_total, true);
230 230
 
231 231
         // need to also adjust unit price too if the pretax total or quantity has been updated
@@ -522,7 +522,7 @@  discard block
 block discarded – undo
522 522
         $taxable_total = $this->taxableAmountForGlobalTaxes($total_line_item);
523 523
         $global_taxes     = $this->applyGlobalTaxes($total_line_item, $taxable_total);
524 524
         $non_global_taxes = $this->calculateNonGlobalTaxes($total_line_item);
525
-        $all_tax_total        = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
525
+        $all_tax_total = $this->applyNonGlobalTaxes($total_line_item, $global_taxes, $non_global_taxes);
526 526
         $this->recalculateTaxSubTotal($total_line_item);
527 527
         return $all_tax_total;
528 528
     }
@@ -615,9 +615,9 @@  discard block
 block discarded – undo
615 615
                         $line_item->pretaxTotal()
616 616
                     );
617 617
                 } elseif ($line_item->isSubTax()) {
618
-                    $tax_ID = $line_item->name() . '_' . $line_item->percent();
619
-                    if (! isset($non_global_taxes[ $tax_ID ])) {
620
-                        $non_global_taxes[ $tax_ID ] = [
618
+                    $tax_ID = $line_item->name().'_'.$line_item->percent();
619
+                    if ( ! isset($non_global_taxes[$tax_ID])) {
620
+                        $non_global_taxes[$tax_ID] = [
621 621
                             'name'  => $line_item->name(),
622 622
                             'rate'  => $line_item->percent(),
623 623
                             'total' => 0,
@@ -626,7 +626,7 @@  discard block
 block discarded – undo
626 626
                         ];
627 627
                     }
628 628
                     $tax = $this->calculatePercentage($line_item_total, $line_item->percent());
629
-                    $non_global_taxes[ $tax_ID ]['total'] += $tax;
629
+                    $non_global_taxes[$tax_ID]['total'] += $tax;
630 630
                 }
631 631
             }
632 632
         }
@@ -665,7 +665,7 @@  discard block
 block discarded – undo
665 665
                     $tax_total += $non_global_tax['total'];
666 666
                 }
667 667
             }
668
-            if (! $found) {
668
+            if ( ! $found) {
669 669
                 // add a new line item for this non global tax
670 670
                 $taxes_subtotal->add_child_line_item(
671 671
                     EE_Line_Item::new_instance(
@@ -723,7 +723,7 @@  discard block
 block discarded – undo
723 723
      */
724 724
     public function taxableAmountForGlobalTaxes(?EE_Line_Item $line_item): float
725 725
     {
726
-        $total      = 0;
726
+        $total = 0;
727 727
         $child_line_items = $line_item->children($this->default_query_params);
728 728
         foreach ($child_line_items as $child_line_item) {
729 729
             $this->validateLineItemAndType($child_line_item);
@@ -754,7 +754,7 @@  discard block
 block discarded – undo
754 754
      */
755 755
     private function validateLineItemAndType(?EE_Line_Item $line_item, ?string $type = null): bool
756 756
     {
757
-        if (! $line_item instanceof EE_Line_Item) {
757
+        if ( ! $line_item instanceof EE_Line_Item) {
758 758
             throw new DomainException(
759 759
                 esc_html__('Invalid or missing Line Item supplied .', 'event_espresso')
760 760
             );
Please login to merge, or discard this patch.
line_item_display/EE_Default_Line_Item_Display_Strategy.strategy.php 1 patch
Indentation   +241 added lines, -241 removed lines patch added patch discarded remove patch
@@ -1,248 +1,248 @@
 block discarded – undo
1 1
 <?php
2 2
 
3 3
  /**
4
- *
5
- * Class EE_Default_Line_Item_Display_Strategy
6
- *
7
- * Description
8
- *
9
- * @package         Event Espresso
10
- * @subpackage    core
11
- * @author              Brent Christensen
12
- *
13
- *
14
- */
4
+  *
5
+  * Class EE_Default_Line_Item_Display_Strategy
6
+  *
7
+  * Description
8
+  *
9
+  * @package         Event Espresso
10
+  * @subpackage    core
11
+  * @author              Brent Christensen
12
+  *
13
+  *
14
+  */
15 15
 
16 16
 class EE_Default_Line_Item_Display_Strategy implements EEI_Line_Item_Display
17 17
 {
18
-    /**
19
-     * @var bool
20
-     */
21
-    protected $prices_include_taxes = false;
22
-
23
-    /**
24
-     * total amount of tax to apply
25
-     * @type float $_tax_rate
26
-     */
27
-    private $_tax_rate = 0;
28
-
29
-    /**
30
-     * total amount including tax we can bill for at this time
31
-     * @type float $_grand_total
32
-     */
33
-    private $_grand_total = 0.00;
34
-
35
-    /**
36
-     * total number of items being billed for
37
-     * @type int $_total_items
38
-     */
39
-    private $_total_items = 0;
40
-
41
-
42
-    public function __construct()
43
-    {
44
-        $this->prices_include_taxes = EE_Registry::instance()->CFG->tax_settings->prices_displayed_including_taxes;
45
-    }
46
-
47
-
48
-    /**
49
-     * @return float
50
-     */
51
-    public function grand_total()
52
-    {
53
-        return $this->_grand_total;
54
-    }
55
-
56
-
57
-
58
-    /**
59
-     * @return int
60
-     */
61
-    public function total_items()
62
-    {
63
-        return $this->_total_items;
64
-    }
65
-
66
-
67
-
68
-    /**
69
-     * @param EE_Line_Item $line_item
70
-     * @param array        $options
71
-     * @return mixed
72
-     */
73
-    public function display_line_item(EE_Line_Item $line_item, $options = array())
74
-    {
75
-
76
-        $html = '';
77
-        // set some default options and merge with incoming
78
-        $default_options = array(
79
-            'show_desc' => true,  //    TRUE        FALSE
80
-            'odd' => false
81
-        );
82
-        $options = array_merge($default_options, (array) $options);
83
-
84
-        switch ($line_item->type()) {
85
-            case EEM_Line_Item::type_line_item:
86
-                // item row
87
-                $html .= $this->_item_row($line_item, $options);
88
-                // got any kids?
89
-                foreach ($line_item->children() as $child_line_item) {
90
-                    $this->display_line_item($child_line_item, $options);
91
-                }
92
-                break;
93
-
94
-            case EEM_Line_Item::type_sub_line_item:
95
-                $html .= $this->_sub_item_row($line_item, $options);
96
-                break;
97
-
98
-            case EEM_Line_Item::type_sub_total:
99
-                break;
100
-
101
-            case EEM_Line_Item::type_tax:
102
-                $this->_tax_rate += $line_item->percent();
103
-                break;
104
-
105
-            case EEM_Line_Item::type_tax_sub_total:
106
-                foreach ($line_item->children() as $child_line_item) {
107
-                    if ($child_line_item->type() == EEM_Line_Item::type_tax) {
108
-                        // recursively feed children back into this method
109
-                        $this->display_line_item($child_line_item, $options);
110
-                    }
111
-                }
112
-                break;
113
-
114
-            case EEM_Line_Item::type_total:
115
-                // get all child line items
116
-                $children = $line_item->children();
117
-                if ($options['set_tax_rate'] === true) {
118
-                    // loop thru tax child line items just to determine tax rate
119
-                    foreach ($children as $child_line_item) {
120
-                        if ($child_line_item->type() == EEM_Line_Item::type_tax_sub_total) {
121
-                            // recursively feed children back into this method
122
-                            $this->display_line_item($child_line_item, $options);
123
-                        }
124
-                    }
125
-                } else {
126
-                    // now loop thru all non-tax child line items
127
-                    foreach ($children as $child_line_item) {
128
-                        if ($child_line_item->type() != EEM_Line_Item::type_tax_sub_total) {
129
-                            // recursively feed children back into this method
130
-                            $html .= $this->display_line_item($child_line_item, $options);
131
-                        }
132
-                    }
133
-                }
134
-                break;
135
-        }
136
-
137
-        return $html;
138
-    }
139
-
140
-
141
-    /**
142
-     *  _total_row
143
-     *
144
-     * @param EE_Line_Item $line_item
145
-     * @param array        $options
146
-     * @return string
147
-     * @throws EE_Error
148
-     * @throws ReflectionException
149
-     */
150
-    private function _item_row(EE_Line_Item $line_item, array $options = array()): string
151
-    {
152
-        $sub_taxes = $line_item->getSubTaxes();
153
-        $has_sub_taxes = ! empty($sub_taxes);
154
-        $is_taxable = $line_item->is_taxable();
155
-
156
-        $row_class = $options['odd'] ? 'item odd' : 'item';
157
-        $html = EEH_HTML::tr('', '', $row_class);
158
-        // name && desc
159
-        $name_and_desc = apply_filters(
160
-            'FHEE__EE_Default_Line_Item_Display_Strategy__item_row__name',
161
-            $line_item->name(),
162
-            $line_item
163
-        );
164
-        $name_and_desc .= apply_filters(
165
-            'FHEE__EE_Default_Line_Item_Display_Strategy__item_row__desc',
166
-            ( $options['show_desc'] ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>' : '' ),
167
-            $line_item,
168
-            $options
169
-        );
170
-        if ($is_taxable || $has_sub_taxes) {
171
-            $includes_taxes = $this->prices_include_taxes
172
-                ? esc_html__('* price includes taxes', 'event_espresso')
173
-                : esc_html__('* price does not include taxes', 'event_espresso');
174
-            $name_and_desc .= '<br /><span class="smaller-text grey-text">' . $includes_taxes . '</span>';
175
-        }
176
-
177
-        // name td
178
-        $html .= EEH_HTML::td($name_and_desc, '', 'item_l');
179
-        // quantity td
180
-        $html .= EEH_HTML::td($line_item->quantity(), '', 'item_l jst-rght');
181
-        $tax_rate = 1;
182
-        if ($this->prices_include_taxes) {
183
-            if ($has_sub_taxes) {
184
-                $tax_rate = 0;
185
-                foreach ($sub_taxes as $sub_tax) {
186
-                    if ($sub_tax instanceof EE_Line_Item && $sub_tax->isSubTax()) {
187
-                        $tax_rate += $sub_tax->percent();
188
-                    }
189
-                }
190
-                $tax_rate = 1 + $tax_rate / 100;
191
-            } elseif ($is_taxable && $this->prices_include_taxes) {
192
-                $tax_rate = 1 + ($this->_tax_rate / 100);
193
-            }
194
-        }
195
-
196
-        // price td
197
-        $unit_price = apply_filters(
198
-            'FHEE__EE_Default_Line_Item_Display_Strategy___item_row__unit_price',
199
-            EEH_Template::format_currency($line_item->unit_price() * $tax_rate, false, false),
200
-            $line_item,
201
-            $tax_rate
202
-        );
203
-        $html .= EEH_HTML::td($unit_price, '', 'item_c jst-rght');
204
-        // total td
205
-        $total = apply_filters(
206
-            'FHEE__EE_Default_Line_Item_Display_Strategy___item_row__total',
207
-            EEH_Template::format_currency($line_item->unit_price() * $line_item->quantity() * $tax_rate, false, false),
208
-            $line_item,
209
-            $tax_rate
210
-        );
211
-        $html .= EEH_HTML::td($total, '', 'item_r jst-rght');
212
-        // end of row
213
-        $html .= EEH_HTML::trx();
214
-
215
-        return $html;
216
-    }
217
-
218
-
219
-
220
-    /**
221
-     *  _sub_item_row
222
-     *
223
-     * @param EE_Line_Item $line_item
224
-     * @param array        $options
225
-     * @return mixed
226
-     */
227
-    private function _sub_item_row(EE_Line_Item $line_item, $options = array())
228
-    {
229
-        // start of row
230
-        $html = EEH_HTML::tr('', 'item sub-item-row');
231
-        // name && desc
232
-        $name_and_desc = $line_item->name();
233
-        $name_and_desc .= $options['show_desc'] ? '<span class="line-sub-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>' : '';
234
-        // name td
235
-        $html .= EEH_HTML::td(/*__FUNCTION__ .*/ $name_and_desc, '', 'item_l sub-item');
236
-        // discount/surcharge td
237
-        if ($line_item->is_percent()) {
238
-            $html .= EEH_HTML::td($line_item->percent() . '%', '', 'item_c');
239
-        } else {
240
-            $html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'item_c jst-rght');
241
-        }
242
-        // total td
243
-        $html .= EEH_HTML::td(EEH_Template::format_currency($line_item->total(), false, false), '', 'item_r jst-rght');
244
-        // end of row
245
-        $html .= EEH_HTML::trx();
246
-        return $html;
247
-    }
18
+	/**
19
+	 * @var bool
20
+	 */
21
+	protected $prices_include_taxes = false;
22
+
23
+	/**
24
+	 * total amount of tax to apply
25
+	 * @type float $_tax_rate
26
+	 */
27
+	private $_tax_rate = 0;
28
+
29
+	/**
30
+	 * total amount including tax we can bill for at this time
31
+	 * @type float $_grand_total
32
+	 */
33
+	private $_grand_total = 0.00;
34
+
35
+	/**
36
+	 * total number of items being billed for
37
+	 * @type int $_total_items
38
+	 */
39
+	private $_total_items = 0;
40
+
41
+
42
+	public function __construct()
43
+	{
44
+		$this->prices_include_taxes = EE_Registry::instance()->CFG->tax_settings->prices_displayed_including_taxes;
45
+	}
46
+
47
+
48
+	/**
49
+	 * @return float
50
+	 */
51
+	public function grand_total()
52
+	{
53
+		return $this->_grand_total;
54
+	}
55
+
56
+
57
+
58
+	/**
59
+	 * @return int
60
+	 */
61
+	public function total_items()
62
+	{
63
+		return $this->_total_items;
64
+	}
65
+
66
+
67
+
68
+	/**
69
+	 * @param EE_Line_Item $line_item
70
+	 * @param array        $options
71
+	 * @return mixed
72
+	 */
73
+	public function display_line_item(EE_Line_Item $line_item, $options = array())
74
+	{
75
+
76
+		$html = '';
77
+		// set some default options and merge with incoming
78
+		$default_options = array(
79
+			'show_desc' => true,  //    TRUE        FALSE
80
+			'odd' => false
81
+		);
82
+		$options = array_merge($default_options, (array) $options);
83
+
84
+		switch ($line_item->type()) {
85
+			case EEM_Line_Item::type_line_item:
86
+				// item row
87
+				$html .= $this->_item_row($line_item, $options);
88
+				// got any kids?
89
+				foreach ($line_item->children() as $child_line_item) {
90
+					$this->display_line_item($child_line_item, $options);
91
+				}
92
+				break;
93
+
94
+			case EEM_Line_Item::type_sub_line_item:
95
+				$html .= $this->_sub_item_row($line_item, $options);
96
+				break;
97
+
98
+			case EEM_Line_Item::type_sub_total:
99
+				break;
100
+
101
+			case EEM_Line_Item::type_tax:
102
+				$this->_tax_rate += $line_item->percent();
103
+				break;
104
+
105
+			case EEM_Line_Item::type_tax_sub_total:
106
+				foreach ($line_item->children() as $child_line_item) {
107
+					if ($child_line_item->type() == EEM_Line_Item::type_tax) {
108
+						// recursively feed children back into this method
109
+						$this->display_line_item($child_line_item, $options);
110
+					}
111
+				}
112
+				break;
113
+
114
+			case EEM_Line_Item::type_total:
115
+				// get all child line items
116
+				$children = $line_item->children();
117
+				if ($options['set_tax_rate'] === true) {
118
+					// loop thru tax child line items just to determine tax rate
119
+					foreach ($children as $child_line_item) {
120
+						if ($child_line_item->type() == EEM_Line_Item::type_tax_sub_total) {
121
+							// recursively feed children back into this method
122
+							$this->display_line_item($child_line_item, $options);
123
+						}
124
+					}
125
+				} else {
126
+					// now loop thru all non-tax child line items
127
+					foreach ($children as $child_line_item) {
128
+						if ($child_line_item->type() != EEM_Line_Item::type_tax_sub_total) {
129
+							// recursively feed children back into this method
130
+							$html .= $this->display_line_item($child_line_item, $options);
131
+						}
132
+					}
133
+				}
134
+				break;
135
+		}
136
+
137
+		return $html;
138
+	}
139
+
140
+
141
+	/**
142
+	 *  _total_row
143
+	 *
144
+	 * @param EE_Line_Item $line_item
145
+	 * @param array        $options
146
+	 * @return string
147
+	 * @throws EE_Error
148
+	 * @throws ReflectionException
149
+	 */
150
+	private function _item_row(EE_Line_Item $line_item, array $options = array()): string
151
+	{
152
+		$sub_taxes = $line_item->getSubTaxes();
153
+		$has_sub_taxes = ! empty($sub_taxes);
154
+		$is_taxable = $line_item->is_taxable();
155
+
156
+		$row_class = $options['odd'] ? 'item odd' : 'item';
157
+		$html = EEH_HTML::tr('', '', $row_class);
158
+		// name && desc
159
+		$name_and_desc = apply_filters(
160
+			'FHEE__EE_Default_Line_Item_Display_Strategy__item_row__name',
161
+			$line_item->name(),
162
+			$line_item
163
+		);
164
+		$name_and_desc .= apply_filters(
165
+			'FHEE__EE_Default_Line_Item_Display_Strategy__item_row__desc',
166
+			( $options['show_desc'] ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>' : '' ),
167
+			$line_item,
168
+			$options
169
+		);
170
+		if ($is_taxable || $has_sub_taxes) {
171
+			$includes_taxes = $this->prices_include_taxes
172
+				? esc_html__('* price includes taxes', 'event_espresso')
173
+				: esc_html__('* price does not include taxes', 'event_espresso');
174
+			$name_and_desc .= '<br /><span class="smaller-text grey-text">' . $includes_taxes . '</span>';
175
+		}
176
+
177
+		// name td
178
+		$html .= EEH_HTML::td($name_and_desc, '', 'item_l');
179
+		// quantity td
180
+		$html .= EEH_HTML::td($line_item->quantity(), '', 'item_l jst-rght');
181
+		$tax_rate = 1;
182
+		if ($this->prices_include_taxes) {
183
+			if ($has_sub_taxes) {
184
+				$tax_rate = 0;
185
+				foreach ($sub_taxes as $sub_tax) {
186
+					if ($sub_tax instanceof EE_Line_Item && $sub_tax->isSubTax()) {
187
+						$tax_rate += $sub_tax->percent();
188
+					}
189
+				}
190
+				$tax_rate = 1 + $tax_rate / 100;
191
+			} elseif ($is_taxable && $this->prices_include_taxes) {
192
+				$tax_rate = 1 + ($this->_tax_rate / 100);
193
+			}
194
+		}
195
+
196
+		// price td
197
+		$unit_price = apply_filters(
198
+			'FHEE__EE_Default_Line_Item_Display_Strategy___item_row__unit_price',
199
+			EEH_Template::format_currency($line_item->unit_price() * $tax_rate, false, false),
200
+			$line_item,
201
+			$tax_rate
202
+		);
203
+		$html .= EEH_HTML::td($unit_price, '', 'item_c jst-rght');
204
+		// total td
205
+		$total = apply_filters(
206
+			'FHEE__EE_Default_Line_Item_Display_Strategy___item_row__total',
207
+			EEH_Template::format_currency($line_item->unit_price() * $line_item->quantity() * $tax_rate, false, false),
208
+			$line_item,
209
+			$tax_rate
210
+		);
211
+		$html .= EEH_HTML::td($total, '', 'item_r jst-rght');
212
+		// end of row
213
+		$html .= EEH_HTML::trx();
214
+
215
+		return $html;
216
+	}
217
+
218
+
219
+
220
+	/**
221
+	 *  _sub_item_row
222
+	 *
223
+	 * @param EE_Line_Item $line_item
224
+	 * @param array        $options
225
+	 * @return mixed
226
+	 */
227
+	private function _sub_item_row(EE_Line_Item $line_item, $options = array())
228
+	{
229
+		// start of row
230
+		$html = EEH_HTML::tr('', 'item sub-item-row');
231
+		// name && desc
232
+		$name_and_desc = $line_item->name();
233
+		$name_and_desc .= $options['show_desc'] ? '<span class="line-sub-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>' : '';
234
+		// name td
235
+		$html .= EEH_HTML::td(/*__FUNCTION__ .*/ $name_and_desc, '', 'item_l sub-item');
236
+		// discount/surcharge td
237
+		if ($line_item->is_percent()) {
238
+			$html .= EEH_HTML::td($line_item->percent() . '%', '', 'item_c');
239
+		} else {
240
+			$html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'item_c jst-rght');
241
+		}
242
+		// total td
243
+		$html .= EEH_HTML::td(EEH_Template::format_currency($line_item->total(), false, false), '', 'item_r jst-rght');
244
+		// end of row
245
+		$html .= EEH_HTML::trx();
246
+		return $html;
247
+	}
248 248
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Debug_Tools.helper.php 2 patches
Indentation   +705 added lines, -705 removed lines patch added patch discarded remove patch
@@ -13,699 +13,699 @@  discard block
 block discarded – undo
13 13
 class EEH_Debug_Tools
14 14
 {
15 15
 
16
-    /**
17
-     *    instance of the EEH_Autoloader object
18
-     *
19
-     * @var    $_instance
20
-     * @access    private
21
-     */
22
-    private static $_instance;
23
-
24
-    /**
25
-     * @var array
26
-     */
27
-    protected $_memory_usage_points = array();
28
-
29
-
30
-
31
-    /**
32
-     * @singleton method used to instantiate class object
33
-     * @access    public
34
-     * @return EEH_Debug_Tools
35
-     */
36
-    public static function instance()
37
-    {
38
-        // check if class object is instantiated, and instantiated properly
39
-        if (! self::$_instance instanceof EEH_Debug_Tools) {
40
-            self::$_instance = new self();
41
-        }
42
-        return self::$_instance;
43
-    }
44
-
45
-
46
-
47
-    /**
48
-     * private class constructor
49
-     */
50
-    private function __construct()
51
-    {
52
-        // load Kint PHP debugging library
53
-        if (! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')) {
54
-            // despite EE4 having a check for an existing copy of the Kint debugging class,
55
-            // if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
56
-            // then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
57
-            // so we've moved it to our test folder so that it is not included with production releases
58
-            // plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
59
-            require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
60
-        }
61
-        // if ( ! defined('DOING_AJAX') || $_REQUEST['noheader'] !== 'true' || ! isset( $_REQUEST['noheader'], $_REQUEST['TB_iframe'] ) ) {
62
-        // add_action( 'shutdown', array($this,'espresso_session_footer_dump') );
63
-        // }
64
-        $plugin = basename(EE_PLUGIN_DIR_PATH);
65
-        add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
66
-        add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
67
-        add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
68
-    }
69
-
70
-
71
-
72
-    /**
73
-     *    show_db_name
74
-     *
75
-     * @return void
76
-     */
77
-    public static function show_db_name()
78
-    {
79
-        if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
80
-            echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
81
-                 . DB_NAME
82
-                 . '</p>';
83
-        }
84
-        if (EE_DEBUG) {
85
-            Benchmark::displayResults();
86
-        }
87
-    }
88
-
89
-
90
-
91
-    /**
92
-     *    dump EE_Session object at bottom of page after everything else has happened
93
-     *
94
-     * @return void
95
-     */
96
-    public function espresso_session_footer_dump()
97
-    {
98
-        if (
99
-            (defined('WP_DEBUG') && WP_DEBUG)
100
-            && ! defined('DOING_AJAX')
101
-            && class_exists('Kint')
102
-            && function_exists('wp_get_current_user')
103
-            && current_user_can('update_core')
104
-            && class_exists('EE_Registry')
105
-        ) {
106
-            Kint::dump(EE_Registry::instance()->SSN->id());
107
-            Kint::dump(EE_Registry::instance()->SSN);
108
-            //          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
109
-            $this->espresso_list_hooked_functions();
110
-            Benchmark::displayResults();
111
-        }
112
-    }
113
-
114
-
115
-
116
-    /**
117
-     *    List All Hooked Functions
118
-     *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
119
-     *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
120
-     *
121
-     * @param string $tag
122
-     * @return void
123
-     */
124
-    public function espresso_list_hooked_functions($tag = '')
125
-    {
126
-        global $wp_filter;
127
-        echo '<br/><br/><br/><h3>Hooked Functions</h3>';
128
-        if ($tag) {
129
-            $hook[ $tag ] = $wp_filter[ $tag ];
130
-            if (! is_array($hook[ $tag ])) {
131
-                trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
132
-                return;
133
-            }
134
-            echo '<h5>For Tag: ' . $tag . '</h5>';
135
-        } else {
136
-            $hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
137
-            ksort($hook);
138
-        }
139
-        foreach ($hook as $tag_name => $priorities) {
140
-            echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>$tag_name</strong><br />";
141
-            ksort($priorities);
142
-            foreach ($priorities as $priority => $function) {
143
-                echo $priority;
144
-                foreach ($function as $name => $properties) {
145
-                    echo "\t$name<br />";
146
-                }
147
-            }
148
-        }
149
-    }
150
-
151
-
152
-
153
-    /**
154
-     *    registered_filter_callbacks
155
-     *
156
-     * @param string $hook_name
157
-     * @return array
158
-     */
159
-    public static function registered_filter_callbacks($hook_name = '')
160
-    {
161
-        $filters = array();
162
-        global $wp_filter;
163
-        if (isset($wp_filter[ $hook_name ])) {
164
-            $filters[ $hook_name ] = array();
165
-            foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
166
-                $filters[ $hook_name ][ $priority ] = array();
167
-                foreach ($callbacks as $callback) {
168
-                    $filters[ $hook_name ][ $priority ][] = $callback['function'];
169
-                }
170
-            }
171
-        }
172
-        return $filters;
173
-    }
174
-
175
-
176
-
177
-    /**
178
-     *    captures plugin activation errors for debugging
179
-     *
180
-     * @return void
181
-     * @throws EE_Error
182
-     */
183
-    public static function ee_plugin_activation_errors()
184
-    {
185
-        if (WP_DEBUG) {
186
-            $activation_errors = ob_get_contents();
187
-            if (empty($activation_errors)) {
188
-                return;
189
-            }
190
-            $activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
191
-            espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
192
-            if (class_exists('EEH_File')) {
193
-                try {
194
-                    EEH_File::ensure_file_exists_and_is_writable(
195
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
196
-                    );
197
-                    EEH_File::write_to_file(
198
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
199
-                        $activation_errors
200
-                    );
201
-                } catch (EE_Error $e) {
202
-                    EE_Error::add_error(
203
-                        sprintf(
204
-                            __(
205
-                                'The Event Espresso activation errors file could not be setup because: %s',
206
-                                'event_espresso'
207
-                            ),
208
-                            $e->getMessage()
209
-                        ),
210
-                        __FILE__,
211
-                        __FUNCTION__,
212
-                        __LINE__
213
-                    );
214
-                }
215
-            } else {
216
-                // old school attempt
217
-                file_put_contents(
218
-                    EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
219
-                    $activation_errors
220
-                );
221
-            }
222
-            $activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
223
-            update_option('ee_plugin_activation_errors', $activation_errors);
224
-        }
225
-    }
226
-
227
-
228
-
229
-    /**
230
-     * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
231
-     * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
232
-     * or we want to make sure they use something the right way.
233
-     *
234
-     * @access public
235
-     * @param string $function      The function that was called
236
-     * @param string $message       A message explaining what has been done incorrectly
237
-     * @param string $version       The version of Event Espresso where the error was added
238
-     * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
239
-     *                              for a deprecated function. This allows deprecation to occur during one version,
240
-     *                              but not have any notices appear until a later version. This allows developers
241
-     *                              extra time to update their code before notices appear.
242
-     * @param int    $error_type
243
-     * @uses   trigger_error()
244
-     */
245
-    public function doing_it_wrong(
246
-        $function,
247
-        $message,
248
-        $version,
249
-        $applies_when = '',
250
-        $error_type = null
251
-    ) {
252
-        $applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
253
-        $error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
254
-        // because we swapped the parameter order around for the last two params,
255
-        // let's verify that some third party isn't still passing an error type value for the third param
256
-        if (is_int($applies_when)) {
257
-            $error_type = $applies_when;
258
-            $applies_when = espresso_version();
259
-        }
260
-        // if not displaying notices yet, then just leave
261
-        if (version_compare(espresso_version(), $applies_when, '<')) {
262
-            return;
263
-        }
264
-        do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
265
-        $version = $version === null
266
-            ? ''
267
-            : sprintf(
268
-                __('(This message was added in version %s of Event Espresso)', 'event_espresso'),
269
-                $version
270
-            );
271
-        $error_message = sprintf(
272
-            esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
273
-            $function,
274
-            '<strong>',
275
-            '</strong>',
276
-            $message,
277
-            $version
278
-        );
279
-        // don't trigger error if doing ajax,
280
-        // instead we'll add a transient EE_Error notice that in theory should show on the next request.
281
-        if (defined('DOING_AJAX') && DOING_AJAX) {
282
-            $error_message .= ' ' . esc_html__(
283
-                'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
284
-                'event_espresso'
285
-            );
286
-            $error_message .= '<ul><li>';
287
-            $error_message .= implode('</li><li>', EE_Registry::instance()->REQ->params());
288
-            $error_message .= '</ul>';
289
-            EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
290
-            // now we set this on the transient so it shows up on the next request.
291
-            EE_Error::get_notices(false, true);
292
-        } else {
293
-            trigger_error($error_message, $error_type);
294
-        }
295
-    }
296
-
297
-
298
-
299
-
300
-    /**
301
-     * Logger helpers
302
-     */
303
-    /**
304
-     * debug
305
-     *
306
-     * @param string $class
307
-     * @param string $func
308
-     * @param string $line
309
-     * @param array  $info
310
-     * @param bool   $display_request
311
-     * @param string $debug_index
312
-     * @param string $debug_key
313
-     * @throws EE_Error
314
-     * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
315
-     */
316
-    public static function log(
317
-        $class = '',
318
-        $func = '',
319
-        $line = '',
320
-        $info = array(),
321
-        $display_request = false,
322
-        $debug_index = '',
323
-        $debug_key = 'EE_DEBUG_SPCO'
324
-    ) {
325
-        if (WP_DEBUG) {
326
-            $debug_key = $debug_key . '_' . EE_Session::instance()->id();
327
-            $debug_data = get_option($debug_key, array());
328
-            $default_data = array(
329
-                $class => $func . '() : ' . $line,
330
-                'REQ'  => $display_request ? $_REQUEST : '',
331
-            );
332
-            // don't serialize objects
333
-            $info = self::strip_objects($info);
334
-            $index = ! empty($debug_index) ? $debug_index : 0;
335
-            if (! isset($debug_data[ $index ])) {
336
-                $debug_data[ $index ] = array();
337
-            }
338
-            $debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
339
-            update_option($debug_key, $debug_data);
340
-        }
341
-    }
342
-
343
-
344
-
345
-    /**
346
-     * strip_objects
347
-     *
348
-     * @param array $info
349
-     * @return array
350
-     */
351
-    public static function strip_objects($info = array())
352
-    {
353
-        foreach ($info as $key => $value) {
354
-            if (is_array($value)) {
355
-                $info[ $key ] = self::strip_objects($value);
356
-            } elseif (is_object($value)) {
357
-                $object_class = get_class($value);
358
-                $info[ $object_class ] = array();
359
-                $info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
360
-                if (method_exists($value, 'ID')) {
361
-                    $info[ $object_class ]['ID'] = $value->ID();
362
-                }
363
-                if (method_exists($value, 'status')) {
364
-                    $info[ $object_class ]['status'] = $value->status();
365
-                } elseif (method_exists($value, 'status_ID')) {
366
-                    $info[ $object_class ]['status'] = $value->status_ID();
367
-                }
368
-                unset($info[ $key ]);
369
-            }
370
-        }
371
-        return (array) $info;
372
-    }
373
-
374
-
375
-
376
-    /**
377
-     * @param mixed      $var
378
-     * @param string     $var_name
379
-     * @param string     $file
380
-     * @param int|string $line
381
-     * @param int|string $heading_tag
382
-     * @param bool       $die
383
-     * @param string     $margin
384
-     */
385
-    public static function printv(
386
-        $var,
387
-        $var_name = '',
388
-        $file = '',
389
-        $line = '',
390
-        $heading_tag = 5,
391
-        $die = false,
392
-        $margin = ''
393
-    ) {
394
-        $var_name = ! $var_name ? 'string' : $var_name;
395
-        $var_name = ucwords(str_replace('$', '', $var_name));
396
-        $is_method = method_exists($var_name, $var);
397
-        $var_name = ucwords(str_replace('_', ' ', $var_name));
398
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
399
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
400
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
401
-        $result .= $is_method
402
-            ? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
403
-            : EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
404
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
405
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
406
-        if ($die) {
407
-            die($result);
408
-        }
409
-        echo $result;
410
-    }
411
-
412
-
413
-    protected static function headingTag($heading_tag)
414
-    {
415
-        $heading_tag = absint($heading_tag);
416
-        return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
417
-    }
418
-
419
-
420
-    protected static function headingSpacer($heading_tag)
421
-    {
422
-        return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
423
-            ? "\n"
424
-            : '';
425
-    }
426
-
427
-
428
-    protected static function plainOutput()
429
-    {
430
-        return defined('EE_TESTS_DIR')
431
-               || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
432
-               || (
433
-                   isset($_SERVER['REQUEST_URI'])
434
-                   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
435
-               );
436
-    }
437
-
438
-
439
-    /**
440
-     * @param string $var_name
441
-     * @param string $heading_tag
442
-     * @param string $margin
443
-     * @param int    $line
444
-     * @return string
445
-     */
446
-    protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
447
-    {
448
-        if (EEH_Debug_Tools::plainOutput()) {
449
-            switch ($heading_tag) {
450
-                case 'h1':
451
-                    $line_breaks = EEH_Debug_Tools::lineBreak(3);
452
-                    break;
453
-                case 'h2':
454
-                    $line_breaks = EEH_Debug_Tools::lineBreak(2);
455
-                    break;
456
-                default:
457
-                    $line_breaks = EEH_Debug_Tools::lineBreak();
458
-                    break;
459
-            }
460
-            return "{$line_breaks}{$line}) {$var_name}";
461
-        }
462
-        $margin = "25px 0 0 {$margin}";
463
-        return '<' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . ';"><b>' . $var_name . '</b>';
464
-    }
465
-
466
-
467
-
468
-    /**
469
-     * @param string $heading_tag
470
-     * @return string
471
-     */
472
-    protected static function headingX($heading_tag = 'h5')
473
-    {
474
-        if (EEH_Debug_Tools::plainOutput()) {
475
-            return '';
476
-        }
477
-        return '</' . $heading_tag . '>';
478
-    }
479
-
480
-
481
-
482
-    /**
483
-     * @param string $content
484
-     * @return string
485
-     */
486
-    protected static function grey_span($content = '')
487
-    {
488
-        if (EEH_Debug_Tools::plainOutput()) {
489
-            return $content;
490
-        }
491
-        return '<span style="color:#999">' . $content . '</span>';
492
-    }
493
-
494
-
495
-
496
-    /**
497
-     * @param string $file
498
-     * @param int    $line
499
-     * @return string
500
-     */
501
-    protected static function file_and_line($file, $line, $heading_tag)
502
-    {
503
-        if ($file === '' || $line === '') {
504
-            return '';
505
-        }
506
-        $file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
507
-        if (EEH_Debug_Tools::plainOutput()) {
508
-            if ($heading_tag === 'h1' || $heading_tag === 'h2') {
509
-                return " ({$file})" . EEH_Debug_Tools::lineBreak();
510
-            }
511
-            return '';
512
-        }
513
-        return EEH_Debug_Tools::lineBreak()
514
-               . '<span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
515
-               . $file
516
-               . EEH_Debug_Tools::lineBreak()
517
-               . 'line no: '
518
-               . $line
519
-               . '</span>';
520
-    }
521
-
522
-
523
-
524
-    /**
525
-     * @param string $content
526
-     * @return string
527
-     */
528
-    protected static function orange_span($content = '')
529
-    {
530
-        if (EEH_Debug_Tools::plainOutput()) {
531
-            return $content;
532
-        }
533
-        return '<span style="color:#E76700">' . $content . '</span>';
534
-    }
535
-
536
-
537
-
538
-    /**
539
-     * @param mixed $var
540
-     * @return string
541
-     */
542
-    protected static function pre_span($var)
543
-    {
544
-        ob_start();
545
-        var_dump($var);
546
-        $var = ob_get_clean();
547
-        if (EEH_Debug_Tools::plainOutput()) {
548
-            return $var;
549
-        }
550
-        return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">' . $var . '</pre>';
551
-    }
552
-
553
-
554
-
555
-    /**
556
-     * @param mixed      $var
557
-     * @param string     $var_name
558
-     * @param string     $file
559
-     * @param int|string $line
560
-     * @param int|string $heading_tag
561
-     * @param bool       $die
562
-     */
563
-    public static function printr(
564
-        $var,
565
-        $var_name = '',
566
-        $file = '',
567
-        $line = '',
568
-        $heading_tag = 5,
569
-        $die = false
570
-    ) {
571
-        // return;
572
-        $file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
573
-        if (empty($var) && empty($var_name)) {
574
-            $var = $file;
575
-            $var_name = "line $line";
576
-            $file = '';
577
-            $line = '';
578
-        }
579
-        $margin = is_admin() ? ' 180px' : '0';
580
-        // $print_r = false;
581
-        if (is_string($var)) {
582
-            EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
583
-            return;
584
-        }
585
-        if (is_object($var)) {
586
-            $var_name = ! $var_name ? 'object' : $var_name;
587
-            // $print_r = true;
588
-        } elseif (is_array($var)) {
589
-            $var_name = ! $var_name ? 'array' : $var_name;
590
-            // $print_r = true;
591
-        } elseif (is_numeric($var)) {
592
-            $var_name = ! $var_name ? 'numeric' : $var_name;
593
-        } elseif ($var === null) {
594
-            $var_name = ! $var_name ? 'null' : $var_name;
595
-        }
596
-        $var_name = ucwords(str_replace(array('$', '_'), array('', ' '), $var_name));
597
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
598
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
599
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
600
-        $result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
601
-            EEH_Debug_Tools::pre_span($var)
602
-        );
603
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
604
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
605
-        if ($die) {
606
-            die($result);
607
-        }
608
-        echo $result;
609
-    }
610
-
611
-
612
-    private static function lineBreak($lines = 1): string
613
-    {
614
-        $linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
615
-        return str_repeat($linebreak, $lines);
616
-    }
617
-
618
-
619
-    public static function shortClassName(string $fqcn): string
620
-    {
621
-        return substr(strrchr($fqcn, '\\'), 1);
622
-    }
623
-
624
-
625
-
626
-    /******************** deprecated ********************/
627
-
628
-
629
-
630
-    /**
631
-     * @deprecated 4.9.39.rc.034
632
-     */
633
-    public function reset_times()
634
-    {
635
-        Benchmark::resetTimes();
636
-    }
637
-
638
-
639
-
640
-    /**
641
-     * @deprecated 4.9.39.rc.034
642
-     * @param null $timer_name
643
-     */
644
-    public function start_timer($timer_name = null)
645
-    {
646
-        Benchmark::startTimer($timer_name);
647
-    }
648
-
649
-
650
-
651
-    /**
652
-     * @deprecated 4.9.39.rc.034
653
-     * @param string $timer_name
654
-     */
655
-    public function stop_timer($timer_name = '')
656
-    {
657
-        Benchmark::stopTimer($timer_name);
658
-    }
659
-
660
-
661
-
662
-    /**
663
-     * @deprecated 4.9.39.rc.034
664
-     * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
665
-     * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
666
-     * @return void
667
-     */
668
-    public function measure_memory($label, $output_now = false)
669
-    {
670
-        Benchmark::measureMemory($label, $output_now);
671
-    }
672
-
673
-
674
-
675
-    /**
676
-     * @deprecated 4.9.39.rc.034
677
-     * @param int $size
678
-     * @return string
679
-     */
680
-    public function convert($size)
681
-    {
682
-        return Benchmark::convert($size);
683
-    }
684
-
685
-
686
-
687
-    /**
688
-     * @deprecated 4.9.39.rc.034
689
-     * @param bool $output_now
690
-     * @return string
691
-     */
692
-    public function show_times($output_now = true)
693
-    {
694
-        return Benchmark::displayResults($output_now);
695
-    }
16
+	/**
17
+	 *    instance of the EEH_Autoloader object
18
+	 *
19
+	 * @var    $_instance
20
+	 * @access    private
21
+	 */
22
+	private static $_instance;
23
+
24
+	/**
25
+	 * @var array
26
+	 */
27
+	protected $_memory_usage_points = array();
28
+
29
+
30
+
31
+	/**
32
+	 * @singleton method used to instantiate class object
33
+	 * @access    public
34
+	 * @return EEH_Debug_Tools
35
+	 */
36
+	public static function instance()
37
+	{
38
+		// check if class object is instantiated, and instantiated properly
39
+		if (! self::$_instance instanceof EEH_Debug_Tools) {
40
+			self::$_instance = new self();
41
+		}
42
+		return self::$_instance;
43
+	}
44
+
45
+
46
+
47
+	/**
48
+	 * private class constructor
49
+	 */
50
+	private function __construct()
51
+	{
52
+		// load Kint PHP debugging library
53
+		if (! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')) {
54
+			// despite EE4 having a check for an existing copy of the Kint debugging class,
55
+			// if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
56
+			// then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
57
+			// so we've moved it to our test folder so that it is not included with production releases
58
+			// plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
59
+			require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
60
+		}
61
+		// if ( ! defined('DOING_AJAX') || $_REQUEST['noheader'] !== 'true' || ! isset( $_REQUEST['noheader'], $_REQUEST['TB_iframe'] ) ) {
62
+		// add_action( 'shutdown', array($this,'espresso_session_footer_dump') );
63
+		// }
64
+		$plugin = basename(EE_PLUGIN_DIR_PATH);
65
+		add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
66
+		add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
67
+		add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
68
+	}
69
+
70
+
71
+
72
+	/**
73
+	 *    show_db_name
74
+	 *
75
+	 * @return void
76
+	 */
77
+	public static function show_db_name()
78
+	{
79
+		if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
80
+			echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
81
+				 . DB_NAME
82
+				 . '</p>';
83
+		}
84
+		if (EE_DEBUG) {
85
+			Benchmark::displayResults();
86
+		}
87
+	}
88
+
89
+
90
+
91
+	/**
92
+	 *    dump EE_Session object at bottom of page after everything else has happened
93
+	 *
94
+	 * @return void
95
+	 */
96
+	public function espresso_session_footer_dump()
97
+	{
98
+		if (
99
+			(defined('WP_DEBUG') && WP_DEBUG)
100
+			&& ! defined('DOING_AJAX')
101
+			&& class_exists('Kint')
102
+			&& function_exists('wp_get_current_user')
103
+			&& current_user_can('update_core')
104
+			&& class_exists('EE_Registry')
105
+		) {
106
+			Kint::dump(EE_Registry::instance()->SSN->id());
107
+			Kint::dump(EE_Registry::instance()->SSN);
108
+			//          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
109
+			$this->espresso_list_hooked_functions();
110
+			Benchmark::displayResults();
111
+		}
112
+	}
113
+
114
+
115
+
116
+	/**
117
+	 *    List All Hooked Functions
118
+	 *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
119
+	 *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
120
+	 *
121
+	 * @param string $tag
122
+	 * @return void
123
+	 */
124
+	public function espresso_list_hooked_functions($tag = '')
125
+	{
126
+		global $wp_filter;
127
+		echo '<br/><br/><br/><h3>Hooked Functions</h3>';
128
+		if ($tag) {
129
+			$hook[ $tag ] = $wp_filter[ $tag ];
130
+			if (! is_array($hook[ $tag ])) {
131
+				trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
132
+				return;
133
+			}
134
+			echo '<h5>For Tag: ' . $tag . '</h5>';
135
+		} else {
136
+			$hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
137
+			ksort($hook);
138
+		}
139
+		foreach ($hook as $tag_name => $priorities) {
140
+			echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>$tag_name</strong><br />";
141
+			ksort($priorities);
142
+			foreach ($priorities as $priority => $function) {
143
+				echo $priority;
144
+				foreach ($function as $name => $properties) {
145
+					echo "\t$name<br />";
146
+				}
147
+			}
148
+		}
149
+	}
150
+
151
+
152
+
153
+	/**
154
+	 *    registered_filter_callbacks
155
+	 *
156
+	 * @param string $hook_name
157
+	 * @return array
158
+	 */
159
+	public static function registered_filter_callbacks($hook_name = '')
160
+	{
161
+		$filters = array();
162
+		global $wp_filter;
163
+		if (isset($wp_filter[ $hook_name ])) {
164
+			$filters[ $hook_name ] = array();
165
+			foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
166
+				$filters[ $hook_name ][ $priority ] = array();
167
+				foreach ($callbacks as $callback) {
168
+					$filters[ $hook_name ][ $priority ][] = $callback['function'];
169
+				}
170
+			}
171
+		}
172
+		return $filters;
173
+	}
174
+
175
+
176
+
177
+	/**
178
+	 *    captures plugin activation errors for debugging
179
+	 *
180
+	 * @return void
181
+	 * @throws EE_Error
182
+	 */
183
+	public static function ee_plugin_activation_errors()
184
+	{
185
+		if (WP_DEBUG) {
186
+			$activation_errors = ob_get_contents();
187
+			if (empty($activation_errors)) {
188
+				return;
189
+			}
190
+			$activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
191
+			espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
192
+			if (class_exists('EEH_File')) {
193
+				try {
194
+					EEH_File::ensure_file_exists_and_is_writable(
195
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
196
+					);
197
+					EEH_File::write_to_file(
198
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
199
+						$activation_errors
200
+					);
201
+				} catch (EE_Error $e) {
202
+					EE_Error::add_error(
203
+						sprintf(
204
+							__(
205
+								'The Event Espresso activation errors file could not be setup because: %s',
206
+								'event_espresso'
207
+							),
208
+							$e->getMessage()
209
+						),
210
+						__FILE__,
211
+						__FUNCTION__,
212
+						__LINE__
213
+					);
214
+				}
215
+			} else {
216
+				// old school attempt
217
+				file_put_contents(
218
+					EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
219
+					$activation_errors
220
+				);
221
+			}
222
+			$activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
223
+			update_option('ee_plugin_activation_errors', $activation_errors);
224
+		}
225
+	}
226
+
227
+
228
+
229
+	/**
230
+	 * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
231
+	 * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
232
+	 * or we want to make sure they use something the right way.
233
+	 *
234
+	 * @access public
235
+	 * @param string $function      The function that was called
236
+	 * @param string $message       A message explaining what has been done incorrectly
237
+	 * @param string $version       The version of Event Espresso where the error was added
238
+	 * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
239
+	 *                              for a deprecated function. This allows deprecation to occur during one version,
240
+	 *                              but not have any notices appear until a later version. This allows developers
241
+	 *                              extra time to update their code before notices appear.
242
+	 * @param int    $error_type
243
+	 * @uses   trigger_error()
244
+	 */
245
+	public function doing_it_wrong(
246
+		$function,
247
+		$message,
248
+		$version,
249
+		$applies_when = '',
250
+		$error_type = null
251
+	) {
252
+		$applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
253
+		$error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
254
+		// because we swapped the parameter order around for the last two params,
255
+		// let's verify that some third party isn't still passing an error type value for the third param
256
+		if (is_int($applies_when)) {
257
+			$error_type = $applies_when;
258
+			$applies_when = espresso_version();
259
+		}
260
+		// if not displaying notices yet, then just leave
261
+		if (version_compare(espresso_version(), $applies_when, '<')) {
262
+			return;
263
+		}
264
+		do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
265
+		$version = $version === null
266
+			? ''
267
+			: sprintf(
268
+				__('(This message was added in version %s of Event Espresso)', 'event_espresso'),
269
+				$version
270
+			);
271
+		$error_message = sprintf(
272
+			esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
273
+			$function,
274
+			'<strong>',
275
+			'</strong>',
276
+			$message,
277
+			$version
278
+		);
279
+		// don't trigger error if doing ajax,
280
+		// instead we'll add a transient EE_Error notice that in theory should show on the next request.
281
+		if (defined('DOING_AJAX') && DOING_AJAX) {
282
+			$error_message .= ' ' . esc_html__(
283
+				'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
284
+				'event_espresso'
285
+			);
286
+			$error_message .= '<ul><li>';
287
+			$error_message .= implode('</li><li>', EE_Registry::instance()->REQ->params());
288
+			$error_message .= '</ul>';
289
+			EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
290
+			// now we set this on the transient so it shows up on the next request.
291
+			EE_Error::get_notices(false, true);
292
+		} else {
293
+			trigger_error($error_message, $error_type);
294
+		}
295
+	}
296
+
297
+
298
+
299
+
300
+	/**
301
+	 * Logger helpers
302
+	 */
303
+	/**
304
+	 * debug
305
+	 *
306
+	 * @param string $class
307
+	 * @param string $func
308
+	 * @param string $line
309
+	 * @param array  $info
310
+	 * @param bool   $display_request
311
+	 * @param string $debug_index
312
+	 * @param string $debug_key
313
+	 * @throws EE_Error
314
+	 * @throws \EventEspresso\core\exceptions\InvalidSessionDataException
315
+	 */
316
+	public static function log(
317
+		$class = '',
318
+		$func = '',
319
+		$line = '',
320
+		$info = array(),
321
+		$display_request = false,
322
+		$debug_index = '',
323
+		$debug_key = 'EE_DEBUG_SPCO'
324
+	) {
325
+		if (WP_DEBUG) {
326
+			$debug_key = $debug_key . '_' . EE_Session::instance()->id();
327
+			$debug_data = get_option($debug_key, array());
328
+			$default_data = array(
329
+				$class => $func . '() : ' . $line,
330
+				'REQ'  => $display_request ? $_REQUEST : '',
331
+			);
332
+			// don't serialize objects
333
+			$info = self::strip_objects($info);
334
+			$index = ! empty($debug_index) ? $debug_index : 0;
335
+			if (! isset($debug_data[ $index ])) {
336
+				$debug_data[ $index ] = array();
337
+			}
338
+			$debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
339
+			update_option($debug_key, $debug_data);
340
+		}
341
+	}
342
+
343
+
344
+
345
+	/**
346
+	 * strip_objects
347
+	 *
348
+	 * @param array $info
349
+	 * @return array
350
+	 */
351
+	public static function strip_objects($info = array())
352
+	{
353
+		foreach ($info as $key => $value) {
354
+			if (is_array($value)) {
355
+				$info[ $key ] = self::strip_objects($value);
356
+			} elseif (is_object($value)) {
357
+				$object_class = get_class($value);
358
+				$info[ $object_class ] = array();
359
+				$info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
360
+				if (method_exists($value, 'ID')) {
361
+					$info[ $object_class ]['ID'] = $value->ID();
362
+				}
363
+				if (method_exists($value, 'status')) {
364
+					$info[ $object_class ]['status'] = $value->status();
365
+				} elseif (method_exists($value, 'status_ID')) {
366
+					$info[ $object_class ]['status'] = $value->status_ID();
367
+				}
368
+				unset($info[ $key ]);
369
+			}
370
+		}
371
+		return (array) $info;
372
+	}
373
+
374
+
375
+
376
+	/**
377
+	 * @param mixed      $var
378
+	 * @param string     $var_name
379
+	 * @param string     $file
380
+	 * @param int|string $line
381
+	 * @param int|string $heading_tag
382
+	 * @param bool       $die
383
+	 * @param string     $margin
384
+	 */
385
+	public static function printv(
386
+		$var,
387
+		$var_name = '',
388
+		$file = '',
389
+		$line = '',
390
+		$heading_tag = 5,
391
+		$die = false,
392
+		$margin = ''
393
+	) {
394
+		$var_name = ! $var_name ? 'string' : $var_name;
395
+		$var_name = ucwords(str_replace('$', '', $var_name));
396
+		$is_method = method_exists($var_name, $var);
397
+		$var_name = ucwords(str_replace('_', ' ', $var_name));
398
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
399
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
400
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
401
+		$result .= $is_method
402
+			? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
403
+			: EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
404
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
405
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
406
+		if ($die) {
407
+			die($result);
408
+		}
409
+		echo $result;
410
+	}
411
+
412
+
413
+	protected static function headingTag($heading_tag)
414
+	{
415
+		$heading_tag = absint($heading_tag);
416
+		return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
417
+	}
418
+
419
+
420
+	protected static function headingSpacer($heading_tag)
421
+	{
422
+		return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
423
+			? "\n"
424
+			: '';
425
+	}
426
+
427
+
428
+	protected static function plainOutput()
429
+	{
430
+		return defined('EE_TESTS_DIR')
431
+			   || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
432
+			   || (
433
+				   isset($_SERVER['REQUEST_URI'])
434
+				   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
435
+			   );
436
+	}
437
+
438
+
439
+	/**
440
+	 * @param string $var_name
441
+	 * @param string $heading_tag
442
+	 * @param string $margin
443
+	 * @param int    $line
444
+	 * @return string
445
+	 */
446
+	protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
447
+	{
448
+		if (EEH_Debug_Tools::plainOutput()) {
449
+			switch ($heading_tag) {
450
+				case 'h1':
451
+					$line_breaks = EEH_Debug_Tools::lineBreak(3);
452
+					break;
453
+				case 'h2':
454
+					$line_breaks = EEH_Debug_Tools::lineBreak(2);
455
+					break;
456
+				default:
457
+					$line_breaks = EEH_Debug_Tools::lineBreak();
458
+					break;
459
+			}
460
+			return "{$line_breaks}{$line}) {$var_name}";
461
+		}
462
+		$margin = "25px 0 0 {$margin}";
463
+		return '<' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . ';"><b>' . $var_name . '</b>';
464
+	}
465
+
466
+
467
+
468
+	/**
469
+	 * @param string $heading_tag
470
+	 * @return string
471
+	 */
472
+	protected static function headingX($heading_tag = 'h5')
473
+	{
474
+		if (EEH_Debug_Tools::plainOutput()) {
475
+			return '';
476
+		}
477
+		return '</' . $heading_tag . '>';
478
+	}
479
+
480
+
481
+
482
+	/**
483
+	 * @param string $content
484
+	 * @return string
485
+	 */
486
+	protected static function grey_span($content = '')
487
+	{
488
+		if (EEH_Debug_Tools::plainOutput()) {
489
+			return $content;
490
+		}
491
+		return '<span style="color:#999">' . $content . '</span>';
492
+	}
493
+
494
+
495
+
496
+	/**
497
+	 * @param string $file
498
+	 * @param int    $line
499
+	 * @return string
500
+	 */
501
+	protected static function file_and_line($file, $line, $heading_tag)
502
+	{
503
+		if ($file === '' || $line === '') {
504
+			return '';
505
+		}
506
+		$file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
507
+		if (EEH_Debug_Tools::plainOutput()) {
508
+			if ($heading_tag === 'h1' || $heading_tag === 'h2') {
509
+				return " ({$file})" . EEH_Debug_Tools::lineBreak();
510
+			}
511
+			return '';
512
+		}
513
+		return EEH_Debug_Tools::lineBreak()
514
+			   . '<span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
515
+			   . $file
516
+			   . EEH_Debug_Tools::lineBreak()
517
+			   . 'line no: '
518
+			   . $line
519
+			   . '</span>';
520
+	}
521
+
522
+
523
+
524
+	/**
525
+	 * @param string $content
526
+	 * @return string
527
+	 */
528
+	protected static function orange_span($content = '')
529
+	{
530
+		if (EEH_Debug_Tools::plainOutput()) {
531
+			return $content;
532
+		}
533
+		return '<span style="color:#E76700">' . $content . '</span>';
534
+	}
535
+
536
+
537
+
538
+	/**
539
+	 * @param mixed $var
540
+	 * @return string
541
+	 */
542
+	protected static function pre_span($var)
543
+	{
544
+		ob_start();
545
+		var_dump($var);
546
+		$var = ob_get_clean();
547
+		if (EEH_Debug_Tools::plainOutput()) {
548
+			return $var;
549
+		}
550
+		return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">' . $var . '</pre>';
551
+	}
552
+
553
+
554
+
555
+	/**
556
+	 * @param mixed      $var
557
+	 * @param string     $var_name
558
+	 * @param string     $file
559
+	 * @param int|string $line
560
+	 * @param int|string $heading_tag
561
+	 * @param bool       $die
562
+	 */
563
+	public static function printr(
564
+		$var,
565
+		$var_name = '',
566
+		$file = '',
567
+		$line = '',
568
+		$heading_tag = 5,
569
+		$die = false
570
+	) {
571
+		// return;
572
+		$file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
573
+		if (empty($var) && empty($var_name)) {
574
+			$var = $file;
575
+			$var_name = "line $line";
576
+			$file = '';
577
+			$line = '';
578
+		}
579
+		$margin = is_admin() ? ' 180px' : '0';
580
+		// $print_r = false;
581
+		if (is_string($var)) {
582
+			EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
583
+			return;
584
+		}
585
+		if (is_object($var)) {
586
+			$var_name = ! $var_name ? 'object' : $var_name;
587
+			// $print_r = true;
588
+		} elseif (is_array($var)) {
589
+			$var_name = ! $var_name ? 'array' : $var_name;
590
+			// $print_r = true;
591
+		} elseif (is_numeric($var)) {
592
+			$var_name = ! $var_name ? 'numeric' : $var_name;
593
+		} elseif ($var === null) {
594
+			$var_name = ! $var_name ? 'null' : $var_name;
595
+		}
596
+		$var_name = ucwords(str_replace(array('$', '_'), array('', ' '), $var_name));
597
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
598
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
599
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
600
+		$result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
601
+			EEH_Debug_Tools::pre_span($var)
602
+		);
603
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
604
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
605
+		if ($die) {
606
+			die($result);
607
+		}
608
+		echo $result;
609
+	}
610
+
611
+
612
+	private static function lineBreak($lines = 1): string
613
+	{
614
+		$linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
615
+		return str_repeat($linebreak, $lines);
616
+	}
617
+
618
+
619
+	public static function shortClassName(string $fqcn): string
620
+	{
621
+		return substr(strrchr($fqcn, '\\'), 1);
622
+	}
623
+
624
+
625
+
626
+	/******************** deprecated ********************/
627
+
628
+
629
+
630
+	/**
631
+	 * @deprecated 4.9.39.rc.034
632
+	 */
633
+	public function reset_times()
634
+	{
635
+		Benchmark::resetTimes();
636
+	}
637
+
638
+
639
+
640
+	/**
641
+	 * @deprecated 4.9.39.rc.034
642
+	 * @param null $timer_name
643
+	 */
644
+	public function start_timer($timer_name = null)
645
+	{
646
+		Benchmark::startTimer($timer_name);
647
+	}
648
+
649
+
650
+
651
+	/**
652
+	 * @deprecated 4.9.39.rc.034
653
+	 * @param string $timer_name
654
+	 */
655
+	public function stop_timer($timer_name = '')
656
+	{
657
+		Benchmark::stopTimer($timer_name);
658
+	}
659
+
660
+
661
+
662
+	/**
663
+	 * @deprecated 4.9.39.rc.034
664
+	 * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
665
+	 * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
666
+	 * @return void
667
+	 */
668
+	public function measure_memory($label, $output_now = false)
669
+	{
670
+		Benchmark::measureMemory($label, $output_now);
671
+	}
672
+
673
+
674
+
675
+	/**
676
+	 * @deprecated 4.9.39.rc.034
677
+	 * @param int $size
678
+	 * @return string
679
+	 */
680
+	public function convert($size)
681
+	{
682
+		return Benchmark::convert($size);
683
+	}
684
+
685
+
686
+
687
+	/**
688
+	 * @deprecated 4.9.39.rc.034
689
+	 * @param bool $output_now
690
+	 * @return string
691
+	 */
692
+	public function show_times($output_now = true)
693
+	{
694
+		return Benchmark::displayResults($output_now);
695
+	}
696 696
 
697 697
 
698 698
 
699
-    /**
700
-     * @deprecated 4.9.39.rc.034
701
-     * @param string $timer_name
702
-     * @param float  $total_time
703
-     * @return string
704
-     */
705
-    public function format_time($timer_name, $total_time)
706
-    {
707
-        return Benchmark::formatTime($timer_name, $total_time);
708
-    }
699
+	/**
700
+	 * @deprecated 4.9.39.rc.034
701
+	 * @param string $timer_name
702
+	 * @param float  $total_time
703
+	 * @return string
704
+	 */
705
+	public function format_time($timer_name, $total_time)
706
+	{
707
+		return Benchmark::formatTime($timer_name, $total_time);
708
+	}
709 709
 }
710 710
 
711 711
 
@@ -715,31 +715,31 @@  discard block
 block discarded – undo
715 715
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
716 716
  */
717 717
 if (class_exists('Kint') && ! function_exists('dump_wp_query')) {
718
-    function dump_wp_query()
719
-    {
720
-        global $wp_query;
721
-        d($wp_query);
722
-    }
718
+	function dump_wp_query()
719
+	{
720
+		global $wp_query;
721
+		d($wp_query);
722
+	}
723 723
 }
724 724
 /**
725 725
  * borrowed from Kint Debugger
726 726
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
727 727
  */
728 728
 if (class_exists('Kint') && ! function_exists('dump_wp')) {
729
-    function dump_wp()
730
-    {
731
-        global $wp;
732
-        d($wp);
733
-    }
729
+	function dump_wp()
730
+	{
731
+		global $wp;
732
+		d($wp);
733
+	}
734 734
 }
735 735
 /**
736 736
  * borrowed from Kint Debugger
737 737
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
738 738
  */
739 739
 if (class_exists('Kint') && ! function_exists('dump_post')) {
740
-    function dump_post()
741
-    {
742
-        global $post;
743
-        d($post);
744
-    }
740
+	function dump_post()
741
+	{
742
+		global $post;
743
+		d($post);
744
+	}
745 745
 }
Please login to merge, or discard this patch.
Spacing   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -36,7 +36,7 @@  discard block
 block discarded – undo
36 36
     public static function instance()
37 37
     {
38 38
         // check if class object is instantiated, and instantiated properly
39
-        if (! self::$_instance instanceof EEH_Debug_Tools) {
39
+        if ( ! self::$_instance instanceof EEH_Debug_Tools) {
40 40
             self::$_instance = new self();
41 41
         }
42 42
         return self::$_instance;
@@ -50,13 +50,13 @@  discard block
 block discarded – undo
50 50
     private function __construct()
51 51
     {
52 52
         // load Kint PHP debugging library
53
-        if (! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')) {
53
+        if ( ! class_exists('Kint') && file_exists(EE_PLUGIN_DIR_PATH.'tests/kint/Kint.class.php')) {
54 54
             // despite EE4 having a check for an existing copy of the Kint debugging class,
55 55
             // if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
56 56
             // then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
57 57
             // so we've moved it to our test folder so that it is not included with production releases
58 58
             // plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
59
-            require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
59
+            require_once(EE_PLUGIN_DIR_PATH.'tests/kint/Kint.class.php');
60 60
         }
61 61
         // if ( ! defined('DOING_AJAX') || $_REQUEST['noheader'] !== 'true' || ! isset( $_REQUEST['noheader'], $_REQUEST['TB_iframe'] ) ) {
62 62
         // add_action( 'shutdown', array($this,'espresso_session_footer_dump') );
@@ -76,7 +76,7 @@  discard block
 block discarded – undo
76 76
      */
77 77
     public static function show_db_name()
78 78
     {
79
-        if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
79
+        if ( ! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
80 80
             echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
81 81
                  . DB_NAME
82 82
                  . '</p>';
@@ -126,12 +126,12 @@  discard block
 block discarded – undo
126 126
         global $wp_filter;
127 127
         echo '<br/><br/><br/><h3>Hooked Functions</h3>';
128 128
         if ($tag) {
129
-            $hook[ $tag ] = $wp_filter[ $tag ];
130
-            if (! is_array($hook[ $tag ])) {
129
+            $hook[$tag] = $wp_filter[$tag];
130
+            if ( ! is_array($hook[$tag])) {
131 131
                 trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
132 132
                 return;
133 133
             }
134
-            echo '<h5>For Tag: ' . $tag . '</h5>';
134
+            echo '<h5>For Tag: '.$tag.'</h5>';
135 135
         } else {
136 136
             $hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
137 137
             ksort($hook);
@@ -160,12 +160,12 @@  discard block
 block discarded – undo
160 160
     {
161 161
         $filters = array();
162 162
         global $wp_filter;
163
-        if (isset($wp_filter[ $hook_name ])) {
164
-            $filters[ $hook_name ] = array();
165
-            foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
166
-                $filters[ $hook_name ][ $priority ] = array();
163
+        if (isset($wp_filter[$hook_name])) {
164
+            $filters[$hook_name] = array();
165
+            foreach ($wp_filter[$hook_name] as $priority => $callbacks) {
166
+                $filters[$hook_name][$priority] = array();
167 167
                 foreach ($callbacks as $callback) {
168
-                    $filters[ $hook_name ][ $priority ][] = $callback['function'];
168
+                    $filters[$hook_name][$priority][] = $callback['function'];
169 169
                 }
170 170
             }
171 171
         }
@@ -187,15 +187,15 @@  discard block
 block discarded – undo
187 187
             if (empty($activation_errors)) {
188 188
                 return;
189 189
             }
190
-            $activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
191
-            espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
190
+            $activation_errors = date('Y-m-d H:i:s')."\n".$activation_errors;
191
+            espresso_load_required('EEH_File', EE_HELPERS.'EEH_File.helper.php');
192 192
             if (class_exists('EEH_File')) {
193 193
                 try {
194 194
                     EEH_File::ensure_file_exists_and_is_writable(
195
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
195
+                        EVENT_ESPRESSO_UPLOAD_DIR.'logs/espresso_plugin_activation_errors.html'
196 196
                     );
197 197
                     EEH_File::write_to_file(
198
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
198
+                        EVENT_ESPRESSO_UPLOAD_DIR.'logs/espresso_plugin_activation_errors.html',
199 199
                         $activation_errors
200 200
                     );
201 201
                 } catch (EE_Error $e) {
@@ -215,11 +215,11 @@  discard block
 block discarded – undo
215 215
             } else {
216 216
                 // old school attempt
217 217
                 file_put_contents(
218
-                    EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
218
+                    EVENT_ESPRESSO_UPLOAD_DIR.'logs/espresso_plugin_activation_errors.html',
219 219
                     $activation_errors
220 220
                 );
221 221
             }
222
-            $activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
222
+            $activation_errors = get_option('ee_plugin_activation_errors', '').$activation_errors;
223 223
             update_option('ee_plugin_activation_errors', $activation_errors);
224 224
         }
225 225
     }
@@ -279,7 +279,7 @@  discard block
 block discarded – undo
279 279
         // don't trigger error if doing ajax,
280 280
         // instead we'll add a transient EE_Error notice that in theory should show on the next request.
281 281
         if (defined('DOING_AJAX') && DOING_AJAX) {
282
-            $error_message .= ' ' . esc_html__(
282
+            $error_message .= ' '.esc_html__(
283 283
                 'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
284 284
                 'event_espresso'
285 285
             );
@@ -323,19 +323,19 @@  discard block
 block discarded – undo
323 323
         $debug_key = 'EE_DEBUG_SPCO'
324 324
     ) {
325 325
         if (WP_DEBUG) {
326
-            $debug_key = $debug_key . '_' . EE_Session::instance()->id();
326
+            $debug_key = $debug_key.'_'.EE_Session::instance()->id();
327 327
             $debug_data = get_option($debug_key, array());
328 328
             $default_data = array(
329
-                $class => $func . '() : ' . $line,
329
+                $class => $func.'() : '.$line,
330 330
                 'REQ'  => $display_request ? $_REQUEST : '',
331 331
             );
332 332
             // don't serialize objects
333 333
             $info = self::strip_objects($info);
334 334
             $index = ! empty($debug_index) ? $debug_index : 0;
335
-            if (! isset($debug_data[ $index ])) {
336
-                $debug_data[ $index ] = array();
335
+            if ( ! isset($debug_data[$index])) {
336
+                $debug_data[$index] = array();
337 337
             }
338
-            $debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
338
+            $debug_data[$index][microtime()] = array_merge($default_data, $info);
339 339
             update_option($debug_key, $debug_data);
340 340
         }
341 341
     }
@@ -352,20 +352,20 @@  discard block
 block discarded – undo
352 352
     {
353 353
         foreach ($info as $key => $value) {
354 354
             if (is_array($value)) {
355
-                $info[ $key ] = self::strip_objects($value);
355
+                $info[$key] = self::strip_objects($value);
356 356
             } elseif (is_object($value)) {
357 357
                 $object_class = get_class($value);
358
-                $info[ $object_class ] = array();
359
-                $info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
358
+                $info[$object_class] = array();
359
+                $info[$object_class]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
360 360
                 if (method_exists($value, 'ID')) {
361
-                    $info[ $object_class ]['ID'] = $value->ID();
361
+                    $info[$object_class]['ID'] = $value->ID();
362 362
                 }
363 363
                 if (method_exists($value, 'status')) {
364
-                    $info[ $object_class ]['status'] = $value->status();
364
+                    $info[$object_class]['status'] = $value->status();
365 365
                 } elseif (method_exists($value, 'status_ID')) {
366
-                    $info[ $object_class ]['status'] = $value->status_ID();
366
+                    $info[$object_class]['status'] = $value->status_ID();
367 367
                 }
368
-                unset($info[ $key ]);
368
+                unset($info[$key]);
369 369
             }
370 370
         }
371 371
         return (array) $info;
@@ -399,8 +399,8 @@  discard block
 block discarded – undo
399 399
         // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
400 400
         $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
401 401
         $result .= $is_method
402
-            ? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
403
-            : EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
402
+            ? EEH_Debug_Tools::grey_span('::').EEH_Debug_Tools::orange_span($var.'()')
403
+            : EEH_Debug_Tools::grey_span(' : ').EEH_Debug_Tools::orange_span($var);
404 404
         $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
405 405
         $result .= EEH_Debug_Tools::headingX($heading_tag);
406 406
         if ($die) {
@@ -460,7 +460,7 @@  discard block
 block discarded – undo
460 460
             return "{$line_breaks}{$line}) {$var_name}";
461 461
         }
462 462
         $margin = "25px 0 0 {$margin}";
463
-        return '<' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . ';"><b>' . $var_name . '</b>';
463
+        return '<'.$heading_tag.' style="color:#2EA2CC; margin:'.$margin.';"><b>'.$var_name.'</b>';
464 464
     }
465 465
 
466 466
 
@@ -474,7 +474,7 @@  discard block
 block discarded – undo
474 474
         if (EEH_Debug_Tools::plainOutput()) {
475 475
             return '';
476 476
         }
477
-        return '</' . $heading_tag . '>';
477
+        return '</'.$heading_tag.'>';
478 478
     }
479 479
 
480 480
 
@@ -488,7 +488,7 @@  discard block
 block discarded – undo
488 488
         if (EEH_Debug_Tools::plainOutput()) {
489 489
             return $content;
490 490
         }
491
-        return '<span style="color:#999">' . $content . '</span>';
491
+        return '<span style="color:#999">'.$content.'</span>';
492 492
     }
493 493
 
494 494
 
@@ -506,7 +506,7 @@  discard block
 block discarded – undo
506 506
         $file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
507 507
         if (EEH_Debug_Tools::plainOutput()) {
508 508
             if ($heading_tag === 'h1' || $heading_tag === 'h2') {
509
-                return " ({$file})" . EEH_Debug_Tools::lineBreak();
509
+                return " ({$file})".EEH_Debug_Tools::lineBreak();
510 510
             }
511 511
             return '';
512 512
         }
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
         if (EEH_Debug_Tools::plainOutput()) {
531 531
             return $content;
532 532
         }
533
-        return '<span style="color:#E76700">' . $content . '</span>';
533
+        return '<span style="color:#E76700">'.$content.'</span>';
534 534
     }
535 535
 
536 536
 
@@ -547,7 +547,7 @@  discard block
 block discarded – undo
547 547
         if (EEH_Debug_Tools::plainOutput()) {
548 548
             return $var;
549 549
         }
550
-        return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">' . $var . '</pre>';
550
+        return '<pre style="color: #9C3; display: inline-block; padding:.4em .6em; background: #334">'.$var.'</pre>';
551 551
     }
552 552
 
553 553
 
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
         $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
598 598
         // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
599 599
         $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
600
-        $result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
600
+        $result .= EEH_Debug_Tools::grey_span(' : ').EEH_Debug_Tools::orange_span(
601 601
             EEH_Debug_Tools::pre_span($var)
602 602
         );
603 603
         $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
Please login to merge, or discard this patch.
core/db_classes/EE_Line_Item.class.php 1 patch
Indentation   +1610 added lines, -1610 removed lines patch added patch discarded remove patch
@@ -16,1614 +16,1614 @@
 block discarded – undo
16 16
 class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
17 17
 {
18 18
 
19
-    /**
20
-     * for children line items (currently not a normal relation)
21
-     *
22
-     * @type EE_Line_Item[]
23
-     */
24
-    protected $_children = array();
25
-
26
-    /**
27
-     * for the parent line item
28
-     *
29
-     * @var EE_Line_Item
30
-     */
31
-    protected $_parent;
32
-
33
-    /**
34
-     * @var LineItemCalculator
35
-     */
36
-    protected $calculator;
37
-
38
-
39
-    /**
40
-     * @param array  $props_n_values          incoming values
41
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
-     *                                        used.)
43
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
-     *                                        date_format and the second value is the time format
45
-     * @return EE_Line_Item
46
-     * @throws EE_Error
47
-     * @throws InvalidArgumentException
48
-     * @throws InvalidDataTypeException
49
-     * @throws InvalidInterfaceException
50
-     * @throws ReflectionException
51
-     */
52
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
53
-    {
54
-        $has_object = parent::_check_for_object(
55
-            $props_n_values,
56
-            __CLASS__,
57
-            $timezone,
58
-            $date_formats
59
-        );
60
-        return $has_object
61
-            ? $has_object
62
-            : new self($props_n_values, false, $timezone);
63
-    }
64
-
65
-
66
-    /**
67
-     * @param array  $props_n_values  incoming values from the database
68
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
69
-     *                                the website will be used.
70
-     * @return EE_Line_Item
71
-     * @throws EE_Error
72
-     * @throws InvalidArgumentException
73
-     * @throws InvalidDataTypeException
74
-     * @throws InvalidInterfaceException
75
-     * @throws ReflectionException
76
-     */
77
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
-    {
79
-        return new self($props_n_values, true, $timezone);
80
-    }
81
-
82
-
83
-    /**
84
-     * Adds some defaults if they're not specified
85
-     *
86
-     * @param array  $fieldValues
87
-     * @param bool   $bydb
88
-     * @param string $timezone
89
-     * @throws EE_Error
90
-     * @throws InvalidArgumentException
91
-     * @throws InvalidDataTypeException
92
-     * @throws InvalidInterfaceException
93
-     * @throws ReflectionException
94
-     */
95
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
96
-    {
97
-        $this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
98
-        parent::__construct($fieldValues, $bydb, $timezone);
99
-        if (! $this->get('LIN_code')) {
100
-            $this->set_code($this->generate_code());
101
-        }
102
-    }
103
-
104
-
105
-    /**
106
-     * Gets ID
107
-     *
108
-     * @return int
109
-     * @throws EE_Error
110
-     * @throws InvalidArgumentException
111
-     * @throws InvalidDataTypeException
112
-     * @throws InvalidInterfaceException
113
-     * @throws ReflectionException
114
-     */
115
-    public function ID()
116
-    {
117
-        return $this->get('LIN_ID');
118
-    }
119
-
120
-
121
-    /**
122
-     * Gets TXN_ID
123
-     *
124
-     * @return int
125
-     * @throws EE_Error
126
-     * @throws InvalidArgumentException
127
-     * @throws InvalidDataTypeException
128
-     * @throws InvalidInterfaceException
129
-     * @throws ReflectionException
130
-     */
131
-    public function TXN_ID()
132
-    {
133
-        return $this->get('TXN_ID');
134
-    }
135
-
136
-
137
-    /**
138
-     * Sets TXN_ID
139
-     *
140
-     * @param int $TXN_ID
141
-     * @throws EE_Error
142
-     * @throws InvalidArgumentException
143
-     * @throws InvalidDataTypeException
144
-     * @throws InvalidInterfaceException
145
-     * @throws ReflectionException
146
-     */
147
-    public function set_TXN_ID($TXN_ID)
148
-    {
149
-        $this->set('TXN_ID', $TXN_ID);
150
-    }
151
-
152
-
153
-    /**
154
-     * Gets name
155
-     *
156
-     * @return string
157
-     * @throws EE_Error
158
-     * @throws InvalidArgumentException
159
-     * @throws InvalidDataTypeException
160
-     * @throws InvalidInterfaceException
161
-     * @throws ReflectionException
162
-     */
163
-    public function name()
164
-    {
165
-        $name = $this->get('LIN_name');
166
-        if (! $name) {
167
-            $name = ucwords(str_replace('-', ' ', $this->type()));
168
-        }
169
-        return $name;
170
-    }
171
-
172
-
173
-    /**
174
-     * Sets name
175
-     *
176
-     * @param string $name
177
-     * @throws EE_Error
178
-     * @throws InvalidArgumentException
179
-     * @throws InvalidDataTypeException
180
-     * @throws InvalidInterfaceException
181
-     * @throws ReflectionException
182
-     */
183
-    public function set_name($name)
184
-    {
185
-        $this->set('LIN_name', $name);
186
-    }
187
-
188
-
189
-    /**
190
-     * Gets desc
191
-     *
192
-     * @return string
193
-     * @throws EE_Error
194
-     * @throws InvalidArgumentException
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws ReflectionException
198
-     */
199
-    public function desc()
200
-    {
201
-        return $this->get('LIN_desc');
202
-    }
203
-
204
-
205
-    /**
206
-     * Sets desc
207
-     *
208
-     * @param string $desc
209
-     * @throws EE_Error
210
-     * @throws InvalidArgumentException
211
-     * @throws InvalidDataTypeException
212
-     * @throws InvalidInterfaceException
213
-     * @throws ReflectionException
214
-     */
215
-    public function set_desc($desc)
216
-    {
217
-        $this->set('LIN_desc', $desc);
218
-    }
219
-
220
-
221
-    /**
222
-     * Gets quantity
223
-     *
224
-     * @return int
225
-     * @throws EE_Error
226
-     * @throws InvalidArgumentException
227
-     * @throws InvalidDataTypeException
228
-     * @throws InvalidInterfaceException
229
-     * @throws ReflectionException
230
-     */
231
-    public function quantity(): int
232
-    {
233
-        return (int) $this->get('LIN_quantity');
234
-    }
235
-
236
-
237
-    /**
238
-     * Sets quantity
239
-     *
240
-     * @param int $quantity
241
-     * @throws EE_Error
242
-     * @throws InvalidArgumentException
243
-     * @throws InvalidDataTypeException
244
-     * @throws InvalidInterfaceException
245
-     * @throws ReflectionException
246
-     */
247
-    public function set_quantity($quantity)
248
-    {
249
-        $this->set('LIN_quantity', max($quantity, 0));
250
-    }
251
-
252
-
253
-    /**
254
-     * Gets item_id
255
-     *
256
-     * @return string
257
-     * @throws EE_Error
258
-     * @throws InvalidArgumentException
259
-     * @throws InvalidDataTypeException
260
-     * @throws InvalidInterfaceException
261
-     * @throws ReflectionException
262
-     */
263
-    public function OBJ_ID()
264
-    {
265
-        return $this->get('OBJ_ID');
266
-    }
267
-
268
-
269
-    /**
270
-     * Sets item_id
271
-     *
272
-     * @param string $item_id
273
-     * @throws EE_Error
274
-     * @throws InvalidArgumentException
275
-     * @throws InvalidDataTypeException
276
-     * @throws InvalidInterfaceException
277
-     * @throws ReflectionException
278
-     */
279
-    public function set_OBJ_ID($item_id)
280
-    {
281
-        $this->set('OBJ_ID', $item_id);
282
-    }
283
-
284
-
285
-    /**
286
-     * Gets item_type
287
-     *
288
-     * @return string
289
-     * @throws EE_Error
290
-     * @throws InvalidArgumentException
291
-     * @throws InvalidDataTypeException
292
-     * @throws InvalidInterfaceException
293
-     * @throws ReflectionException
294
-     */
295
-    public function OBJ_type()
296
-    {
297
-        return $this->get('OBJ_type');
298
-    }
299
-
300
-
301
-    /**
302
-     * Gets item_type
303
-     *
304
-     * @return string
305
-     * @throws EE_Error
306
-     * @throws InvalidArgumentException
307
-     * @throws InvalidDataTypeException
308
-     * @throws InvalidInterfaceException
309
-     * @throws ReflectionException
310
-     */
311
-    public function OBJ_type_i18n()
312
-    {
313
-        $obj_type = $this->OBJ_type();
314
-        switch ($obj_type) {
315
-            case EEM_Line_Item::OBJ_TYPE_EVENT:
316
-                $obj_type = esc_html__('Event', 'event_espresso');
317
-                break;
318
-            case EEM_Line_Item::OBJ_TYPE_PRICE:
319
-                $obj_type = esc_html__('Price', 'event_espresso');
320
-                break;
321
-            case EEM_Line_Item::OBJ_TYPE_PROMOTION:
322
-                $obj_type = esc_html__('Promotion', 'event_espresso');
323
-                break;
324
-            case EEM_Line_Item::OBJ_TYPE_TICKET:
325
-                $obj_type = esc_html__('Ticket', 'event_espresso');
326
-                break;
327
-            case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
328
-                $obj_type = esc_html__('Transaction', 'event_espresso');
329
-                break;
330
-        }
331
-        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
332
-    }
333
-
334
-
335
-    /**
336
-     * Sets item_type
337
-     *
338
-     * @param string $OBJ_type
339
-     * @throws EE_Error
340
-     * @throws InvalidArgumentException
341
-     * @throws InvalidDataTypeException
342
-     * @throws InvalidInterfaceException
343
-     * @throws ReflectionException
344
-     */
345
-    public function set_OBJ_type($OBJ_type)
346
-    {
347
-        $this->set('OBJ_type', $OBJ_type);
348
-    }
349
-
350
-
351
-    /**
352
-     * Gets unit_price
353
-     *
354
-     * @return float
355
-     * @throws EE_Error
356
-     * @throws InvalidArgumentException
357
-     * @throws InvalidDataTypeException
358
-     * @throws InvalidInterfaceException
359
-     * @throws ReflectionException
360
-     */
361
-    public function unit_price()
362
-    {
363
-        return $this->get('LIN_unit_price');
364
-    }
365
-
366
-
367
-    /**
368
-     * Sets unit_price
369
-     *
370
-     * @param float $unit_price
371
-     * @throws EE_Error
372
-     * @throws InvalidArgumentException
373
-     * @throws InvalidDataTypeException
374
-     * @throws InvalidInterfaceException
375
-     * @throws ReflectionException
376
-     */
377
-    public function set_unit_price($unit_price)
378
-    {
379
-        $this->set('LIN_unit_price', $unit_price);
380
-    }
381
-
382
-
383
-    /**
384
-     * Checks if this item is a percentage modifier or not
385
-     *
386
-     * @return boolean
387
-     * @throws EE_Error
388
-     * @throws InvalidArgumentException
389
-     * @throws InvalidDataTypeException
390
-     * @throws InvalidInterfaceException
391
-     * @throws ReflectionException
392
-     */
393
-    public function is_percent()
394
-    {
395
-        if ($this->is_tax_sub_total()) {
396
-            // tax subtotals HAVE a percent on them, that percentage only applies
397
-            // to taxable items, so its' an exception. Treat it like a flat line item
398
-            return false;
399
-        }
400
-        $unit_price = abs($this->get('LIN_unit_price'));
401
-        $percent = abs($this->get('LIN_percent'));
402
-        if ($unit_price < .001 && $percent) {
403
-            return true;
404
-        }
405
-        if ($unit_price >= .001 && ! $percent) {
406
-            return false;
407
-        }
408
-        if ($unit_price >= .001 && $percent) {
409
-            throw new EE_Error(
410
-                sprintf(
411
-                    esc_html__(
412
-                        'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
413
-                        'event_espresso'
414
-                    ),
415
-                    $unit_price,
416
-                    $percent
417
-                )
418
-            );
419
-        }
420
-        // if they're both 0, assume its not a percent item
421
-        return false;
422
-    }
423
-
424
-
425
-    /**
426
-     * Gets percent (between 100-.001)
427
-     *
428
-     * @return float
429
-     * @throws EE_Error
430
-     * @throws InvalidArgumentException
431
-     * @throws InvalidDataTypeException
432
-     * @throws InvalidInterfaceException
433
-     * @throws ReflectionException
434
-     */
435
-    public function percent()
436
-    {
437
-        return $this->get('LIN_percent');
438
-    }
439
-
440
-
441
-    /**
442
-     * Sets percent (between 100-0.01)
443
-     *
444
-     * @param float $percent
445
-     * @throws EE_Error
446
-     * @throws InvalidArgumentException
447
-     * @throws InvalidDataTypeException
448
-     * @throws InvalidInterfaceException
449
-     * @throws ReflectionException
450
-     */
451
-    public function set_percent($percent)
452
-    {
453
-        $this->set('LIN_percent', $percent);
454
-    }
455
-
456
-
457
-    /**
458
-     * Gets total
459
-     *
460
-     * @return float
461
-     * @throws EE_Error
462
-     * @throws InvalidArgumentException
463
-     * @throws InvalidDataTypeException
464
-     * @throws InvalidInterfaceException
465
-     * @throws ReflectionException
466
-     */
467
-    public function pretaxTotal(): float
468
-    {
469
-        return $this->get('LIN_pretax');
470
-    }
471
-
472
-
473
-    /**
474
-     * Sets total
475
-     *
476
-     * @param float $pretax_total
477
-     * @throws EE_Error
478
-     * @throws InvalidArgumentException
479
-     * @throws InvalidDataTypeException
480
-     * @throws InvalidInterfaceException
481
-     * @throws ReflectionException
482
-     */
483
-    public function setPretaxTotal(float $pretax_total)
484
-    {
485
-        $this->set('LIN_pretax', $pretax_total);
486
-    }
487
-
488
-
489
-    /**
490
-     * @return float
491
-     * @throws EE_Error
492
-     * @throws ReflectionException
493
-     * @since  $VID:$
494
-     */
495
-    public function totalWithTax(): float
496
-    {
497
-        return $this->get('LIN_total');
498
-    }
499
-
500
-
501
-    /**
502
-     * Sets total
503
-     *
504
-     * @param float $total
505
-     * @throws EE_Error
506
-     * @throws ReflectionException
507
-     * @since  $VID:$
508
-     */
509
-    public function setTotalWithTax(float $total)
510
-    {
511
-        $this->set('LIN_total', $total);
512
-    }
513
-
514
-
515
-    /**
516
-     * Gets total
517
-     *
518
-     * @return float
519
-     * @throws EE_Error
520
-     * @throws ReflectionException
521
-     * @deprecatd $VID:$
522
-     */
523
-    public function total(): float
524
-    {
525
-        return $this->totalWithTax();
526
-    }
527
-
528
-
529
-    /**
530
-     * Sets total
531
-     *
532
-     * @param float $total
533
-     * @throws EE_Error
534
-     * @throws ReflectionException
535
-     * @deprecatd $VID:$
536
-     */
537
-    public function set_total($total)
538
-    {
539
-        $this->setTotalWithTax($total);
540
-    }
541
-
542
-
543
-    /**
544
-     * Gets order
545
-     *
546
-     * @return int
547
-     * @throws EE_Error
548
-     * @throws InvalidArgumentException
549
-     * @throws InvalidDataTypeException
550
-     * @throws InvalidInterfaceException
551
-     * @throws ReflectionException
552
-     */
553
-    public function order()
554
-    {
555
-        return $this->get('LIN_order');
556
-    }
557
-
558
-
559
-    /**
560
-     * Sets order
561
-     *
562
-     * @param int $order
563
-     * @throws EE_Error
564
-     * @throws InvalidArgumentException
565
-     * @throws InvalidDataTypeException
566
-     * @throws InvalidInterfaceException
567
-     * @throws ReflectionException
568
-     */
569
-    public function set_order($order)
570
-    {
571
-        $this->set('LIN_order', $order);
572
-    }
573
-
574
-
575
-    /**
576
-     * Gets parent
577
-     *
578
-     * @return int
579
-     * @throws EE_Error
580
-     * @throws InvalidArgumentException
581
-     * @throws InvalidDataTypeException
582
-     * @throws InvalidInterfaceException
583
-     * @throws ReflectionException
584
-     */
585
-    public function parent_ID()
586
-    {
587
-        return $this->get('LIN_parent');
588
-    }
589
-
590
-
591
-    /**
592
-     * Sets parent
593
-     *
594
-     * @param int $parent
595
-     * @throws EE_Error
596
-     * @throws InvalidArgumentException
597
-     * @throws InvalidDataTypeException
598
-     * @throws InvalidInterfaceException
599
-     * @throws ReflectionException
600
-     */
601
-    public function set_parent_ID($parent)
602
-    {
603
-        $this->set('LIN_parent', $parent);
604
-    }
605
-
606
-
607
-    /**
608
-     * Gets type
609
-     *
610
-     * @return string
611
-     * @throws EE_Error
612
-     * @throws InvalidArgumentException
613
-     * @throws InvalidDataTypeException
614
-     * @throws InvalidInterfaceException
615
-     * @throws ReflectionException
616
-     */
617
-    public function type()
618
-    {
619
-        return $this->get('LIN_type');
620
-    }
621
-
622
-
623
-    /**
624
-     * Sets type
625
-     *
626
-     * @param string $type
627
-     * @throws EE_Error
628
-     * @throws InvalidArgumentException
629
-     * @throws InvalidDataTypeException
630
-     * @throws InvalidInterfaceException
631
-     * @throws ReflectionException
632
-     */
633
-    public function set_type($type)
634
-    {
635
-        $this->set('LIN_type', $type);
636
-    }
637
-
638
-
639
-    /**
640
-     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
641
-     * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
642
-     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
643
-     * or indirectly by `EE_Line_item::add_child_line_item()`)
644
-     *
645
-     * @return EE_Base_Class|EE_Line_Item
646
-     * @throws EE_Error
647
-     * @throws InvalidArgumentException
648
-     * @throws InvalidDataTypeException
649
-     * @throws InvalidInterfaceException
650
-     * @throws ReflectionException
651
-     */
652
-    public function parent()
653
-    {
654
-        return $this->ID()
655
-            ? $this->get_model()->get_one_by_ID($this->parent_ID())
656
-            : $this->_parent;
657
-    }
658
-
659
-
660
-    /**
661
-     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
662
-     *
663
-     * @return EE_Line_Item[]
664
-     * @throws EE_Error
665
-     * @throws InvalidArgumentException
666
-     * @throws InvalidDataTypeException
667
-     * @throws InvalidInterfaceException
668
-     * @throws ReflectionException
669
-     */
670
-    public function children(array $query_params = []): array
671
-    {
672
-        if ($this->ID()) {
673
-            // ensure where params are an array
674
-            $query_params[0] = $query_params[0] ?? [];
675
-            // add defaults for line item parent and orderby
676
-            $query_params[0] += ['LIN_parent' => $this->ID()];
677
-            $query_params += ['order_by' => ['LIN_order' => 'ASC']];
678
-            return $this->get_model()->get_all($query_params);
679
-        }
680
-        if (! is_array($this->_children)) {
681
-            $this->_children = array();
682
-        }
683
-        return $this->_children;
684
-    }
685
-
686
-
687
-    /**
688
-     * Gets code
689
-     *
690
-     * @return string
691
-     * @throws EE_Error
692
-     * @throws InvalidArgumentException
693
-     * @throws InvalidDataTypeException
694
-     * @throws InvalidInterfaceException
695
-     * @throws ReflectionException
696
-     */
697
-    public function code()
698
-    {
699
-        return $this->get('LIN_code');
700
-    }
701
-
702
-
703
-    /**
704
-     * Sets code
705
-     *
706
-     * @param string $code
707
-     * @throws EE_Error
708
-     * @throws InvalidArgumentException
709
-     * @throws InvalidDataTypeException
710
-     * @throws InvalidInterfaceException
711
-     * @throws ReflectionException
712
-     */
713
-    public function set_code($code)
714
-    {
715
-        $this->set('LIN_code', $code);
716
-    }
717
-
718
-
719
-    /**
720
-     * Gets is_taxable
721
-     *
722
-     * @return boolean
723
-     * @throws EE_Error
724
-     * @throws InvalidArgumentException
725
-     * @throws InvalidDataTypeException
726
-     * @throws InvalidInterfaceException
727
-     * @throws ReflectionException
728
-     */
729
-    public function is_taxable()
730
-    {
731
-        return $this->get('LIN_is_taxable');
732
-    }
733
-
734
-
735
-    /**
736
-     * Sets is_taxable
737
-     *
738
-     * @param boolean $is_taxable
739
-     * @throws EE_Error
740
-     * @throws InvalidArgumentException
741
-     * @throws InvalidDataTypeException
742
-     * @throws InvalidInterfaceException
743
-     * @throws ReflectionException
744
-     */
745
-    public function set_is_taxable($is_taxable)
746
-    {
747
-        $this->set('LIN_is_taxable', $is_taxable);
748
-    }
749
-
750
-
751
-    /**
752
-     * Gets the object that this model-joins-to.
753
-     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
754
-     * EEM_Promotion_Object
755
-     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
756
-     *
757
-     * @return EE_Base_Class | NULL
758
-     * @throws EE_Error
759
-     * @throws InvalidArgumentException
760
-     * @throws InvalidDataTypeException
761
-     * @throws InvalidInterfaceException
762
-     * @throws ReflectionException
763
-     */
764
-    public function get_object()
765
-    {
766
-        $model_name_of_related_obj = $this->OBJ_type();
767
-        return $this->get_model()->has_relation($model_name_of_related_obj)
768
-            ? $this->get_first_related($model_name_of_related_obj)
769
-            : null;
770
-    }
771
-
772
-
773
-    /**
774
-     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
775
-     * (IE, if this line item is for a price or something else, will return NULL)
776
-     *
777
-     * @param array $query_params
778
-     * @return EE_Base_Class|EE_Ticket
779
-     * @throws EE_Error
780
-     * @throws InvalidArgumentException
781
-     * @throws InvalidDataTypeException
782
-     * @throws InvalidInterfaceException
783
-     * @throws ReflectionException
784
-     */
785
-    public function ticket($query_params = array())
786
-    {
787
-        // we're going to assume that when this method is called
788
-        // we always want to receive the attached ticket EVEN if that ticket is archived.
789
-        // This can be overridden via the incoming $query_params argument
790
-        $remove_defaults = array('default_where_conditions' => 'none');
791
-        $query_params = array_merge($remove_defaults, $query_params);
792
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
793
-    }
794
-
795
-
796
-    /**
797
-     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
798
-     *
799
-     * @return EE_Datetime | NULL
800
-     * @throws EE_Error
801
-     * @throws InvalidArgumentException
802
-     * @throws InvalidDataTypeException
803
-     * @throws InvalidInterfaceException
804
-     * @throws ReflectionException
805
-     */
806
-    public function get_ticket_datetime()
807
-    {
808
-        if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
809
-            $ticket = $this->ticket();
810
-            if ($ticket instanceof EE_Ticket) {
811
-                $datetime = $ticket->first_datetime();
812
-                if ($datetime instanceof EE_Datetime) {
813
-                    return $datetime;
814
-                }
815
-            }
816
-        }
817
-        return null;
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets the event's name that's related to the ticket, if this is for
823
-     * a ticket
824
-     *
825
-     * @return string
826
-     * @throws EE_Error
827
-     * @throws InvalidArgumentException
828
-     * @throws InvalidDataTypeException
829
-     * @throws InvalidInterfaceException
830
-     * @throws ReflectionException
831
-     */
832
-    public function ticket_event_name()
833
-    {
834
-        $event_name = esc_html__('Unknown', 'event_espresso');
835
-        $event = $this->ticket_event();
836
-        if ($event instanceof EE_Event) {
837
-            $event_name = $event->name();
838
-        }
839
-        return $event_name;
840
-    }
841
-
842
-
843
-    /**
844
-     * Gets the event that's related to the ticket, if this line item represents a ticket.
845
-     *
846
-     * @return EE_Event|null
847
-     * @throws EE_Error
848
-     * @throws InvalidArgumentException
849
-     * @throws InvalidDataTypeException
850
-     * @throws InvalidInterfaceException
851
-     * @throws ReflectionException
852
-     */
853
-    public function ticket_event()
854
-    {
855
-        $event = null;
856
-        $ticket = $this->ticket();
857
-        if ($ticket instanceof EE_Ticket) {
858
-            $datetime = $ticket->first_datetime();
859
-            if ($datetime instanceof EE_Datetime) {
860
-                $event = $datetime->event();
861
-            }
862
-        }
863
-        return $event;
864
-    }
865
-
866
-
867
-    /**
868
-     * Gets the first datetime for this lien item, assuming it's for a ticket
869
-     *
870
-     * @param string $date_format
871
-     * @param string $time_format
872
-     * @return string
873
-     * @throws EE_Error
874
-     * @throws InvalidArgumentException
875
-     * @throws InvalidDataTypeException
876
-     * @throws InvalidInterfaceException
877
-     * @throws ReflectionException
878
-     */
879
-    public function ticket_datetime_start($date_format = '', $time_format = '')
880
-    {
881
-        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
882
-        $datetime = $this->get_ticket_datetime();
883
-        if ($datetime) {
884
-            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
885
-        }
886
-        return $first_datetime_string;
887
-    }
888
-
889
-
890
-    /**
891
-     * Adds the line item as a child to this line item. If there is another child line
892
-     * item with the same LIN_code, it is overwritten by this new one
893
-     *
894
-     * @param EE_Line_Item $line_item
895
-     * @param bool          $set_order
896
-     * @return bool success
897
-     * @throws EE_Error
898
-     * @throws InvalidArgumentException
899
-     * @throws InvalidDataTypeException
900
-     * @throws InvalidInterfaceException
901
-     * @throws ReflectionException
902
-     */
903
-    public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
904
-    {
905
-        // should we calculate the LIN_order for this line item ?
906
-        if ($set_order || $line_item->order() === null) {
907
-            $line_item->set_order(count($this->children()));
908
-        }
909
-        if ($this->ID()) {
910
-            // check for any duplicate line items (with the same code), if so, this replaces it
911
-            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
912
-            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
913
-                $this->delete_child_line_item($line_item_with_same_code->code());
914
-            }
915
-            $line_item->set_parent_ID($this->ID());
916
-            if ($this->TXN_ID()) {
917
-                $line_item->set_TXN_ID($this->TXN_ID());
918
-            }
919
-            return $line_item->save();
920
-        }
921
-        $this->_children[ $line_item->code() ] = $line_item;
922
-        if ($line_item->parent() !== $this) {
923
-            $line_item->set_parent($this);
924
-        }
925
-        return true;
926
-    }
927
-
928
-
929
-    /**
930
-     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
931
-     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
932
-     * However, if this line item is NOT saved to the DB, this just caches the parent on
933
-     * the EE_Line_Item::_parent property.
934
-     *
935
-     * @param EE_Line_Item $line_item
936
-     * @throws EE_Error
937
-     * @throws InvalidArgumentException
938
-     * @throws InvalidDataTypeException
939
-     * @throws InvalidInterfaceException
940
-     * @throws ReflectionException
941
-     */
942
-    public function set_parent($line_item)
943
-    {
944
-        if ($this->ID()) {
945
-            if (! $line_item->ID()) {
946
-                $line_item->save();
947
-            }
948
-            $this->set_parent_ID($line_item->ID());
949
-            $this->save();
950
-        } else {
951
-            $this->_parent = $line_item;
952
-            $this->set_parent_ID($line_item->ID());
953
-        }
954
-    }
955
-
956
-
957
-    /**
958
-     * Gets the child line item as specified by its code. Because this returns an object (by reference)
959
-     * you can modify this child line item and the parent (this object) can know about them
960
-     * because it also has a reference to that line item
961
-     *
962
-     * @param string $code
963
-     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
964
-     * @throws EE_Error
965
-     * @throws InvalidArgumentException
966
-     * @throws InvalidDataTypeException
967
-     * @throws InvalidInterfaceException
968
-     * @throws ReflectionException
969
-     */
970
-    public function get_child_line_item($code)
971
-    {
972
-        if ($this->ID()) {
973
-            return $this->get_model()->get_one(
974
-                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
975
-            );
976
-        }
977
-        return isset($this->_children[ $code ])
978
-            ? $this->_children[ $code ]
979
-            : null;
980
-    }
981
-
982
-
983
-    /**
984
-     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
985
-     * cached on it)
986
-     *
987
-     * @return int
988
-     * @throws EE_Error
989
-     * @throws InvalidArgumentException
990
-     * @throws InvalidDataTypeException
991
-     * @throws InvalidInterfaceException
992
-     * @throws ReflectionException
993
-     */
994
-    public function delete_children_line_items()
995
-    {
996
-        if ($this->ID()) {
997
-            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
998
-        }
999
-        $count = count($this->_children);
1000
-        $this->_children = array();
1001
-        return $count;
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
1007
-     * HAS NOT been saved to the DB, removes the child line item with index $code.
1008
-     * Also searches through the child's children for a matching line item. However, once a line item has been found
1009
-     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
1010
-     * deleted)
1011
-     *
1012
-     * @param string $code
1013
-     * @param bool   $stop_search_once_found
1014
-     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
1015
-     *             the DB yet)
1016
-     * @throws EE_Error
1017
-     * @throws InvalidArgumentException
1018
-     * @throws InvalidDataTypeException
1019
-     * @throws InvalidInterfaceException
1020
-     * @throws ReflectionException
1021
-     */
1022
-    public function delete_child_line_item($code, $stop_search_once_found = true)
1023
-    {
1024
-        if ($this->ID()) {
1025
-            $items_deleted = 0;
1026
-            if ($this->code() === $code) {
1027
-                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
1028
-                $items_deleted += (int) $this->delete();
1029
-                if ($stop_search_once_found) {
1030
-                    return $items_deleted;
1031
-                }
1032
-            }
1033
-            foreach ($this->children() as $child_line_item) {
1034
-                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1035
-            }
1036
-            return $items_deleted;
1037
-        }
1038
-        if (isset($this->_children[ $code ])) {
1039
-            unset($this->_children[ $code ]);
1040
-            return 1;
1041
-        }
1042
-        return 0;
1043
-    }
1044
-
1045
-
1046
-    /**
1047
-     * If this line item is in the database, is of the type subtotal, and
1048
-     * has no children, why do we have it? It should be deleted so this function
1049
-     * does that
1050
-     *
1051
-     * @return boolean
1052
-     * @throws EE_Error
1053
-     * @throws InvalidArgumentException
1054
-     * @throws InvalidDataTypeException
1055
-     * @throws InvalidInterfaceException
1056
-     * @throws ReflectionException
1057
-     */
1058
-    public function delete_if_childless_subtotal()
1059
-    {
1060
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1061
-            return $this->delete();
1062
-        }
1063
-        return false;
1064
-    }
1065
-
1066
-
1067
-    /**
1068
-     * Creates a code and returns a string. doesn't assign the code to this model object
1069
-     *
1070
-     * @return string
1071
-     * @throws EE_Error
1072
-     * @throws InvalidArgumentException
1073
-     * @throws InvalidDataTypeException
1074
-     * @throws InvalidInterfaceException
1075
-     * @throws ReflectionException
1076
-     */
1077
-    public function generate_code()
1078
-    {
1079
-        // each line item in the cart requires a unique identifier
1080
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1081
-    }
1082
-
1083
-
1084
-    /**
1085
-     * @return bool
1086
-     * @throws EE_Error
1087
-     * @throws InvalidArgumentException
1088
-     * @throws InvalidDataTypeException
1089
-     * @throws InvalidInterfaceException
1090
-     * @throws ReflectionException
1091
-     */
1092
-    public function isGlobalTax(): bool
1093
-    {
1094
-        return $this->type() === EEM_Line_Item::type_tax;
1095
-    }
1096
-
1097
-
1098
-    /**
1099
-     * @return bool
1100
-     * @throws EE_Error
1101
-     * @throws InvalidArgumentException
1102
-     * @throws InvalidDataTypeException
1103
-     * @throws InvalidInterfaceException
1104
-     * @throws ReflectionException
1105
-     */
1106
-    public function isSubTax(): bool
1107
-    {
1108
-        return $this->type() === EEM_Line_Item::type_sub_tax;
1109
-    }
1110
-
1111
-
1112
-    /**
1113
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1114
-     *
1115
-     * @return array
1116
-     * @throws EE_Error
1117
-     * @throws InvalidArgumentException
1118
-     * @throws InvalidDataTypeException
1119
-     * @throws InvalidInterfaceException
1120
-     * @throws ReflectionException
1121
-     */
1122
-    public function getSubTaxes(): array
1123
-    {
1124
-        if (! $this->is_line_item()) {
1125
-            return [];
1126
-        }
1127
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1128
-    }
1129
-
1130
-
1131
-    /**
1132
-     * returns true if this is a line item with a direct descendent of the type sub-tax
1133
-     *
1134
-     * @return bool
1135
-     * @throws EE_Error
1136
-     * @throws InvalidArgumentException
1137
-     * @throws InvalidDataTypeException
1138
-     * @throws InvalidInterfaceException
1139
-     * @throws ReflectionException
1140
-     */
1141
-    public function hasSubTaxes(): bool
1142
-    {
1143
-        if (! $this->is_line_item()) {
1144
-            return false;
1145
-        }
1146
-        $sub_taxes = $this->getSubTaxes();
1147
-        return ! empty($sub_taxes);
1148
-    }
1149
-
1150
-
1151
-    /**
1152
-     * @return bool
1153
-     * @throws EE_Error
1154
-     * @throws ReflectionException
1155
-     * @deprecated   $VID:$
1156
-     */
1157
-    public function is_tax(): bool
1158
-    {
1159
-        return $this->isGlobalTax();
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * @return bool
1165
-     * @throws EE_Error
1166
-     * @throws InvalidArgumentException
1167
-     * @throws InvalidDataTypeException
1168
-     * @throws InvalidInterfaceException
1169
-     * @throws ReflectionException
1170
-     */
1171
-    public function is_tax_sub_total()
1172
-    {
1173
-        return $this->type() === EEM_Line_Item::type_tax_sub_total;
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * @return bool
1179
-     * @throws EE_Error
1180
-     * @throws InvalidArgumentException
1181
-     * @throws InvalidDataTypeException
1182
-     * @throws InvalidInterfaceException
1183
-     * @throws ReflectionException
1184
-     */
1185
-    public function is_line_item()
1186
-    {
1187
-        return $this->type() === EEM_Line_Item::type_line_item;
1188
-    }
1189
-
1190
-
1191
-    /**
1192
-     * @return bool
1193
-     * @throws EE_Error
1194
-     * @throws InvalidArgumentException
1195
-     * @throws InvalidDataTypeException
1196
-     * @throws InvalidInterfaceException
1197
-     * @throws ReflectionException
1198
-     */
1199
-    public function is_sub_line_item()
1200
-    {
1201
-        return $this->type() === EEM_Line_Item::type_sub_line_item;
1202
-    }
1203
-
1204
-
1205
-    /**
1206
-     * @return bool
1207
-     * @throws EE_Error
1208
-     * @throws InvalidArgumentException
1209
-     * @throws InvalidDataTypeException
1210
-     * @throws InvalidInterfaceException
1211
-     * @throws ReflectionException
1212
-     */
1213
-    public function is_sub_total()
1214
-    {
1215
-        return $this->type() === EEM_Line_Item::type_sub_total;
1216
-    }
1217
-
1218
-
1219
-    /**
1220
-     * Whether or not this line item is a cancellation line item
1221
-     *
1222
-     * @return boolean
1223
-     * @throws EE_Error
1224
-     * @throws InvalidArgumentException
1225
-     * @throws InvalidDataTypeException
1226
-     * @throws InvalidInterfaceException
1227
-     * @throws ReflectionException
1228
-     */
1229
-    public function is_cancellation()
1230
-    {
1231
-        return EEM_Line_Item::type_cancellation === $this->type();
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * @return bool
1237
-     * @throws EE_Error
1238
-     * @throws InvalidArgumentException
1239
-     * @throws InvalidDataTypeException
1240
-     * @throws InvalidInterfaceException
1241
-     * @throws ReflectionException
1242
-     */
1243
-    public function is_total()
1244
-    {
1245
-        return $this->type() === EEM_Line_Item::type_total;
1246
-    }
1247
-
1248
-
1249
-    /**
1250
-     * @return bool
1251
-     * @throws EE_Error
1252
-     * @throws InvalidArgumentException
1253
-     * @throws InvalidDataTypeException
1254
-     * @throws InvalidInterfaceException
1255
-     * @throws ReflectionException
1256
-     */
1257
-    public function is_cancelled()
1258
-    {
1259
-        return $this->type() === EEM_Line_Item::type_cancellation;
1260
-    }
1261
-
1262
-
1263
-    /**
1264
-     * @return string like '2, 004.00', formatted according to the localized currency
1265
-     * @throws EE_Error
1266
-     * @throws InvalidArgumentException
1267
-     * @throws InvalidDataTypeException
1268
-     * @throws InvalidInterfaceException
1269
-     * @throws ReflectionException
1270
-     */
1271
-    public function unit_price_no_code()
1272
-    {
1273
-        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1274
-    }
1275
-
1276
-
1277
-    /**
1278
-     * @return string like '2, 004.00', formatted according to the localized currency
1279
-     * @throws EE_Error
1280
-     * @throws InvalidArgumentException
1281
-     * @throws InvalidDataTypeException
1282
-     * @throws InvalidInterfaceException
1283
-     * @throws ReflectionException
1284
-     */
1285
-    public function total_no_code()
1286
-    {
1287
-        return $this->get_pretty('LIN_total', 'no_currency_code');
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * Gets the final total on this item, taking taxes into account.
1293
-     * Has the side-effect of setting the sub-total as it was just calculated.
1294
-     * If this is used on a grand-total line item, also updates the transaction's
1295
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
1296
-     * want to change a persistable transaction with info from a non-persistent line item)
1297
-     *
1298
-     * @param bool $update_txn_status
1299
-     * @return float
1300
-     * @throws EE_Error
1301
-     * @throws InvalidArgumentException
1302
-     * @throws InvalidDataTypeException
1303
-     * @throws InvalidInterfaceException
1304
-     * @throws ReflectionException
1305
-     * @throws RuntimeException
1306
-     */
1307
-    public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1308
-    {
1309
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1310
-        return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1311
-    }
1312
-
1313
-
1314
-    /**
1315
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1316
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1317
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1318
-     * when this is called on the grand total
1319
-     *
1320
-     * @return float
1321
-     * @throws EE_Error
1322
-     * @throws InvalidArgumentException
1323
-     * @throws InvalidDataTypeException
1324
-     * @throws InvalidInterfaceException
1325
-     * @throws ReflectionException
1326
-     */
1327
-    public function recalculate_pre_tax_total(): float
1328
-    {
1329
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1330
-        [$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1331
-        return $total;
1332
-    }
1333
-
1334
-
1335
-    /**
1336
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1337
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1338
-     * and tax sub-total if already in the DB
1339
-     *
1340
-     * @return float
1341
-     * @throws EE_Error
1342
-     * @throws InvalidArgumentException
1343
-     * @throws InvalidDataTypeException
1344
-     * @throws InvalidInterfaceException
1345
-     * @throws ReflectionException
1346
-     */
1347
-    public function recalculate_taxes_and_tax_total(): float
1348
-    {
1349
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1350
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1356
-     * recalculate_taxes_and_total
1357
-     *
1358
-     * @return float
1359
-     * @throws EE_Error
1360
-     * @throws InvalidArgumentException
1361
-     * @throws InvalidDataTypeException
1362
-     * @throws InvalidInterfaceException
1363
-     * @throws ReflectionException
1364
-     */
1365
-    public function get_total_tax()
1366
-    {
1367
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1368
-        return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1369
-    }
1370
-
1371
-
1372
-    /**
1373
-     * Gets the total for all the items purchased only
1374
-     *
1375
-     * @return float
1376
-     * @throws EE_Error
1377
-     * @throws InvalidArgumentException
1378
-     * @throws InvalidDataTypeException
1379
-     * @throws InvalidInterfaceException
1380
-     * @throws ReflectionException
1381
-     */
1382
-    public function get_items_total()
1383
-    {
1384
-        // by default, let's make sure we're consistent with the existing line item
1385
-        if ($this->is_total()) {
1386
-            return $this->pretaxTotal();
1387
-        }
1388
-        $total = 0;
1389
-        foreach ($this->get_items() as $item) {
1390
-            if ($item instanceof EE_Line_Item) {
1391
-                $total += $item->pretaxTotal();
1392
-            }
1393
-        }
1394
-        return $total;
1395
-    }
1396
-
1397
-
1398
-    /**
1399
-     * Gets all the descendants (ie, children or children of children etc) that
1400
-     * are of the type 'tax'
1401
-     *
1402
-     * @return EE_Line_Item[]
1403
-     * @throws EE_Error
1404
-     */
1405
-    public function tax_descendants()
1406
-    {
1407
-        return EEH_Line_Item::get_tax_descendants($this);
1408
-    }
1409
-
1410
-
1411
-    /**
1412
-     * Gets all the real items purchased which are children of this item
1413
-     *
1414
-     * @return EE_Line_Item[]
1415
-     * @throws EE_Error
1416
-     */
1417
-    public function get_items()
1418
-    {
1419
-        return EEH_Line_Item::get_line_item_descendants($this);
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * Returns the amount taxable among this line item's children (or if it has no children,
1425
-     * how much of it is taxable). Does not recalculate totals or subtotals.
1426
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1427
-     * but there is a "Taxable" discount), returns 0.
1428
-     *
1429
-     * @return float
1430
-     * @throws EE_Error
1431
-     * @throws InvalidArgumentException
1432
-     * @throws InvalidDataTypeException
1433
-     * @throws InvalidInterfaceException
1434
-     * @throws ReflectionException
1435
-     */
1436
-    public function taxable_total(): float
1437
-    {
1438
-        return $this->calculator->taxableAmountForGlobalTaxes($this);
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * Gets the transaction for this line item
1444
-     *
1445
-     * @return EE_Base_Class|EE_Transaction
1446
-     * @throws EE_Error
1447
-     * @throws InvalidArgumentException
1448
-     * @throws InvalidDataTypeException
1449
-     * @throws InvalidInterfaceException
1450
-     * @throws ReflectionException
1451
-     */
1452
-    public function transaction()
1453
-    {
1454
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1455
-    }
1456
-
1457
-
1458
-    /**
1459
-     * Saves this line item to the DB, and recursively saves its descendants.
1460
-     * Because there currently is no proper parent-child relation on the model,
1461
-     * save_this_and_cached() will NOT save the descendants.
1462
-     * Also sets the transaction on this line item and all its descendants before saving
1463
-     *
1464
-     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1465
-     * @return int count of items saved
1466
-     * @throws EE_Error
1467
-     * @throws InvalidArgumentException
1468
-     * @throws InvalidDataTypeException
1469
-     * @throws InvalidInterfaceException
1470
-     * @throws ReflectionException
1471
-     */
1472
-    public function save_this_and_descendants_to_txn($txn_id = null)
1473
-    {
1474
-        $count = 0;
1475
-        if (! $txn_id) {
1476
-            $txn_id = $this->TXN_ID();
1477
-        }
1478
-        $this->set_TXN_ID($txn_id);
1479
-        $children = $this->children();
1480
-        $count += $this->save()
1481
-            ? 1
1482
-            : 0;
1483
-        foreach ($children as $child_line_item) {
1484
-            if ($child_line_item instanceof EE_Line_Item) {
1485
-                $child_line_item->set_parent_ID($this->ID());
1486
-                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1487
-            }
1488
-        }
1489
-        return $count;
1490
-    }
1491
-
1492
-
1493
-    /**
1494
-     * Saves this line item to the DB, and recursively saves its descendants.
1495
-     *
1496
-     * @return int count of items saved
1497
-     * @throws EE_Error
1498
-     * @throws InvalidArgumentException
1499
-     * @throws InvalidDataTypeException
1500
-     * @throws InvalidInterfaceException
1501
-     * @throws ReflectionException
1502
-     */
1503
-    public function save_this_and_descendants()
1504
-    {
1505
-        $count = 0;
1506
-        $children = $this->children();
1507
-        $count += $this->save()
1508
-            ? 1
1509
-            : 0;
1510
-        foreach ($children as $child_line_item) {
1511
-            if ($child_line_item instanceof EE_Line_Item) {
1512
-                $child_line_item->set_parent_ID($this->ID());
1513
-                $count += $child_line_item->save_this_and_descendants();
1514
-            }
1515
-        }
1516
-        return $count;
1517
-    }
1518
-
1519
-
1520
-    /**
1521
-     * returns the cancellation line item if this item was cancelled
1522
-     *
1523
-     * @return EE_Line_Item[]
1524
-     * @throws InvalidArgumentException
1525
-     * @throws InvalidInterfaceException
1526
-     * @throws InvalidDataTypeException
1527
-     * @throws ReflectionException
1528
-     * @throws EE_Error
1529
-     */
1530
-    public function get_cancellations()
1531
-    {
1532
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1533
-    }
1534
-
1535
-
1536
-    /**
1537
-     * If this item has an ID, then this saves it again to update the db
1538
-     *
1539
-     * @return int count of items saved
1540
-     * @throws EE_Error
1541
-     * @throws InvalidArgumentException
1542
-     * @throws InvalidDataTypeException
1543
-     * @throws InvalidInterfaceException
1544
-     * @throws ReflectionException
1545
-     */
1546
-    public function maybe_save()
1547
-    {
1548
-        if ($this->ID()) {
1549
-            return $this->save();
1550
-        }
1551
-        return false;
1552
-    }
1553
-
1554
-
1555
-    /**
1556
-     * clears the cached children and parent from the line item
1557
-     *
1558
-     * @return void
1559
-     */
1560
-    public function clear_related_line_item_cache()
1561
-    {
1562
-        $this->_children = array();
1563
-        $this->_parent = null;
1564
-    }
1565
-
1566
-
1567
-    /**
1568
-     * @param bool $raw
1569
-     * @return int
1570
-     * @throws EE_Error
1571
-     * @throws InvalidArgumentException
1572
-     * @throws InvalidDataTypeException
1573
-     * @throws InvalidInterfaceException
1574
-     * @throws ReflectionException
1575
-     */
1576
-    public function timestamp($raw = false)
1577
-    {
1578
-        return $raw
1579
-            ? $this->get_raw('LIN_timestamp')
1580
-            : $this->get('LIN_timestamp');
1581
-    }
1582
-
1583
-
1584
-
1585
-
1586
-    /************************* DEPRECATED *************************/
1587
-    /**
1588
-     * @deprecated 4.6.0
1589
-     * @param string $type one of the constants on EEM_Line_Item
1590
-     * @return EE_Line_Item[]
1591
-     * @throws EE_Error
1592
-     */
1593
-    protected function _get_descendants_of_type($type)
1594
-    {
1595
-        EE_Error::doing_it_wrong(
1596
-            'EE_Line_Item::_get_descendants_of_type()',
1597
-            sprintf(
1598
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1599
-                'EEH_Line_Item::get_descendants_of_type()'
1600
-            ),
1601
-            '4.6.0'
1602
-        );
1603
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1604
-    }
1605
-
1606
-
1607
-    /**
1608
-     * @deprecated 4.6.0
1609
-     * @param string $type like one of the EEM_Line_Item::type_*
1610
-     * @return EE_Line_Item
1611
-     * @throws EE_Error
1612
-     * @throws InvalidArgumentException
1613
-     * @throws InvalidDataTypeException
1614
-     * @throws InvalidInterfaceException
1615
-     * @throws ReflectionException
1616
-     */
1617
-    public function get_nearest_descendant_of_type($type)
1618
-    {
1619
-        EE_Error::doing_it_wrong(
1620
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1621
-            sprintf(
1622
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1623
-                'EEH_Line_Item::get_nearest_descendant_of_type()'
1624
-            ),
1625
-            '4.6.0'
1626
-        );
1627
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1628
-    }
19
+	/**
20
+	 * for children line items (currently not a normal relation)
21
+	 *
22
+	 * @type EE_Line_Item[]
23
+	 */
24
+	protected $_children = array();
25
+
26
+	/**
27
+	 * for the parent line item
28
+	 *
29
+	 * @var EE_Line_Item
30
+	 */
31
+	protected $_parent;
32
+
33
+	/**
34
+	 * @var LineItemCalculator
35
+	 */
36
+	protected $calculator;
37
+
38
+
39
+	/**
40
+	 * @param array  $props_n_values          incoming values
41
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
42
+	 *                                        used.)
43
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
44
+	 *                                        date_format and the second value is the time format
45
+	 * @return EE_Line_Item
46
+	 * @throws EE_Error
47
+	 * @throws InvalidArgumentException
48
+	 * @throws InvalidDataTypeException
49
+	 * @throws InvalidInterfaceException
50
+	 * @throws ReflectionException
51
+	 */
52
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
53
+	{
54
+		$has_object = parent::_check_for_object(
55
+			$props_n_values,
56
+			__CLASS__,
57
+			$timezone,
58
+			$date_formats
59
+		);
60
+		return $has_object
61
+			? $has_object
62
+			: new self($props_n_values, false, $timezone);
63
+	}
64
+
65
+
66
+	/**
67
+	 * @param array  $props_n_values  incoming values from the database
68
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
69
+	 *                                the website will be used.
70
+	 * @return EE_Line_Item
71
+	 * @throws EE_Error
72
+	 * @throws InvalidArgumentException
73
+	 * @throws InvalidDataTypeException
74
+	 * @throws InvalidInterfaceException
75
+	 * @throws ReflectionException
76
+	 */
77
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
+	{
79
+		return new self($props_n_values, true, $timezone);
80
+	}
81
+
82
+
83
+	/**
84
+	 * Adds some defaults if they're not specified
85
+	 *
86
+	 * @param array  $fieldValues
87
+	 * @param bool   $bydb
88
+	 * @param string $timezone
89
+	 * @throws EE_Error
90
+	 * @throws InvalidArgumentException
91
+	 * @throws InvalidDataTypeException
92
+	 * @throws InvalidInterfaceException
93
+	 * @throws ReflectionException
94
+	 */
95
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
96
+	{
97
+		$this->calculator = LoaderFactory::getShared(LineItemCalculator::class);
98
+		parent::__construct($fieldValues, $bydb, $timezone);
99
+		if (! $this->get('LIN_code')) {
100
+			$this->set_code($this->generate_code());
101
+		}
102
+	}
103
+
104
+
105
+	/**
106
+	 * Gets ID
107
+	 *
108
+	 * @return int
109
+	 * @throws EE_Error
110
+	 * @throws InvalidArgumentException
111
+	 * @throws InvalidDataTypeException
112
+	 * @throws InvalidInterfaceException
113
+	 * @throws ReflectionException
114
+	 */
115
+	public function ID()
116
+	{
117
+		return $this->get('LIN_ID');
118
+	}
119
+
120
+
121
+	/**
122
+	 * Gets TXN_ID
123
+	 *
124
+	 * @return int
125
+	 * @throws EE_Error
126
+	 * @throws InvalidArgumentException
127
+	 * @throws InvalidDataTypeException
128
+	 * @throws InvalidInterfaceException
129
+	 * @throws ReflectionException
130
+	 */
131
+	public function TXN_ID()
132
+	{
133
+		return $this->get('TXN_ID');
134
+	}
135
+
136
+
137
+	/**
138
+	 * Sets TXN_ID
139
+	 *
140
+	 * @param int $TXN_ID
141
+	 * @throws EE_Error
142
+	 * @throws InvalidArgumentException
143
+	 * @throws InvalidDataTypeException
144
+	 * @throws InvalidInterfaceException
145
+	 * @throws ReflectionException
146
+	 */
147
+	public function set_TXN_ID($TXN_ID)
148
+	{
149
+		$this->set('TXN_ID', $TXN_ID);
150
+	}
151
+
152
+
153
+	/**
154
+	 * Gets name
155
+	 *
156
+	 * @return string
157
+	 * @throws EE_Error
158
+	 * @throws InvalidArgumentException
159
+	 * @throws InvalidDataTypeException
160
+	 * @throws InvalidInterfaceException
161
+	 * @throws ReflectionException
162
+	 */
163
+	public function name()
164
+	{
165
+		$name = $this->get('LIN_name');
166
+		if (! $name) {
167
+			$name = ucwords(str_replace('-', ' ', $this->type()));
168
+		}
169
+		return $name;
170
+	}
171
+
172
+
173
+	/**
174
+	 * Sets name
175
+	 *
176
+	 * @param string $name
177
+	 * @throws EE_Error
178
+	 * @throws InvalidArgumentException
179
+	 * @throws InvalidDataTypeException
180
+	 * @throws InvalidInterfaceException
181
+	 * @throws ReflectionException
182
+	 */
183
+	public function set_name($name)
184
+	{
185
+		$this->set('LIN_name', $name);
186
+	}
187
+
188
+
189
+	/**
190
+	 * Gets desc
191
+	 *
192
+	 * @return string
193
+	 * @throws EE_Error
194
+	 * @throws InvalidArgumentException
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws ReflectionException
198
+	 */
199
+	public function desc()
200
+	{
201
+		return $this->get('LIN_desc');
202
+	}
203
+
204
+
205
+	/**
206
+	 * Sets desc
207
+	 *
208
+	 * @param string $desc
209
+	 * @throws EE_Error
210
+	 * @throws InvalidArgumentException
211
+	 * @throws InvalidDataTypeException
212
+	 * @throws InvalidInterfaceException
213
+	 * @throws ReflectionException
214
+	 */
215
+	public function set_desc($desc)
216
+	{
217
+		$this->set('LIN_desc', $desc);
218
+	}
219
+
220
+
221
+	/**
222
+	 * Gets quantity
223
+	 *
224
+	 * @return int
225
+	 * @throws EE_Error
226
+	 * @throws InvalidArgumentException
227
+	 * @throws InvalidDataTypeException
228
+	 * @throws InvalidInterfaceException
229
+	 * @throws ReflectionException
230
+	 */
231
+	public function quantity(): int
232
+	{
233
+		return (int) $this->get('LIN_quantity');
234
+	}
235
+
236
+
237
+	/**
238
+	 * Sets quantity
239
+	 *
240
+	 * @param int $quantity
241
+	 * @throws EE_Error
242
+	 * @throws InvalidArgumentException
243
+	 * @throws InvalidDataTypeException
244
+	 * @throws InvalidInterfaceException
245
+	 * @throws ReflectionException
246
+	 */
247
+	public function set_quantity($quantity)
248
+	{
249
+		$this->set('LIN_quantity', max($quantity, 0));
250
+	}
251
+
252
+
253
+	/**
254
+	 * Gets item_id
255
+	 *
256
+	 * @return string
257
+	 * @throws EE_Error
258
+	 * @throws InvalidArgumentException
259
+	 * @throws InvalidDataTypeException
260
+	 * @throws InvalidInterfaceException
261
+	 * @throws ReflectionException
262
+	 */
263
+	public function OBJ_ID()
264
+	{
265
+		return $this->get('OBJ_ID');
266
+	}
267
+
268
+
269
+	/**
270
+	 * Sets item_id
271
+	 *
272
+	 * @param string $item_id
273
+	 * @throws EE_Error
274
+	 * @throws InvalidArgumentException
275
+	 * @throws InvalidDataTypeException
276
+	 * @throws InvalidInterfaceException
277
+	 * @throws ReflectionException
278
+	 */
279
+	public function set_OBJ_ID($item_id)
280
+	{
281
+		$this->set('OBJ_ID', $item_id);
282
+	}
283
+
284
+
285
+	/**
286
+	 * Gets item_type
287
+	 *
288
+	 * @return string
289
+	 * @throws EE_Error
290
+	 * @throws InvalidArgumentException
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws InvalidInterfaceException
293
+	 * @throws ReflectionException
294
+	 */
295
+	public function OBJ_type()
296
+	{
297
+		return $this->get('OBJ_type');
298
+	}
299
+
300
+
301
+	/**
302
+	 * Gets item_type
303
+	 *
304
+	 * @return string
305
+	 * @throws EE_Error
306
+	 * @throws InvalidArgumentException
307
+	 * @throws InvalidDataTypeException
308
+	 * @throws InvalidInterfaceException
309
+	 * @throws ReflectionException
310
+	 */
311
+	public function OBJ_type_i18n()
312
+	{
313
+		$obj_type = $this->OBJ_type();
314
+		switch ($obj_type) {
315
+			case EEM_Line_Item::OBJ_TYPE_EVENT:
316
+				$obj_type = esc_html__('Event', 'event_espresso');
317
+				break;
318
+			case EEM_Line_Item::OBJ_TYPE_PRICE:
319
+				$obj_type = esc_html__('Price', 'event_espresso');
320
+				break;
321
+			case EEM_Line_Item::OBJ_TYPE_PROMOTION:
322
+				$obj_type = esc_html__('Promotion', 'event_espresso');
323
+				break;
324
+			case EEM_Line_Item::OBJ_TYPE_TICKET:
325
+				$obj_type = esc_html__('Ticket', 'event_espresso');
326
+				break;
327
+			case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
328
+				$obj_type = esc_html__('Transaction', 'event_espresso');
329
+				break;
330
+		}
331
+		return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
332
+	}
333
+
334
+
335
+	/**
336
+	 * Sets item_type
337
+	 *
338
+	 * @param string $OBJ_type
339
+	 * @throws EE_Error
340
+	 * @throws InvalidArgumentException
341
+	 * @throws InvalidDataTypeException
342
+	 * @throws InvalidInterfaceException
343
+	 * @throws ReflectionException
344
+	 */
345
+	public function set_OBJ_type($OBJ_type)
346
+	{
347
+		$this->set('OBJ_type', $OBJ_type);
348
+	}
349
+
350
+
351
+	/**
352
+	 * Gets unit_price
353
+	 *
354
+	 * @return float
355
+	 * @throws EE_Error
356
+	 * @throws InvalidArgumentException
357
+	 * @throws InvalidDataTypeException
358
+	 * @throws InvalidInterfaceException
359
+	 * @throws ReflectionException
360
+	 */
361
+	public function unit_price()
362
+	{
363
+		return $this->get('LIN_unit_price');
364
+	}
365
+
366
+
367
+	/**
368
+	 * Sets unit_price
369
+	 *
370
+	 * @param float $unit_price
371
+	 * @throws EE_Error
372
+	 * @throws InvalidArgumentException
373
+	 * @throws InvalidDataTypeException
374
+	 * @throws InvalidInterfaceException
375
+	 * @throws ReflectionException
376
+	 */
377
+	public function set_unit_price($unit_price)
378
+	{
379
+		$this->set('LIN_unit_price', $unit_price);
380
+	}
381
+
382
+
383
+	/**
384
+	 * Checks if this item is a percentage modifier or not
385
+	 *
386
+	 * @return boolean
387
+	 * @throws EE_Error
388
+	 * @throws InvalidArgumentException
389
+	 * @throws InvalidDataTypeException
390
+	 * @throws InvalidInterfaceException
391
+	 * @throws ReflectionException
392
+	 */
393
+	public function is_percent()
394
+	{
395
+		if ($this->is_tax_sub_total()) {
396
+			// tax subtotals HAVE a percent on them, that percentage only applies
397
+			// to taxable items, so its' an exception. Treat it like a flat line item
398
+			return false;
399
+		}
400
+		$unit_price = abs($this->get('LIN_unit_price'));
401
+		$percent = abs($this->get('LIN_percent'));
402
+		if ($unit_price < .001 && $percent) {
403
+			return true;
404
+		}
405
+		if ($unit_price >= .001 && ! $percent) {
406
+			return false;
407
+		}
408
+		if ($unit_price >= .001 && $percent) {
409
+			throw new EE_Error(
410
+				sprintf(
411
+					esc_html__(
412
+						'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
413
+						'event_espresso'
414
+					),
415
+					$unit_price,
416
+					$percent
417
+				)
418
+			);
419
+		}
420
+		// if they're both 0, assume its not a percent item
421
+		return false;
422
+	}
423
+
424
+
425
+	/**
426
+	 * Gets percent (between 100-.001)
427
+	 *
428
+	 * @return float
429
+	 * @throws EE_Error
430
+	 * @throws InvalidArgumentException
431
+	 * @throws InvalidDataTypeException
432
+	 * @throws InvalidInterfaceException
433
+	 * @throws ReflectionException
434
+	 */
435
+	public function percent()
436
+	{
437
+		return $this->get('LIN_percent');
438
+	}
439
+
440
+
441
+	/**
442
+	 * Sets percent (between 100-0.01)
443
+	 *
444
+	 * @param float $percent
445
+	 * @throws EE_Error
446
+	 * @throws InvalidArgumentException
447
+	 * @throws InvalidDataTypeException
448
+	 * @throws InvalidInterfaceException
449
+	 * @throws ReflectionException
450
+	 */
451
+	public function set_percent($percent)
452
+	{
453
+		$this->set('LIN_percent', $percent);
454
+	}
455
+
456
+
457
+	/**
458
+	 * Gets total
459
+	 *
460
+	 * @return float
461
+	 * @throws EE_Error
462
+	 * @throws InvalidArgumentException
463
+	 * @throws InvalidDataTypeException
464
+	 * @throws InvalidInterfaceException
465
+	 * @throws ReflectionException
466
+	 */
467
+	public function pretaxTotal(): float
468
+	{
469
+		return $this->get('LIN_pretax');
470
+	}
471
+
472
+
473
+	/**
474
+	 * Sets total
475
+	 *
476
+	 * @param float $pretax_total
477
+	 * @throws EE_Error
478
+	 * @throws InvalidArgumentException
479
+	 * @throws InvalidDataTypeException
480
+	 * @throws InvalidInterfaceException
481
+	 * @throws ReflectionException
482
+	 */
483
+	public function setPretaxTotal(float $pretax_total)
484
+	{
485
+		$this->set('LIN_pretax', $pretax_total);
486
+	}
487
+
488
+
489
+	/**
490
+	 * @return float
491
+	 * @throws EE_Error
492
+	 * @throws ReflectionException
493
+	 * @since  $VID:$
494
+	 */
495
+	public function totalWithTax(): float
496
+	{
497
+		return $this->get('LIN_total');
498
+	}
499
+
500
+
501
+	/**
502
+	 * Sets total
503
+	 *
504
+	 * @param float $total
505
+	 * @throws EE_Error
506
+	 * @throws ReflectionException
507
+	 * @since  $VID:$
508
+	 */
509
+	public function setTotalWithTax(float $total)
510
+	{
511
+		$this->set('LIN_total', $total);
512
+	}
513
+
514
+
515
+	/**
516
+	 * Gets total
517
+	 *
518
+	 * @return float
519
+	 * @throws EE_Error
520
+	 * @throws ReflectionException
521
+	 * @deprecatd $VID:$
522
+	 */
523
+	public function total(): float
524
+	{
525
+		return $this->totalWithTax();
526
+	}
527
+
528
+
529
+	/**
530
+	 * Sets total
531
+	 *
532
+	 * @param float $total
533
+	 * @throws EE_Error
534
+	 * @throws ReflectionException
535
+	 * @deprecatd $VID:$
536
+	 */
537
+	public function set_total($total)
538
+	{
539
+		$this->setTotalWithTax($total);
540
+	}
541
+
542
+
543
+	/**
544
+	 * Gets order
545
+	 *
546
+	 * @return int
547
+	 * @throws EE_Error
548
+	 * @throws InvalidArgumentException
549
+	 * @throws InvalidDataTypeException
550
+	 * @throws InvalidInterfaceException
551
+	 * @throws ReflectionException
552
+	 */
553
+	public function order()
554
+	{
555
+		return $this->get('LIN_order');
556
+	}
557
+
558
+
559
+	/**
560
+	 * Sets order
561
+	 *
562
+	 * @param int $order
563
+	 * @throws EE_Error
564
+	 * @throws InvalidArgumentException
565
+	 * @throws InvalidDataTypeException
566
+	 * @throws InvalidInterfaceException
567
+	 * @throws ReflectionException
568
+	 */
569
+	public function set_order($order)
570
+	{
571
+		$this->set('LIN_order', $order);
572
+	}
573
+
574
+
575
+	/**
576
+	 * Gets parent
577
+	 *
578
+	 * @return int
579
+	 * @throws EE_Error
580
+	 * @throws InvalidArgumentException
581
+	 * @throws InvalidDataTypeException
582
+	 * @throws InvalidInterfaceException
583
+	 * @throws ReflectionException
584
+	 */
585
+	public function parent_ID()
586
+	{
587
+		return $this->get('LIN_parent');
588
+	}
589
+
590
+
591
+	/**
592
+	 * Sets parent
593
+	 *
594
+	 * @param int $parent
595
+	 * @throws EE_Error
596
+	 * @throws InvalidArgumentException
597
+	 * @throws InvalidDataTypeException
598
+	 * @throws InvalidInterfaceException
599
+	 * @throws ReflectionException
600
+	 */
601
+	public function set_parent_ID($parent)
602
+	{
603
+		$this->set('LIN_parent', $parent);
604
+	}
605
+
606
+
607
+	/**
608
+	 * Gets type
609
+	 *
610
+	 * @return string
611
+	 * @throws EE_Error
612
+	 * @throws InvalidArgumentException
613
+	 * @throws InvalidDataTypeException
614
+	 * @throws InvalidInterfaceException
615
+	 * @throws ReflectionException
616
+	 */
617
+	public function type()
618
+	{
619
+		return $this->get('LIN_type');
620
+	}
621
+
622
+
623
+	/**
624
+	 * Sets type
625
+	 *
626
+	 * @param string $type
627
+	 * @throws EE_Error
628
+	 * @throws InvalidArgumentException
629
+	 * @throws InvalidDataTypeException
630
+	 * @throws InvalidInterfaceException
631
+	 * @throws ReflectionException
632
+	 */
633
+	public function set_type($type)
634
+	{
635
+		$this->set('LIN_type', $type);
636
+	}
637
+
638
+
639
+	/**
640
+	 * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
641
+	 * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
642
+	 * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
643
+	 * or indirectly by `EE_Line_item::add_child_line_item()`)
644
+	 *
645
+	 * @return EE_Base_Class|EE_Line_Item
646
+	 * @throws EE_Error
647
+	 * @throws InvalidArgumentException
648
+	 * @throws InvalidDataTypeException
649
+	 * @throws InvalidInterfaceException
650
+	 * @throws ReflectionException
651
+	 */
652
+	public function parent()
653
+	{
654
+		return $this->ID()
655
+			? $this->get_model()->get_one_by_ID($this->parent_ID())
656
+			: $this->_parent;
657
+	}
658
+
659
+
660
+	/**
661
+	 * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
662
+	 *
663
+	 * @return EE_Line_Item[]
664
+	 * @throws EE_Error
665
+	 * @throws InvalidArgumentException
666
+	 * @throws InvalidDataTypeException
667
+	 * @throws InvalidInterfaceException
668
+	 * @throws ReflectionException
669
+	 */
670
+	public function children(array $query_params = []): array
671
+	{
672
+		if ($this->ID()) {
673
+			// ensure where params are an array
674
+			$query_params[0] = $query_params[0] ?? [];
675
+			// add defaults for line item parent and orderby
676
+			$query_params[0] += ['LIN_parent' => $this->ID()];
677
+			$query_params += ['order_by' => ['LIN_order' => 'ASC']];
678
+			return $this->get_model()->get_all($query_params);
679
+		}
680
+		if (! is_array($this->_children)) {
681
+			$this->_children = array();
682
+		}
683
+		return $this->_children;
684
+	}
685
+
686
+
687
+	/**
688
+	 * Gets code
689
+	 *
690
+	 * @return string
691
+	 * @throws EE_Error
692
+	 * @throws InvalidArgumentException
693
+	 * @throws InvalidDataTypeException
694
+	 * @throws InvalidInterfaceException
695
+	 * @throws ReflectionException
696
+	 */
697
+	public function code()
698
+	{
699
+		return $this->get('LIN_code');
700
+	}
701
+
702
+
703
+	/**
704
+	 * Sets code
705
+	 *
706
+	 * @param string $code
707
+	 * @throws EE_Error
708
+	 * @throws InvalidArgumentException
709
+	 * @throws InvalidDataTypeException
710
+	 * @throws InvalidInterfaceException
711
+	 * @throws ReflectionException
712
+	 */
713
+	public function set_code($code)
714
+	{
715
+		$this->set('LIN_code', $code);
716
+	}
717
+
718
+
719
+	/**
720
+	 * Gets is_taxable
721
+	 *
722
+	 * @return boolean
723
+	 * @throws EE_Error
724
+	 * @throws InvalidArgumentException
725
+	 * @throws InvalidDataTypeException
726
+	 * @throws InvalidInterfaceException
727
+	 * @throws ReflectionException
728
+	 */
729
+	public function is_taxable()
730
+	{
731
+		return $this->get('LIN_is_taxable');
732
+	}
733
+
734
+
735
+	/**
736
+	 * Sets is_taxable
737
+	 *
738
+	 * @param boolean $is_taxable
739
+	 * @throws EE_Error
740
+	 * @throws InvalidArgumentException
741
+	 * @throws InvalidDataTypeException
742
+	 * @throws InvalidInterfaceException
743
+	 * @throws ReflectionException
744
+	 */
745
+	public function set_is_taxable($is_taxable)
746
+	{
747
+		$this->set('LIN_is_taxable', $is_taxable);
748
+	}
749
+
750
+
751
+	/**
752
+	 * Gets the object that this model-joins-to.
753
+	 * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
754
+	 * EEM_Promotion_Object
755
+	 *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
756
+	 *
757
+	 * @return EE_Base_Class | NULL
758
+	 * @throws EE_Error
759
+	 * @throws InvalidArgumentException
760
+	 * @throws InvalidDataTypeException
761
+	 * @throws InvalidInterfaceException
762
+	 * @throws ReflectionException
763
+	 */
764
+	public function get_object()
765
+	{
766
+		$model_name_of_related_obj = $this->OBJ_type();
767
+		return $this->get_model()->has_relation($model_name_of_related_obj)
768
+			? $this->get_first_related($model_name_of_related_obj)
769
+			: null;
770
+	}
771
+
772
+
773
+	/**
774
+	 * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
775
+	 * (IE, if this line item is for a price or something else, will return NULL)
776
+	 *
777
+	 * @param array $query_params
778
+	 * @return EE_Base_Class|EE_Ticket
779
+	 * @throws EE_Error
780
+	 * @throws InvalidArgumentException
781
+	 * @throws InvalidDataTypeException
782
+	 * @throws InvalidInterfaceException
783
+	 * @throws ReflectionException
784
+	 */
785
+	public function ticket($query_params = array())
786
+	{
787
+		// we're going to assume that when this method is called
788
+		// we always want to receive the attached ticket EVEN if that ticket is archived.
789
+		// This can be overridden via the incoming $query_params argument
790
+		$remove_defaults = array('default_where_conditions' => 'none');
791
+		$query_params = array_merge($remove_defaults, $query_params);
792
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
793
+	}
794
+
795
+
796
+	/**
797
+	 * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
798
+	 *
799
+	 * @return EE_Datetime | NULL
800
+	 * @throws EE_Error
801
+	 * @throws InvalidArgumentException
802
+	 * @throws InvalidDataTypeException
803
+	 * @throws InvalidInterfaceException
804
+	 * @throws ReflectionException
805
+	 */
806
+	public function get_ticket_datetime()
807
+	{
808
+		if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
809
+			$ticket = $this->ticket();
810
+			if ($ticket instanceof EE_Ticket) {
811
+				$datetime = $ticket->first_datetime();
812
+				if ($datetime instanceof EE_Datetime) {
813
+					return $datetime;
814
+				}
815
+			}
816
+		}
817
+		return null;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets the event's name that's related to the ticket, if this is for
823
+	 * a ticket
824
+	 *
825
+	 * @return string
826
+	 * @throws EE_Error
827
+	 * @throws InvalidArgumentException
828
+	 * @throws InvalidDataTypeException
829
+	 * @throws InvalidInterfaceException
830
+	 * @throws ReflectionException
831
+	 */
832
+	public function ticket_event_name()
833
+	{
834
+		$event_name = esc_html__('Unknown', 'event_espresso');
835
+		$event = $this->ticket_event();
836
+		if ($event instanceof EE_Event) {
837
+			$event_name = $event->name();
838
+		}
839
+		return $event_name;
840
+	}
841
+
842
+
843
+	/**
844
+	 * Gets the event that's related to the ticket, if this line item represents a ticket.
845
+	 *
846
+	 * @return EE_Event|null
847
+	 * @throws EE_Error
848
+	 * @throws InvalidArgumentException
849
+	 * @throws InvalidDataTypeException
850
+	 * @throws InvalidInterfaceException
851
+	 * @throws ReflectionException
852
+	 */
853
+	public function ticket_event()
854
+	{
855
+		$event = null;
856
+		$ticket = $this->ticket();
857
+		if ($ticket instanceof EE_Ticket) {
858
+			$datetime = $ticket->first_datetime();
859
+			if ($datetime instanceof EE_Datetime) {
860
+				$event = $datetime->event();
861
+			}
862
+		}
863
+		return $event;
864
+	}
865
+
866
+
867
+	/**
868
+	 * Gets the first datetime for this lien item, assuming it's for a ticket
869
+	 *
870
+	 * @param string $date_format
871
+	 * @param string $time_format
872
+	 * @return string
873
+	 * @throws EE_Error
874
+	 * @throws InvalidArgumentException
875
+	 * @throws InvalidDataTypeException
876
+	 * @throws InvalidInterfaceException
877
+	 * @throws ReflectionException
878
+	 */
879
+	public function ticket_datetime_start($date_format = '', $time_format = '')
880
+	{
881
+		$first_datetime_string = esc_html__('Unknown', 'event_espresso');
882
+		$datetime = $this->get_ticket_datetime();
883
+		if ($datetime) {
884
+			$first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
885
+		}
886
+		return $first_datetime_string;
887
+	}
888
+
889
+
890
+	/**
891
+	 * Adds the line item as a child to this line item. If there is another child line
892
+	 * item with the same LIN_code, it is overwritten by this new one
893
+	 *
894
+	 * @param EE_Line_Item $line_item
895
+	 * @param bool          $set_order
896
+	 * @return bool success
897
+	 * @throws EE_Error
898
+	 * @throws InvalidArgumentException
899
+	 * @throws InvalidDataTypeException
900
+	 * @throws InvalidInterfaceException
901
+	 * @throws ReflectionException
902
+	 */
903
+	public function add_child_line_item(EE_Line_Item $line_item, $set_order = true)
904
+	{
905
+		// should we calculate the LIN_order for this line item ?
906
+		if ($set_order || $line_item->order() === null) {
907
+			$line_item->set_order(count($this->children()));
908
+		}
909
+		if ($this->ID()) {
910
+			// check for any duplicate line items (with the same code), if so, this replaces it
911
+			$line_item_with_same_code = $this->get_child_line_item($line_item->code());
912
+			if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
913
+				$this->delete_child_line_item($line_item_with_same_code->code());
914
+			}
915
+			$line_item->set_parent_ID($this->ID());
916
+			if ($this->TXN_ID()) {
917
+				$line_item->set_TXN_ID($this->TXN_ID());
918
+			}
919
+			return $line_item->save();
920
+		}
921
+		$this->_children[ $line_item->code() ] = $line_item;
922
+		if ($line_item->parent() !== $this) {
923
+			$line_item->set_parent($this);
924
+		}
925
+		return true;
926
+	}
927
+
928
+
929
+	/**
930
+	 * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
931
+	 * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
932
+	 * However, if this line item is NOT saved to the DB, this just caches the parent on
933
+	 * the EE_Line_Item::_parent property.
934
+	 *
935
+	 * @param EE_Line_Item $line_item
936
+	 * @throws EE_Error
937
+	 * @throws InvalidArgumentException
938
+	 * @throws InvalidDataTypeException
939
+	 * @throws InvalidInterfaceException
940
+	 * @throws ReflectionException
941
+	 */
942
+	public function set_parent($line_item)
943
+	{
944
+		if ($this->ID()) {
945
+			if (! $line_item->ID()) {
946
+				$line_item->save();
947
+			}
948
+			$this->set_parent_ID($line_item->ID());
949
+			$this->save();
950
+		} else {
951
+			$this->_parent = $line_item;
952
+			$this->set_parent_ID($line_item->ID());
953
+		}
954
+	}
955
+
956
+
957
+	/**
958
+	 * Gets the child line item as specified by its code. Because this returns an object (by reference)
959
+	 * you can modify this child line item and the parent (this object) can know about them
960
+	 * because it also has a reference to that line item
961
+	 *
962
+	 * @param string $code
963
+	 * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
964
+	 * @throws EE_Error
965
+	 * @throws InvalidArgumentException
966
+	 * @throws InvalidDataTypeException
967
+	 * @throws InvalidInterfaceException
968
+	 * @throws ReflectionException
969
+	 */
970
+	public function get_child_line_item($code)
971
+	{
972
+		if ($this->ID()) {
973
+			return $this->get_model()->get_one(
974
+				array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
975
+			);
976
+		}
977
+		return isset($this->_children[ $code ])
978
+			? $this->_children[ $code ]
979
+			: null;
980
+	}
981
+
982
+
983
+	/**
984
+	 * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
985
+	 * cached on it)
986
+	 *
987
+	 * @return int
988
+	 * @throws EE_Error
989
+	 * @throws InvalidArgumentException
990
+	 * @throws InvalidDataTypeException
991
+	 * @throws InvalidInterfaceException
992
+	 * @throws ReflectionException
993
+	 */
994
+	public function delete_children_line_items()
995
+	{
996
+		if ($this->ID()) {
997
+			return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
998
+		}
999
+		$count = count($this->_children);
1000
+		$this->_children = array();
1001
+		return $count;
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
1007
+	 * HAS NOT been saved to the DB, removes the child line item with index $code.
1008
+	 * Also searches through the child's children for a matching line item. However, once a line item has been found
1009
+	 * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
1010
+	 * deleted)
1011
+	 *
1012
+	 * @param string $code
1013
+	 * @param bool   $stop_search_once_found
1014
+	 * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
1015
+	 *             the DB yet)
1016
+	 * @throws EE_Error
1017
+	 * @throws InvalidArgumentException
1018
+	 * @throws InvalidDataTypeException
1019
+	 * @throws InvalidInterfaceException
1020
+	 * @throws ReflectionException
1021
+	 */
1022
+	public function delete_child_line_item($code, $stop_search_once_found = true)
1023
+	{
1024
+		if ($this->ID()) {
1025
+			$items_deleted = 0;
1026
+			if ($this->code() === $code) {
1027
+				$items_deleted += EEH_Line_Item::delete_all_child_items($this);
1028
+				$items_deleted += (int) $this->delete();
1029
+				if ($stop_search_once_found) {
1030
+					return $items_deleted;
1031
+				}
1032
+			}
1033
+			foreach ($this->children() as $child_line_item) {
1034
+				$items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
1035
+			}
1036
+			return $items_deleted;
1037
+		}
1038
+		if (isset($this->_children[ $code ])) {
1039
+			unset($this->_children[ $code ]);
1040
+			return 1;
1041
+		}
1042
+		return 0;
1043
+	}
1044
+
1045
+
1046
+	/**
1047
+	 * If this line item is in the database, is of the type subtotal, and
1048
+	 * has no children, why do we have it? It should be deleted so this function
1049
+	 * does that
1050
+	 *
1051
+	 * @return boolean
1052
+	 * @throws EE_Error
1053
+	 * @throws InvalidArgumentException
1054
+	 * @throws InvalidDataTypeException
1055
+	 * @throws InvalidInterfaceException
1056
+	 * @throws ReflectionException
1057
+	 */
1058
+	public function delete_if_childless_subtotal()
1059
+	{
1060
+		if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
1061
+			return $this->delete();
1062
+		}
1063
+		return false;
1064
+	}
1065
+
1066
+
1067
+	/**
1068
+	 * Creates a code and returns a string. doesn't assign the code to this model object
1069
+	 *
1070
+	 * @return string
1071
+	 * @throws EE_Error
1072
+	 * @throws InvalidArgumentException
1073
+	 * @throws InvalidDataTypeException
1074
+	 * @throws InvalidInterfaceException
1075
+	 * @throws ReflectionException
1076
+	 */
1077
+	public function generate_code()
1078
+	{
1079
+		// each line item in the cart requires a unique identifier
1080
+		return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1081
+	}
1082
+
1083
+
1084
+	/**
1085
+	 * @return bool
1086
+	 * @throws EE_Error
1087
+	 * @throws InvalidArgumentException
1088
+	 * @throws InvalidDataTypeException
1089
+	 * @throws InvalidInterfaceException
1090
+	 * @throws ReflectionException
1091
+	 */
1092
+	public function isGlobalTax(): bool
1093
+	{
1094
+		return $this->type() === EEM_Line_Item::type_tax;
1095
+	}
1096
+
1097
+
1098
+	/**
1099
+	 * @return bool
1100
+	 * @throws EE_Error
1101
+	 * @throws InvalidArgumentException
1102
+	 * @throws InvalidDataTypeException
1103
+	 * @throws InvalidInterfaceException
1104
+	 * @throws ReflectionException
1105
+	 */
1106
+	public function isSubTax(): bool
1107
+	{
1108
+		return $this->type() === EEM_Line_Item::type_sub_tax;
1109
+	}
1110
+
1111
+
1112
+	/**
1113
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1114
+	 *
1115
+	 * @return array
1116
+	 * @throws EE_Error
1117
+	 * @throws InvalidArgumentException
1118
+	 * @throws InvalidDataTypeException
1119
+	 * @throws InvalidInterfaceException
1120
+	 * @throws ReflectionException
1121
+	 */
1122
+	public function getSubTaxes(): array
1123
+	{
1124
+		if (! $this->is_line_item()) {
1125
+			return [];
1126
+		}
1127
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_sub_tax);
1128
+	}
1129
+
1130
+
1131
+	/**
1132
+	 * returns true if this is a line item with a direct descendent of the type sub-tax
1133
+	 *
1134
+	 * @return bool
1135
+	 * @throws EE_Error
1136
+	 * @throws InvalidArgumentException
1137
+	 * @throws InvalidDataTypeException
1138
+	 * @throws InvalidInterfaceException
1139
+	 * @throws ReflectionException
1140
+	 */
1141
+	public function hasSubTaxes(): bool
1142
+	{
1143
+		if (! $this->is_line_item()) {
1144
+			return false;
1145
+		}
1146
+		$sub_taxes = $this->getSubTaxes();
1147
+		return ! empty($sub_taxes);
1148
+	}
1149
+
1150
+
1151
+	/**
1152
+	 * @return bool
1153
+	 * @throws EE_Error
1154
+	 * @throws ReflectionException
1155
+	 * @deprecated   $VID:$
1156
+	 */
1157
+	public function is_tax(): bool
1158
+	{
1159
+		return $this->isGlobalTax();
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * @return bool
1165
+	 * @throws EE_Error
1166
+	 * @throws InvalidArgumentException
1167
+	 * @throws InvalidDataTypeException
1168
+	 * @throws InvalidInterfaceException
1169
+	 * @throws ReflectionException
1170
+	 */
1171
+	public function is_tax_sub_total()
1172
+	{
1173
+		return $this->type() === EEM_Line_Item::type_tax_sub_total;
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * @return bool
1179
+	 * @throws EE_Error
1180
+	 * @throws InvalidArgumentException
1181
+	 * @throws InvalidDataTypeException
1182
+	 * @throws InvalidInterfaceException
1183
+	 * @throws ReflectionException
1184
+	 */
1185
+	public function is_line_item()
1186
+	{
1187
+		return $this->type() === EEM_Line_Item::type_line_item;
1188
+	}
1189
+
1190
+
1191
+	/**
1192
+	 * @return bool
1193
+	 * @throws EE_Error
1194
+	 * @throws InvalidArgumentException
1195
+	 * @throws InvalidDataTypeException
1196
+	 * @throws InvalidInterfaceException
1197
+	 * @throws ReflectionException
1198
+	 */
1199
+	public function is_sub_line_item()
1200
+	{
1201
+		return $this->type() === EEM_Line_Item::type_sub_line_item;
1202
+	}
1203
+
1204
+
1205
+	/**
1206
+	 * @return bool
1207
+	 * @throws EE_Error
1208
+	 * @throws InvalidArgumentException
1209
+	 * @throws InvalidDataTypeException
1210
+	 * @throws InvalidInterfaceException
1211
+	 * @throws ReflectionException
1212
+	 */
1213
+	public function is_sub_total()
1214
+	{
1215
+		return $this->type() === EEM_Line_Item::type_sub_total;
1216
+	}
1217
+
1218
+
1219
+	/**
1220
+	 * Whether or not this line item is a cancellation line item
1221
+	 *
1222
+	 * @return boolean
1223
+	 * @throws EE_Error
1224
+	 * @throws InvalidArgumentException
1225
+	 * @throws InvalidDataTypeException
1226
+	 * @throws InvalidInterfaceException
1227
+	 * @throws ReflectionException
1228
+	 */
1229
+	public function is_cancellation()
1230
+	{
1231
+		return EEM_Line_Item::type_cancellation === $this->type();
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * @return bool
1237
+	 * @throws EE_Error
1238
+	 * @throws InvalidArgumentException
1239
+	 * @throws InvalidDataTypeException
1240
+	 * @throws InvalidInterfaceException
1241
+	 * @throws ReflectionException
1242
+	 */
1243
+	public function is_total()
1244
+	{
1245
+		return $this->type() === EEM_Line_Item::type_total;
1246
+	}
1247
+
1248
+
1249
+	/**
1250
+	 * @return bool
1251
+	 * @throws EE_Error
1252
+	 * @throws InvalidArgumentException
1253
+	 * @throws InvalidDataTypeException
1254
+	 * @throws InvalidInterfaceException
1255
+	 * @throws ReflectionException
1256
+	 */
1257
+	public function is_cancelled()
1258
+	{
1259
+		return $this->type() === EEM_Line_Item::type_cancellation;
1260
+	}
1261
+
1262
+
1263
+	/**
1264
+	 * @return string like '2, 004.00', formatted according to the localized currency
1265
+	 * @throws EE_Error
1266
+	 * @throws InvalidArgumentException
1267
+	 * @throws InvalidDataTypeException
1268
+	 * @throws InvalidInterfaceException
1269
+	 * @throws ReflectionException
1270
+	 */
1271
+	public function unit_price_no_code()
1272
+	{
1273
+		return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1274
+	}
1275
+
1276
+
1277
+	/**
1278
+	 * @return string like '2, 004.00', formatted according to the localized currency
1279
+	 * @throws EE_Error
1280
+	 * @throws InvalidArgumentException
1281
+	 * @throws InvalidDataTypeException
1282
+	 * @throws InvalidInterfaceException
1283
+	 * @throws ReflectionException
1284
+	 */
1285
+	public function total_no_code()
1286
+	{
1287
+		return $this->get_pretty('LIN_total', 'no_currency_code');
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * Gets the final total on this item, taking taxes into account.
1293
+	 * Has the side-effect of setting the sub-total as it was just calculated.
1294
+	 * If this is used on a grand-total line item, also updates the transaction's
1295
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
1296
+	 * want to change a persistable transaction with info from a non-persistent line item)
1297
+	 *
1298
+	 * @param bool $update_txn_status
1299
+	 * @return float
1300
+	 * @throws EE_Error
1301
+	 * @throws InvalidArgumentException
1302
+	 * @throws InvalidDataTypeException
1303
+	 * @throws InvalidInterfaceException
1304
+	 * @throws ReflectionException
1305
+	 * @throws RuntimeException
1306
+	 */
1307
+	public function recalculate_total_including_taxes(bool $update_txn_status = false): float
1308
+	{
1309
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1310
+		return $this->calculator->recalculateTotalIncludingTaxes($grand_total_line_item, $update_txn_status);
1311
+	}
1312
+
1313
+
1314
+	/**
1315
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1316
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1317
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1318
+	 * when this is called on the grand total
1319
+	 *
1320
+	 * @return float
1321
+	 * @throws EE_Error
1322
+	 * @throws InvalidArgumentException
1323
+	 * @throws InvalidDataTypeException
1324
+	 * @throws InvalidInterfaceException
1325
+	 * @throws ReflectionException
1326
+	 */
1327
+	public function recalculate_pre_tax_total(): float
1328
+	{
1329
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1330
+		[$total] = $this->calculator->recalculateLineItemTotals($grand_total_line_item);
1331
+		return $total;
1332
+	}
1333
+
1334
+
1335
+	/**
1336
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1337
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1338
+	 * and tax sub-total if already in the DB
1339
+	 *
1340
+	 * @return float
1341
+	 * @throws EE_Error
1342
+	 * @throws InvalidArgumentException
1343
+	 * @throws InvalidDataTypeException
1344
+	 * @throws InvalidInterfaceException
1345
+	 * @throws ReflectionException
1346
+	 */
1347
+	public function recalculate_taxes_and_tax_total(): float
1348
+	{
1349
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1350
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
1356
+	 * recalculate_taxes_and_total
1357
+	 *
1358
+	 * @return float
1359
+	 * @throws EE_Error
1360
+	 * @throws InvalidArgumentException
1361
+	 * @throws InvalidDataTypeException
1362
+	 * @throws InvalidInterfaceException
1363
+	 * @throws ReflectionException
1364
+	 */
1365
+	public function get_total_tax()
1366
+	{
1367
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($this);
1368
+		return $this->calculator->recalculateTaxesAndTaxTotal($grand_total_line_item);
1369
+	}
1370
+
1371
+
1372
+	/**
1373
+	 * Gets the total for all the items purchased only
1374
+	 *
1375
+	 * @return float
1376
+	 * @throws EE_Error
1377
+	 * @throws InvalidArgumentException
1378
+	 * @throws InvalidDataTypeException
1379
+	 * @throws InvalidInterfaceException
1380
+	 * @throws ReflectionException
1381
+	 */
1382
+	public function get_items_total()
1383
+	{
1384
+		// by default, let's make sure we're consistent with the existing line item
1385
+		if ($this->is_total()) {
1386
+			return $this->pretaxTotal();
1387
+		}
1388
+		$total = 0;
1389
+		foreach ($this->get_items() as $item) {
1390
+			if ($item instanceof EE_Line_Item) {
1391
+				$total += $item->pretaxTotal();
1392
+			}
1393
+		}
1394
+		return $total;
1395
+	}
1396
+
1397
+
1398
+	/**
1399
+	 * Gets all the descendants (ie, children or children of children etc) that
1400
+	 * are of the type 'tax'
1401
+	 *
1402
+	 * @return EE_Line_Item[]
1403
+	 * @throws EE_Error
1404
+	 */
1405
+	public function tax_descendants()
1406
+	{
1407
+		return EEH_Line_Item::get_tax_descendants($this);
1408
+	}
1409
+
1410
+
1411
+	/**
1412
+	 * Gets all the real items purchased which are children of this item
1413
+	 *
1414
+	 * @return EE_Line_Item[]
1415
+	 * @throws EE_Error
1416
+	 */
1417
+	public function get_items()
1418
+	{
1419
+		return EEH_Line_Item::get_line_item_descendants($this);
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * Returns the amount taxable among this line item's children (or if it has no children,
1425
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
1426
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
1427
+	 * but there is a "Taxable" discount), returns 0.
1428
+	 *
1429
+	 * @return float
1430
+	 * @throws EE_Error
1431
+	 * @throws InvalidArgumentException
1432
+	 * @throws InvalidDataTypeException
1433
+	 * @throws InvalidInterfaceException
1434
+	 * @throws ReflectionException
1435
+	 */
1436
+	public function taxable_total(): float
1437
+	{
1438
+		return $this->calculator->taxableAmountForGlobalTaxes($this);
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * Gets the transaction for this line item
1444
+	 *
1445
+	 * @return EE_Base_Class|EE_Transaction
1446
+	 * @throws EE_Error
1447
+	 * @throws InvalidArgumentException
1448
+	 * @throws InvalidDataTypeException
1449
+	 * @throws InvalidInterfaceException
1450
+	 * @throws ReflectionException
1451
+	 */
1452
+	public function transaction()
1453
+	{
1454
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1455
+	}
1456
+
1457
+
1458
+	/**
1459
+	 * Saves this line item to the DB, and recursively saves its descendants.
1460
+	 * Because there currently is no proper parent-child relation on the model,
1461
+	 * save_this_and_cached() will NOT save the descendants.
1462
+	 * Also sets the transaction on this line item and all its descendants before saving
1463
+	 *
1464
+	 * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1465
+	 * @return int count of items saved
1466
+	 * @throws EE_Error
1467
+	 * @throws InvalidArgumentException
1468
+	 * @throws InvalidDataTypeException
1469
+	 * @throws InvalidInterfaceException
1470
+	 * @throws ReflectionException
1471
+	 */
1472
+	public function save_this_and_descendants_to_txn($txn_id = null)
1473
+	{
1474
+		$count = 0;
1475
+		if (! $txn_id) {
1476
+			$txn_id = $this->TXN_ID();
1477
+		}
1478
+		$this->set_TXN_ID($txn_id);
1479
+		$children = $this->children();
1480
+		$count += $this->save()
1481
+			? 1
1482
+			: 0;
1483
+		foreach ($children as $child_line_item) {
1484
+			if ($child_line_item instanceof EE_Line_Item) {
1485
+				$child_line_item->set_parent_ID($this->ID());
1486
+				$count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1487
+			}
1488
+		}
1489
+		return $count;
1490
+	}
1491
+
1492
+
1493
+	/**
1494
+	 * Saves this line item to the DB, and recursively saves its descendants.
1495
+	 *
1496
+	 * @return int count of items saved
1497
+	 * @throws EE_Error
1498
+	 * @throws InvalidArgumentException
1499
+	 * @throws InvalidDataTypeException
1500
+	 * @throws InvalidInterfaceException
1501
+	 * @throws ReflectionException
1502
+	 */
1503
+	public function save_this_and_descendants()
1504
+	{
1505
+		$count = 0;
1506
+		$children = $this->children();
1507
+		$count += $this->save()
1508
+			? 1
1509
+			: 0;
1510
+		foreach ($children as $child_line_item) {
1511
+			if ($child_line_item instanceof EE_Line_Item) {
1512
+				$child_line_item->set_parent_ID($this->ID());
1513
+				$count += $child_line_item->save_this_and_descendants();
1514
+			}
1515
+		}
1516
+		return $count;
1517
+	}
1518
+
1519
+
1520
+	/**
1521
+	 * returns the cancellation line item if this item was cancelled
1522
+	 *
1523
+	 * @return EE_Line_Item[]
1524
+	 * @throws InvalidArgumentException
1525
+	 * @throws InvalidInterfaceException
1526
+	 * @throws InvalidDataTypeException
1527
+	 * @throws ReflectionException
1528
+	 * @throws EE_Error
1529
+	 */
1530
+	public function get_cancellations()
1531
+	{
1532
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1533
+	}
1534
+
1535
+
1536
+	/**
1537
+	 * If this item has an ID, then this saves it again to update the db
1538
+	 *
1539
+	 * @return int count of items saved
1540
+	 * @throws EE_Error
1541
+	 * @throws InvalidArgumentException
1542
+	 * @throws InvalidDataTypeException
1543
+	 * @throws InvalidInterfaceException
1544
+	 * @throws ReflectionException
1545
+	 */
1546
+	public function maybe_save()
1547
+	{
1548
+		if ($this->ID()) {
1549
+			return $this->save();
1550
+		}
1551
+		return false;
1552
+	}
1553
+
1554
+
1555
+	/**
1556
+	 * clears the cached children and parent from the line item
1557
+	 *
1558
+	 * @return void
1559
+	 */
1560
+	public function clear_related_line_item_cache()
1561
+	{
1562
+		$this->_children = array();
1563
+		$this->_parent = null;
1564
+	}
1565
+
1566
+
1567
+	/**
1568
+	 * @param bool $raw
1569
+	 * @return int
1570
+	 * @throws EE_Error
1571
+	 * @throws InvalidArgumentException
1572
+	 * @throws InvalidDataTypeException
1573
+	 * @throws InvalidInterfaceException
1574
+	 * @throws ReflectionException
1575
+	 */
1576
+	public function timestamp($raw = false)
1577
+	{
1578
+		return $raw
1579
+			? $this->get_raw('LIN_timestamp')
1580
+			: $this->get('LIN_timestamp');
1581
+	}
1582
+
1583
+
1584
+
1585
+
1586
+	/************************* DEPRECATED *************************/
1587
+	/**
1588
+	 * @deprecated 4.6.0
1589
+	 * @param string $type one of the constants on EEM_Line_Item
1590
+	 * @return EE_Line_Item[]
1591
+	 * @throws EE_Error
1592
+	 */
1593
+	protected function _get_descendants_of_type($type)
1594
+	{
1595
+		EE_Error::doing_it_wrong(
1596
+			'EE_Line_Item::_get_descendants_of_type()',
1597
+			sprintf(
1598
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1599
+				'EEH_Line_Item::get_descendants_of_type()'
1600
+			),
1601
+			'4.6.0'
1602
+		);
1603
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1604
+	}
1605
+
1606
+
1607
+	/**
1608
+	 * @deprecated 4.6.0
1609
+	 * @param string $type like one of the EEM_Line_Item::type_*
1610
+	 * @return EE_Line_Item
1611
+	 * @throws EE_Error
1612
+	 * @throws InvalidArgumentException
1613
+	 * @throws InvalidDataTypeException
1614
+	 * @throws InvalidInterfaceException
1615
+	 * @throws ReflectionException
1616
+	 */
1617
+	public function get_nearest_descendant_of_type($type)
1618
+	{
1619
+		EE_Error::doing_it_wrong(
1620
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1621
+			sprintf(
1622
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1623
+				'EEH_Line_Item::get_nearest_descendant_of_type()'
1624
+			),
1625
+			'4.6.0'
1626
+		);
1627
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1628
+	}
1629 1629
 }
Please login to merge, or discard this patch.
payment_methods/Paypal_Express/EEG_Paypal_Express.gateway.php 2 patches
Indentation   +687 added lines, -687 removed lines patch added patch discarded remove patch
@@ -13,695 +13,695 @@
 block discarded – undo
13 13
 
14 14
 // Quickfix to address https://events.codebasehq.com/projects/event-espresso/tickets/11089 ASAP
15 15
 if (! function_exists('mb_strcut')) {
16
-    /**
17
-     * Very simple mimic of mb_substr (which WP ensures exists in wp-includes/compat.php). Still has all the problems of mb_substr
18
-     * (namely, that we might send too many characters to PayPal; however in this case they just issue a warning but nothing breaks)
19
-     * @param $string
20
-     * @param $start
21
-     * @param $length
22
-     * @return bool|string
23
-     */
24
-    function mb_strcut($string, $start, $length = null)
25
-    {
26
-        return mb_substr($string, $start, $length);
27
-    }
16
+	/**
17
+	 * Very simple mimic of mb_substr (which WP ensures exists in wp-includes/compat.php). Still has all the problems of mb_substr
18
+	 * (namely, that we might send too many characters to PayPal; however in this case they just issue a warning but nothing breaks)
19
+	 * @param $string
20
+	 * @param $start
21
+	 * @param $length
22
+	 * @return bool|string
23
+	 */
24
+	function mb_strcut($string, $start, $length = null)
25
+	{
26
+		return mb_substr($string, $start, $length);
27
+	}
28 28
 }
29 29
 class EEG_Paypal_Express extends EE_Offsite_Gateway
30 30
 {
31 31
 
32
-    /**
33
-     * Merchant API Username.
34
-     *
35
-     * @var string
36
-     */
37
-    protected $_api_username;
38
-
39
-    /**
40
-     * Merchant API Password.
41
-     *
42
-     * @var string
43
-     */
44
-    protected $_api_password;
45
-
46
-    /**
47
-     * API Signature.
48
-     *
49
-     * @var string
50
-     */
51
-    protected $_api_signature;
52
-
53
-    /**
54
-     * Request Shipping address on PP checkout page.
55
-     *
56
-     * @var string
57
-     */
58
-    protected $_request_shipping_addr;
59
-
60
-    /**
61
-     * Business/personal logo.
62
-     *
63
-     * @var string
64
-     */
65
-    protected $_image_url;
66
-
67
-    /**
68
-     * gateway URL variable
69
-     *
70
-     * @var string
71
-     */
72
-    protected $_base_gateway_url = '';
73
-
74
-
75
-    /**
76
-     * number of decimal places to round numbers to when performing calculations
77
-     *
78
-     * @var integer
79
-     */
80
-    protected $decimal_precision = 6;
81
-
82
-
83
-    /**
84
-     * EEG_Paypal_Express constructor.
85
-     */
86
-    public function __construct()
87
-    {
88
-        $this->_currencies_supported = array(
89
-            'USD',
90
-            'AUD',
91
-            'BRL',
92
-            'CAD',
93
-            'CZK',
94
-            'DKK',
95
-            'EUR',
96
-            'HKD',
97
-            'HUF',
98
-            'ILS',
99
-            'JPY',
100
-            'MYR',
101
-            'MXN',
102
-            'NOK',
103
-            'NZD',
104
-            'PHP',
105
-            'PLN',
106
-            'GBP',
107
-            'RUB',
108
-            'SGD',
109
-            'SEK',
110
-            'CHF',
111
-            'TWD',
112
-            'THB',
113
-            'TRY',
114
-            'INR',
115
-        );
116
-        parent::__construct();
117
-        $this->decimal_precision = EE_Registry::instance()->CFG->currency->dec_plc;
118
-    }
119
-
120
-
121
-
122
-    /**
123
-     * Sets the gateway URL variable based on whether debug mode is enabled or not.
124
-     *
125
-     * @param array $settings_array
126
-     */
127
-    public function set_settings($settings_array)
128
-    {
129
-        parent::set_settings($settings_array);
130
-        // Redirect URL.
131
-        $this->_base_gateway_url = $this->_debug_mode
132
-            ? 'https://api-3t.sandbox.paypal.com/nvp'
133
-            : 'https://api-3t.paypal.com/nvp';
134
-    }
135
-
136
-
137
-
138
-    /**
139
-     * @param EEI_Payment $payment
140
-     * @param array       $billing_info
141
-     * @param string      $return_url
142
-     * @param string      $notify_url
143
-     * @param string      $cancel_url
144
-     * @return \EE_Payment|\EEI_Payment
145
-     * @throws \EE_Error
146
-     */
147
-    public function set_redirection_info(
148
-        $payment,
149
-        $billing_info = array(),
150
-        $return_url = null,
151
-        $notify_url = null,
152
-        $cancel_url = null
153
-    ) {
154
-        if (! $payment instanceof EEI_Payment) {
155
-            $payment->set_gateway_response(
156
-                esc_html__(
157
-                    'Error. No associated payment was found.',
158
-                    'event_espresso'
159
-                )
160
-            );
161
-            $payment->set_status($this->_pay_model->failed_status());
162
-            return $payment;
163
-        }
164
-        $transaction = $payment->transaction();
165
-        if (! $transaction instanceof EEI_Transaction) {
166
-            $payment->set_gateway_response(
167
-                esc_html__(
168
-                    'Could not process this payment because it has no associated transaction.',
169
-                    'event_espresso'
170
-                )
171
-            );
172
-            $payment->set_status($this->_pay_model->failed_status());
173
-            return $payment;
174
-        }
175
-        $gateway_formatter = $this->_get_gateway_formatter();
176
-        $order_description = mb_strcut($gateway_formatter->formatOrderDescription($payment), 0, 127);
177
-        $primary_registration = $transaction->primary_registration();
178
-        $primary_attendee = $primary_registration instanceof EE_Registration
179
-            ? $primary_registration->attendee()
180
-            : false;
181
-        $locale = explode('-', get_bloginfo('language'));
182
-        // Gather request parameters.
183
-        $token_request_dtls = array(
184
-            'METHOD'                         => 'SetExpressCheckout',
185
-            'PAYMENTREQUEST_0_AMT'           => $payment->amount(),
186
-            'PAYMENTREQUEST_0_CURRENCYCODE'  => $payment->currency_code(),
187
-            'PAYMENTREQUEST_0_DESC'          => $order_description,
188
-            'RETURNURL'                      => $return_url,
189
-            'CANCELURL'                      => $cancel_url,
190
-            'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
191
-            // Buyer does not need to create a PayPal account to check out.
192
-            // This is referred to as PayPal Account Optional.
193
-            'SOLUTIONTYPE'                   => 'Sole',
194
-            // Locale of the pages displayed by PayPal during Express Checkout.
195
-            'LOCALECODE'                     => $locale[1]
196
-        );
197
-        // Show itemized list.
198
-        $itemized_list = $this->itemize_list($payment, $transaction);
199
-        $token_request_dtls = array_merge($token_request_dtls, $itemized_list);
200
-        // Automatically filling out shipping and contact information.
201
-        if ($this->_request_shipping_addr && $primary_attendee instanceof EEI_Attendee) {
202
-            // If you do not pass the shipping address, PayPal obtains it from the buyer's account profile.
203
-            $token_request_dtls['NOSHIPPING'] = '2';
204
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTREET'] = $primary_attendee->address();
205
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTREET2'] = $primary_attendee->address2();
206
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOCITY'] = $primary_attendee->city();
207
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTATE'] = $primary_attendee->state_abbrev();
208
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] = $primary_attendee->country_ID();
209
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOZIP'] = $primary_attendee->zip();
210
-            $token_request_dtls['PAYMENTREQUEST_0_EMAIL'] = $primary_attendee->email();
211
-            $token_request_dtls['PAYMENTREQUEST_0_SHIPTOPHONENUM'] = $primary_attendee->phone();
212
-        } elseif (! $this->_request_shipping_addr) {
213
-            // Do not request shipping details on the PP Checkout page.
214
-            $token_request_dtls['NOSHIPPING'] = '1';
215
-            $token_request_dtls['REQCONFIRMSHIPPING'] = '0';
216
-        }
217
-        // Used a business/personal logo on the PayPal page.
218
-        if (! empty($this->_image_url)) {
219
-            $token_request_dtls['LOGOIMG'] = $this->_image_url;
220
-        }
221
-        $token_request_dtls = apply_filters(
222
-            'FHEE__EEG_Paypal_Express__set_redirection_info__arguments',
223
-            $token_request_dtls,
224
-            $this
225
-        );
226
-        // Request PayPal token.
227
-        $token_request_response = $this->_ppExpress_request($token_request_dtls, 'Payment Token', $payment);
228
-        $token_rstatus = $this->_ppExpress_check_response($token_request_response);
229
-        $response_args = (isset($token_rstatus['args']) && is_array($token_rstatus['args']))
230
-            ? $token_rstatus['args']
231
-            : array();
232
-        if ($token_rstatus['status']) {
233
-            // We got the Token so we may continue with the payment and redirect the client.
234
-            $payment->set_details($response_args);
235
-            $gateway_url = $this->_debug_mode ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
236
-            $payment->set_redirect_url(
237
-                $gateway_url
238
-                . '/checkoutnow?useraction=commit&cmd=_express-checkout&token='
239
-                . $response_args['TOKEN']
240
-            );
241
-        } else {
242
-            if (isset($response_args['L_ERRORCODE'])) {
243
-                $payment->set_gateway_response($response_args['L_ERRORCODE'] . '; ' . $response_args['L_SHORTMESSAGE']);
244
-            } else {
245
-                $payment->set_gateway_response(
246
-                    esc_html__(
247
-                        'Error occurred while trying to setup the Express Checkout.',
248
-                        'event_espresso'
249
-                    )
250
-                );
251
-            }
252
-            $payment->set_details($response_args);
253
-            $payment->set_status($this->_pay_model->failed_status());
254
-        }
255
-        return $payment;
256
-    }
257
-
258
-
259
-
260
-    /**
261
-     * @param array           $update_info {
262
-     * @type string           $gateway_txn_id
263
-     * @type string status an EEMI_Payment status
264
-     *                                     }
265
-     * @param EEI_Transaction $transaction
266
-     * @return EEI_Payment
267
-     */
268
-    public function handle_payment_update($update_info, $transaction)
269
-    {
270
-        $payment = $transaction instanceof EEI_Transaction ? $transaction->last_payment() : null;
271
-        if ($payment instanceof EEI_Payment) {
272
-            $this->log(array('Return from Authorization' => $update_info), $payment);
273
-            $transaction = $payment->transaction();
274
-            if (! $transaction instanceof EEI_Transaction) {
275
-                $payment->set_gateway_response(
276
-                    esc_html__(
277
-                        'Could not process this payment because it has no associated transaction.',
278
-                        'event_espresso'
279
-                    )
280
-                );
281
-                $payment->set_status($this->_pay_model->failed_status());
282
-                return $payment;
283
-            }
284
-            $primary_registrant = $transaction->primary_registration();
285
-            $payment_details = $payment->details();
286
-            // Check if we still have the token.
287
-            if (! isset($payment_details['TOKEN']) || empty($payment_details['TOKEN'])) {
288
-                $payment->set_status($this->_pay_model->failed_status());
289
-                return $payment;
290
-            }
291
-            $cdetails_request_dtls = array(
292
-                'METHOD' => 'GetExpressCheckoutDetails',
293
-                'TOKEN'  => $payment_details['TOKEN'],
294
-            );
295
-            // Request Customer Details.
296
-            $cdetails_request_response = $this->_ppExpress_request(
297
-                $cdetails_request_dtls,
298
-                'Customer Details',
299
-                $payment
300
-            );
301
-            $cdetails_rstatus = $this->_ppExpress_check_response($cdetails_request_response);
302
-            $cdata_response_args = (isset($cdetails_rstatus['args']) && is_array($cdetails_rstatus['args']))
303
-                ? $cdetails_rstatus['args']
304
-                : array();
305
-            if ($cdetails_rstatus['status']) {
306
-                // We got the PayerID so now we can Complete the transaction.
307
-                $docheckout_request_dtls = array(
308
-                    'METHOD'                         => 'DoExpressCheckoutPayment',
309
-                    'PAYERID'                        => $cdata_response_args['PAYERID'],
310
-                    'TOKEN'                          => $payment_details['TOKEN'],
311
-                    'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
312
-                    'PAYMENTREQUEST_0_AMT'           => $payment->amount(),
313
-                    'PAYMENTREQUEST_0_CURRENCYCODE'  => $payment->currency_code(),
314
-                );
315
-                 // Include itemized list.
316
-                $itemized_list = $this->itemize_list(
317
-                    $payment,
318
-                    $transaction,
319
-                    $cdata_response_args
320
-                );
321
-                $docheckout_request_dtls = array_merge($docheckout_request_dtls, $itemized_list);
322
-                // Payment Checkout/Capture.
323
-                $docheckout_request_response = $this->_ppExpress_request(
324
-                    $docheckout_request_dtls,
325
-                    'Do Payment',
326
-                    $payment
327
-                );
328
-                $docheckout_rstatus = $this->_ppExpress_check_response($docheckout_request_response);
329
-                $docheckout_response_args = (isset($docheckout_rstatus['args']) && is_array($docheckout_rstatus['args']))
330
-                    ? $docheckout_rstatus['args']
331
-                    : array();
332
-                if ($docheckout_rstatus['status']) {
333
-                    // All is well, payment approved.
334
-                    $primary_registration_code = $primary_registrant instanceof EE_Registration ?
335
-                        $primary_registrant->reg_code()
336
-                        : '';
337
-                    $payment->set_extra_accntng($primary_registration_code);
338
-                    $payment->set_amount(isset($docheckout_response_args['PAYMENTINFO_0_AMT'])
339
-                        ? (float) $docheckout_response_args['PAYMENTINFO_0_AMT']
340
-                        : 0);
341
-                    $payment->set_txn_id_chq_nmbr(isset($docheckout_response_args['PAYMENTINFO_0_TRANSACTIONID'])
342
-                        ? $docheckout_response_args['PAYMENTINFO_0_TRANSACTIONID']
343
-                        : null);
344
-                    $payment->set_details($cdata_response_args);
345
-                    $payment->set_gateway_response(isset($docheckout_response_args['PAYMENTINFO_0_ACK'])
346
-                        ? $docheckout_response_args['PAYMENTINFO_0_ACK']
347
-                        : '');
348
-                    $payment->set_status($this->_pay_model->approved_status());
349
-                } else {
350
-                    if (isset($docheckout_response_args['L_ERRORCODE'])) {
351
-                        $payment->set_gateway_response(
352
-                            $docheckout_response_args['L_ERRORCODE']
353
-                            . '; '
354
-                            . $docheckout_response_args['L_SHORTMESSAGE']
355
-                        );
356
-                    } else {
357
-                        $payment->set_gateway_response(
358
-                            esc_html__(
359
-                                'Error occurred while trying to Capture the funds.',
360
-                                'event_espresso'
361
-                            )
362
-                        );
363
-                    }
364
-                    $payment->set_details($docheckout_response_args);
365
-                    $payment->set_status($this->_pay_model->declined_status());
366
-                }
367
-            } else {
368
-                if (isset($cdata_response_args['L_ERRORCODE'])) {
369
-                    $payment->set_gateway_response(
370
-                        $cdata_response_args['L_ERRORCODE']
371
-                        . '; '
372
-                        . $cdata_response_args['L_SHORTMESSAGE']
373
-                    );
374
-                } else {
375
-                    $payment->set_gateway_response(
376
-                        esc_html__(
377
-                            'Error occurred while trying to get payment Details from PayPal.',
378
-                            'event_espresso'
379
-                        )
380
-                    );
381
-                }
382
-                $payment->set_details($cdata_response_args);
383
-                $payment->set_status($this->_pay_model->failed_status());
384
-            }
385
-        } else {
386
-            $payment->set_gateway_response(
387
-                esc_html__(
388
-                    'Error occurred while trying to process the payment.',
389
-                    'event_espresso'
390
-                )
391
-            );
392
-            $payment->set_status($this->_pay_model->failed_status());
393
-        }
394
-        return $payment;
395
-    }
396
-
397
-
398
-
399
-    /**
400
-     *  Make a list of items that are in the giver transaction.
401
-     *
402
-     * @param EEI_Payment     $payment
403
-     * @param EEI_Transaction $transaction
404
-     * @param array           $request_response_args Data from a previous communication with PP.
405
-     * @return array
406
-     */
407
-    public function itemize_list(EEI_Payment $payment, EEI_Transaction $transaction, $request_response_args = array())
408
-    {
409
-        $itemized_list = array();
410
-        $gateway_formatter = $this->_get_gateway_formatter();
411
-        // If we have data from a previous communication with PP (on this transaction) we may use that for our list...
412
-        if (
413
-            ! empty($request_response_args)
414
-            && array_key_exists('L_PAYMENTREQUEST_0_AMT0', $request_response_args)
415
-            && array_key_exists('PAYMENTREQUEST_0_ITEMAMT', $request_response_args)
416
-        ) {
417
-            foreach ($request_response_args as $arg_key => $arg_val) {
418
-                if (
419
-                    strpos($arg_key, 'PAYMENTREQUEST_') !== false
420
-                    && strpos($arg_key, 'NOTIFYURL') === false
421
-                ) {
422
-                    $itemized_list[ $arg_key ] = $arg_val;
423
-                }
424
-            }
425
-            // If we got only a few Items then something is not right.
426
-            if (count($itemized_list) > 2) {
427
-                return $itemized_list;
428
-            } else {
429
-                if (WP_DEBUG) {
430
-                    throw new EE_Error(
431
-                        sprintf(
432
-                            esc_html__(
433
-                                // @codingStandardsIgnoreStart
434
-                                'Unable to continue with the checkout because a proper purchase list could not be generated. The purchased list we could have sent was %1$s',
435
-                                // @codingStandardsIgnoreEnd
436
-                                'event_espresso'
437
-                            ),
438
-                            wp_json_encode($itemized_list)
439
-                        )
440
-                    );
441
-                }
442
-                // Reset the list and log an error, maybe allow to try and generate a new list (below).
443
-                $itemized_list = array();
444
-                $this->log(
445
-                    array(
446
-                        (string) esc_html__(
447
-                            'Could not generate a proper item list with:',
448
-                            'event_espresso'
449
-                        ) => $request_response_args
450
-                    ),
451
-                    $payment
452
-                );
453
-            }
454
-        }
455
-        // ...otherwise we generate a new list for this transaction.
456
-        if ($this->_money->compare_floats($payment->amount(), $transaction->total(), '==')) {
457
-            $item_num = 0;
458
-            $itemized_sum = 0;
459
-            $total_line_items = $transaction->total_line_item();
460
-            // Go through each item in the list.
461
-            foreach ($total_line_items->get_items() as $line_item) {
462
-                if ($line_item instanceof EE_Line_Item) {
463
-                    // PayPal doesn't like line items with 0.00 amount, so we may skip those.
464
-                    if (EEH_Money::compare_floats($line_item->pretaxTotal(), '0.00', '==')) {
465
-                        continue;
466
-                    }
467
-                    $unit_price = $line_item->unit_price();
468
-                    $line_item_quantity = $line_item->quantity();
469
-                    // This is a discount.
470
-                    if ($line_item->is_percent()) {
471
-                        $unit_price = $line_item->pretaxTotal();
472
-                        $line_item_quantity = 1;
473
-                    }
474
-                    // Item Name.
475
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
476
-                        $gateway_formatter->formatLineItemName($line_item, $payment),
477
-                        0,
478
-                        127
479
-                    );
480
-                    // Item description.
481
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = mb_strcut(
482
-                        $gateway_formatter->formatLineItemDesc($line_item, $payment),
483
-                        0,
484
-                        127
485
-                    );
486
-                    // Cost of individual item.
487
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
488
-                        $unit_price,
489
-                        $this->decimal_precision
490
-                    );
491
-                    // Item Number.
492
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
493
-                    // Item quantity.
494
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = $line_item_quantity;
495
-                    // Digital item is sold.
496
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
497
-                    $itemized_sum += $line_item->pretaxTotal();
498
-                    ++$item_num;
499
-                }
500
-            }
501
-            // Item's sales S/H and tax amount.
502
-            $itemized_list['PAYMENTREQUEST_0_ITEMAMT'] = $total_line_items->get_items_total();
503
-            $itemized_list['PAYMENTREQUEST_0_TAXAMT'] = $total_line_items->get_total_tax();
504
-            $itemized_list['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0';
505
-            $itemized_list['PAYMENTREQUEST_0_HANDLINGAMT'] = '0';
506
-            $itemized_sum_diff_from_txn_total = round(
507
-                $transaction->total() - $itemized_sum - $total_line_items->get_total_tax(),
508
-                $this->decimal_precision
509
-            );
510
-            // If we were not able to recognize some item like promotion, surcharge or cancellation,
511
-            // add the difference as an extra line item.
512
-            if ($this->_money->compare_floats($itemized_sum_diff_from_txn_total, 0, '!=')) {
513
-                // Item Name.
514
-                $itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
515
-                    esc_html__(
516
-                        'Other (promotion/surcharge/cancellation)',
517
-                        'event_espresso'
518
-                    ),
519
-                    0,
520
-                    127
521
-                );
522
-                // Item description.
523
-                $itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = '';
524
-                // Cost of individual item.
525
-                $itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
526
-                    $itemized_sum_diff_from_txn_total,
527
-                    $this->decimal_precision
528
-                );
529
-                // Item Number.
530
-                $itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
531
-                // Item quantity.
532
-                $itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = 1;
533
-                // Digital item is sold.
534
-                $itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
535
-                $item_num++;
536
-            }
537
-        } else {
538
-            // Just one Item.
539
-            // Item Name.
540
-            $itemized_list['L_PAYMENTREQUEST_0_NAME0'] = mb_strcut(
541
-                $gateway_formatter->formatPartialPaymentLineItemName($payment),
542
-                0,
543
-                127
544
-            );
545
-            // Item description.
546
-            $itemized_list['L_PAYMENTREQUEST_0_DESC0'] = mb_strcut(
547
-                $gateway_formatter->formatPartialPaymentLineItemDesc($payment),
548
-                0,
549
-                127
550
-            );
551
-            // Cost of individual item.
552
-            $itemized_list['L_PAYMENTREQUEST_0_AMT0'] = $gateway_formatter->formatCurrency(
553
-                $payment->amount(),
554
-                $this->decimal_precision
555
-            );
556
-            // Item Number.
557
-            $itemized_list['L_PAYMENTREQUEST_0_NUMBER0'] = 1;
558
-            // Item quantity.
559
-            $itemized_list['L_PAYMENTREQUEST_0_QTY0'] = 1;
560
-            // Digital item is sold.
561
-            $itemized_list['L_PAYMENTREQUEST_0_ITEMCATEGORY0'] = 'Physical';
562
-            // Item's sales S/H and tax amount.
563
-            $itemized_list['PAYMENTREQUEST_0_ITEMAMT'] = $gateway_formatter->formatCurrency(
564
-                $payment->amount(),
565
-                $this->decimal_precision
566
-            );
567
-            $itemized_list['PAYMENTREQUEST_0_TAXAMT'] = '0';
568
-            $itemized_list['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0';
569
-            $itemized_list['PAYMENTREQUEST_0_HANDLINGAMT'] = '0';
570
-        }
571
-        return $itemized_list;
572
-    }
573
-
574
-
575
-
576
-    /**
577
-     *  Make the Express checkout request.
578
-     *
579
-     * @param array       $request_params
580
-     * @param string      $request_text
581
-     * @param EEI_Payment $payment
582
-     * @return mixed
583
-     */
584
-    public function _ppExpress_request($request_params, $request_text, $payment)
585
-    {
586
-        $request_dtls = array(
587
-            'VERSION' => '204.0',
588
-            'USER' => $this->_api_username,
589
-            'PWD' => $this->_api_password,
590
-            'SIGNATURE' => $this->_api_signature,
591
-            // EE will blow up if you change this
592
-            'BUTTONSOURCE' => 'EventEspresso_SP',
593
-        );
594
-        $dtls = array_merge($request_dtls, $request_params);
595
-        $this->_log_clean_request($dtls, $payment, $request_text . ' Request');
596
-        // Request Customer Details.
597
-        $request_response = wp_remote_post(
598
-            $this->_base_gateway_url,
599
-            array(
600
-                'method'      => 'POST',
601
-                'timeout'     => 45,
602
-                'httpversion' => '1.1',
603
-                'cookies'     => array(),
604
-                'headers'     => array(),
605
-                'body'        => http_build_query($dtls, '', '&'),
606
-            )
607
-        );
608
-        // Log the response.
609
-        $this->log(array($request_text . ' Response' => $request_response), $payment);
610
-        return $request_response;
611
-    }
612
-
613
-
614
-
615
-    /**
616
-     *  Check the response status.
617
-     *
618
-     * @param mixed $request_response
619
-     * @return array
620
-     */
621
-    public function _ppExpress_check_response($request_response)
622
-    {
623
-        if (is_wp_error($request_response) || empty($request_response['body'])) {
624
-            // If we got here then there was an error in this request.
625
-            return array('status' => false, 'args' => $request_response);
626
-        }
627
-        $response_args = array();
628
-        parse_str(urldecode($request_response['body']), $response_args);
629
-        if (! isset($response_args['ACK'])) {
630
-            return array('status' => false, 'args' => $request_response);
631
-        }
632
-        if (
633
-            (
634
-                isset($response_args['PAYERID'])
635
-                || isset($response_args['TOKEN'])
636
-                || isset($response_args['PAYMENTINFO_0_TRANSACTIONID'])
637
-                || (isset($response_args['PAYMENTSTATUS']) && $response_args['PAYMENTSTATUS'] === 'Completed')
638
-            )
639
-            && in_array($response_args['ACK'], array('Success', 'SuccessWithWarning'), true)
640
-        ) {
641
-            // Response status OK, return response parameters for further processing.
642
-            return array('status' => true, 'args' => $response_args);
643
-        }
644
-        $errors = $this->_get_errors($response_args);
645
-        return array('status' => false, 'args' => $errors);
646
-    }
647
-
648
-
649
-
650
-    /**
651
-     *  Log a "Cleared" request.
652
-     *
653
-     * @param array       $request
654
-     * @param EEI_Payment $payment
655
-     * @param string      $info
656
-     * @return void
657
-     */
658
-    private function _log_clean_request($request, $payment, $info)
659
-    {
660
-        $cleaned_request_data = $request;
661
-        unset($cleaned_request_data['PWD'], $cleaned_request_data['USER'], $cleaned_request_data['SIGNATURE']);
662
-        $this->log(array($info => $cleaned_request_data), $payment);
663
-    }
664
-
665
-
666
-
667
-    /**
668
-     *  Get error from the response data.
669
-     *
670
-     * @param array $data_array
671
-     * @return array
672
-     */
673
-    private function _get_errors($data_array)
674
-    {
675
-        $errors = array();
676
-        $n = 0;
677
-        while (isset($data_array[ "L_ERRORCODE{$n}" ])) {
678
-            $l_error_code = isset($data_array[ "L_ERRORCODE{$n}" ])
679
-                ? $data_array[ "L_ERRORCODE{$n}" ]
680
-                : '';
681
-            $l_severity_code = isset($data_array[ "L_SEVERITYCODE{$n}" ])
682
-                ? $data_array[ "L_SEVERITYCODE{$n}" ]
683
-                : '';
684
-            $l_short_message = isset($data_array[ "L_SHORTMESSAGE{$n}" ])
685
-                ? $data_array[ "L_SHORTMESSAGE{$n}" ]
686
-                : '';
687
-            $l_long_message = isset($data_array[ "L_LONGMESSAGE{$n}" ])
688
-                ? $data_array[ "L_LONGMESSAGE{$n}" ]
689
-                : '';
690
-            if ($n === 0) {
691
-                $errors = array(
692
-                    'L_ERRORCODE'    => $l_error_code,
693
-                    'L_SHORTMESSAGE' => $l_short_message,
694
-                    'L_LONGMESSAGE'  => $l_long_message,
695
-                    'L_SEVERITYCODE' => $l_severity_code,
696
-                );
697
-            } else {
698
-                $errors['L_ERRORCODE'] .= ', ' . $l_error_code;
699
-                $errors['L_SHORTMESSAGE'] .= ', ' . $l_short_message;
700
-                $errors['L_LONGMESSAGE'] .= ', ' . $l_long_message;
701
-                $errors['L_SEVERITYCODE'] .= ', ' . $l_severity_code;
702
-            }
703
-            $n++;
704
-        }
705
-        return $errors;
706
-    }
32
+	/**
33
+	 * Merchant API Username.
34
+	 *
35
+	 * @var string
36
+	 */
37
+	protected $_api_username;
38
+
39
+	/**
40
+	 * Merchant API Password.
41
+	 *
42
+	 * @var string
43
+	 */
44
+	protected $_api_password;
45
+
46
+	/**
47
+	 * API Signature.
48
+	 *
49
+	 * @var string
50
+	 */
51
+	protected $_api_signature;
52
+
53
+	/**
54
+	 * Request Shipping address on PP checkout page.
55
+	 *
56
+	 * @var string
57
+	 */
58
+	protected $_request_shipping_addr;
59
+
60
+	/**
61
+	 * Business/personal logo.
62
+	 *
63
+	 * @var string
64
+	 */
65
+	protected $_image_url;
66
+
67
+	/**
68
+	 * gateway URL variable
69
+	 *
70
+	 * @var string
71
+	 */
72
+	protected $_base_gateway_url = '';
73
+
74
+
75
+	/**
76
+	 * number of decimal places to round numbers to when performing calculations
77
+	 *
78
+	 * @var integer
79
+	 */
80
+	protected $decimal_precision = 6;
81
+
82
+
83
+	/**
84
+	 * EEG_Paypal_Express constructor.
85
+	 */
86
+	public function __construct()
87
+	{
88
+		$this->_currencies_supported = array(
89
+			'USD',
90
+			'AUD',
91
+			'BRL',
92
+			'CAD',
93
+			'CZK',
94
+			'DKK',
95
+			'EUR',
96
+			'HKD',
97
+			'HUF',
98
+			'ILS',
99
+			'JPY',
100
+			'MYR',
101
+			'MXN',
102
+			'NOK',
103
+			'NZD',
104
+			'PHP',
105
+			'PLN',
106
+			'GBP',
107
+			'RUB',
108
+			'SGD',
109
+			'SEK',
110
+			'CHF',
111
+			'TWD',
112
+			'THB',
113
+			'TRY',
114
+			'INR',
115
+		);
116
+		parent::__construct();
117
+		$this->decimal_precision = EE_Registry::instance()->CFG->currency->dec_plc;
118
+	}
119
+
120
+
121
+
122
+	/**
123
+	 * Sets the gateway URL variable based on whether debug mode is enabled or not.
124
+	 *
125
+	 * @param array $settings_array
126
+	 */
127
+	public function set_settings($settings_array)
128
+	{
129
+		parent::set_settings($settings_array);
130
+		// Redirect URL.
131
+		$this->_base_gateway_url = $this->_debug_mode
132
+			? 'https://api-3t.sandbox.paypal.com/nvp'
133
+			: 'https://api-3t.paypal.com/nvp';
134
+	}
135
+
136
+
137
+
138
+	/**
139
+	 * @param EEI_Payment $payment
140
+	 * @param array       $billing_info
141
+	 * @param string      $return_url
142
+	 * @param string      $notify_url
143
+	 * @param string      $cancel_url
144
+	 * @return \EE_Payment|\EEI_Payment
145
+	 * @throws \EE_Error
146
+	 */
147
+	public function set_redirection_info(
148
+		$payment,
149
+		$billing_info = array(),
150
+		$return_url = null,
151
+		$notify_url = null,
152
+		$cancel_url = null
153
+	) {
154
+		if (! $payment instanceof EEI_Payment) {
155
+			$payment->set_gateway_response(
156
+				esc_html__(
157
+					'Error. No associated payment was found.',
158
+					'event_espresso'
159
+				)
160
+			);
161
+			$payment->set_status($this->_pay_model->failed_status());
162
+			return $payment;
163
+		}
164
+		$transaction = $payment->transaction();
165
+		if (! $transaction instanceof EEI_Transaction) {
166
+			$payment->set_gateway_response(
167
+				esc_html__(
168
+					'Could not process this payment because it has no associated transaction.',
169
+					'event_espresso'
170
+				)
171
+			);
172
+			$payment->set_status($this->_pay_model->failed_status());
173
+			return $payment;
174
+		}
175
+		$gateway_formatter = $this->_get_gateway_formatter();
176
+		$order_description = mb_strcut($gateway_formatter->formatOrderDescription($payment), 0, 127);
177
+		$primary_registration = $transaction->primary_registration();
178
+		$primary_attendee = $primary_registration instanceof EE_Registration
179
+			? $primary_registration->attendee()
180
+			: false;
181
+		$locale = explode('-', get_bloginfo('language'));
182
+		// Gather request parameters.
183
+		$token_request_dtls = array(
184
+			'METHOD'                         => 'SetExpressCheckout',
185
+			'PAYMENTREQUEST_0_AMT'           => $payment->amount(),
186
+			'PAYMENTREQUEST_0_CURRENCYCODE'  => $payment->currency_code(),
187
+			'PAYMENTREQUEST_0_DESC'          => $order_description,
188
+			'RETURNURL'                      => $return_url,
189
+			'CANCELURL'                      => $cancel_url,
190
+			'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
191
+			// Buyer does not need to create a PayPal account to check out.
192
+			// This is referred to as PayPal Account Optional.
193
+			'SOLUTIONTYPE'                   => 'Sole',
194
+			// Locale of the pages displayed by PayPal during Express Checkout.
195
+			'LOCALECODE'                     => $locale[1]
196
+		);
197
+		// Show itemized list.
198
+		$itemized_list = $this->itemize_list($payment, $transaction);
199
+		$token_request_dtls = array_merge($token_request_dtls, $itemized_list);
200
+		// Automatically filling out shipping and contact information.
201
+		if ($this->_request_shipping_addr && $primary_attendee instanceof EEI_Attendee) {
202
+			// If you do not pass the shipping address, PayPal obtains it from the buyer's account profile.
203
+			$token_request_dtls['NOSHIPPING'] = '2';
204
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTREET'] = $primary_attendee->address();
205
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTREET2'] = $primary_attendee->address2();
206
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOCITY'] = $primary_attendee->city();
207
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOSTATE'] = $primary_attendee->state_abbrev();
208
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE'] = $primary_attendee->country_ID();
209
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOZIP'] = $primary_attendee->zip();
210
+			$token_request_dtls['PAYMENTREQUEST_0_EMAIL'] = $primary_attendee->email();
211
+			$token_request_dtls['PAYMENTREQUEST_0_SHIPTOPHONENUM'] = $primary_attendee->phone();
212
+		} elseif (! $this->_request_shipping_addr) {
213
+			// Do not request shipping details on the PP Checkout page.
214
+			$token_request_dtls['NOSHIPPING'] = '1';
215
+			$token_request_dtls['REQCONFIRMSHIPPING'] = '0';
216
+		}
217
+		// Used a business/personal logo on the PayPal page.
218
+		if (! empty($this->_image_url)) {
219
+			$token_request_dtls['LOGOIMG'] = $this->_image_url;
220
+		}
221
+		$token_request_dtls = apply_filters(
222
+			'FHEE__EEG_Paypal_Express__set_redirection_info__arguments',
223
+			$token_request_dtls,
224
+			$this
225
+		);
226
+		// Request PayPal token.
227
+		$token_request_response = $this->_ppExpress_request($token_request_dtls, 'Payment Token', $payment);
228
+		$token_rstatus = $this->_ppExpress_check_response($token_request_response);
229
+		$response_args = (isset($token_rstatus['args']) && is_array($token_rstatus['args']))
230
+			? $token_rstatus['args']
231
+			: array();
232
+		if ($token_rstatus['status']) {
233
+			// We got the Token so we may continue with the payment and redirect the client.
234
+			$payment->set_details($response_args);
235
+			$gateway_url = $this->_debug_mode ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
236
+			$payment->set_redirect_url(
237
+				$gateway_url
238
+				. '/checkoutnow?useraction=commit&cmd=_express-checkout&token='
239
+				. $response_args['TOKEN']
240
+			);
241
+		} else {
242
+			if (isset($response_args['L_ERRORCODE'])) {
243
+				$payment->set_gateway_response($response_args['L_ERRORCODE'] . '; ' . $response_args['L_SHORTMESSAGE']);
244
+			} else {
245
+				$payment->set_gateway_response(
246
+					esc_html__(
247
+						'Error occurred while trying to setup the Express Checkout.',
248
+						'event_espresso'
249
+					)
250
+				);
251
+			}
252
+			$payment->set_details($response_args);
253
+			$payment->set_status($this->_pay_model->failed_status());
254
+		}
255
+		return $payment;
256
+	}
257
+
258
+
259
+
260
+	/**
261
+	 * @param array           $update_info {
262
+	 * @type string           $gateway_txn_id
263
+	 * @type string status an EEMI_Payment status
264
+	 *                                     }
265
+	 * @param EEI_Transaction $transaction
266
+	 * @return EEI_Payment
267
+	 */
268
+	public function handle_payment_update($update_info, $transaction)
269
+	{
270
+		$payment = $transaction instanceof EEI_Transaction ? $transaction->last_payment() : null;
271
+		if ($payment instanceof EEI_Payment) {
272
+			$this->log(array('Return from Authorization' => $update_info), $payment);
273
+			$transaction = $payment->transaction();
274
+			if (! $transaction instanceof EEI_Transaction) {
275
+				$payment->set_gateway_response(
276
+					esc_html__(
277
+						'Could not process this payment because it has no associated transaction.',
278
+						'event_espresso'
279
+					)
280
+				);
281
+				$payment->set_status($this->_pay_model->failed_status());
282
+				return $payment;
283
+			}
284
+			$primary_registrant = $transaction->primary_registration();
285
+			$payment_details = $payment->details();
286
+			// Check if we still have the token.
287
+			if (! isset($payment_details['TOKEN']) || empty($payment_details['TOKEN'])) {
288
+				$payment->set_status($this->_pay_model->failed_status());
289
+				return $payment;
290
+			}
291
+			$cdetails_request_dtls = array(
292
+				'METHOD' => 'GetExpressCheckoutDetails',
293
+				'TOKEN'  => $payment_details['TOKEN'],
294
+			);
295
+			// Request Customer Details.
296
+			$cdetails_request_response = $this->_ppExpress_request(
297
+				$cdetails_request_dtls,
298
+				'Customer Details',
299
+				$payment
300
+			);
301
+			$cdetails_rstatus = $this->_ppExpress_check_response($cdetails_request_response);
302
+			$cdata_response_args = (isset($cdetails_rstatus['args']) && is_array($cdetails_rstatus['args']))
303
+				? $cdetails_rstatus['args']
304
+				: array();
305
+			if ($cdetails_rstatus['status']) {
306
+				// We got the PayerID so now we can Complete the transaction.
307
+				$docheckout_request_dtls = array(
308
+					'METHOD'                         => 'DoExpressCheckoutPayment',
309
+					'PAYERID'                        => $cdata_response_args['PAYERID'],
310
+					'TOKEN'                          => $payment_details['TOKEN'],
311
+					'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
312
+					'PAYMENTREQUEST_0_AMT'           => $payment->amount(),
313
+					'PAYMENTREQUEST_0_CURRENCYCODE'  => $payment->currency_code(),
314
+				);
315
+				 // Include itemized list.
316
+				$itemized_list = $this->itemize_list(
317
+					$payment,
318
+					$transaction,
319
+					$cdata_response_args
320
+				);
321
+				$docheckout_request_dtls = array_merge($docheckout_request_dtls, $itemized_list);
322
+				// Payment Checkout/Capture.
323
+				$docheckout_request_response = $this->_ppExpress_request(
324
+					$docheckout_request_dtls,
325
+					'Do Payment',
326
+					$payment
327
+				);
328
+				$docheckout_rstatus = $this->_ppExpress_check_response($docheckout_request_response);
329
+				$docheckout_response_args = (isset($docheckout_rstatus['args']) && is_array($docheckout_rstatus['args']))
330
+					? $docheckout_rstatus['args']
331
+					: array();
332
+				if ($docheckout_rstatus['status']) {
333
+					// All is well, payment approved.
334
+					$primary_registration_code = $primary_registrant instanceof EE_Registration ?
335
+						$primary_registrant->reg_code()
336
+						: '';
337
+					$payment->set_extra_accntng($primary_registration_code);
338
+					$payment->set_amount(isset($docheckout_response_args['PAYMENTINFO_0_AMT'])
339
+						? (float) $docheckout_response_args['PAYMENTINFO_0_AMT']
340
+						: 0);
341
+					$payment->set_txn_id_chq_nmbr(isset($docheckout_response_args['PAYMENTINFO_0_TRANSACTIONID'])
342
+						? $docheckout_response_args['PAYMENTINFO_0_TRANSACTIONID']
343
+						: null);
344
+					$payment->set_details($cdata_response_args);
345
+					$payment->set_gateway_response(isset($docheckout_response_args['PAYMENTINFO_0_ACK'])
346
+						? $docheckout_response_args['PAYMENTINFO_0_ACK']
347
+						: '');
348
+					$payment->set_status($this->_pay_model->approved_status());
349
+				} else {
350
+					if (isset($docheckout_response_args['L_ERRORCODE'])) {
351
+						$payment->set_gateway_response(
352
+							$docheckout_response_args['L_ERRORCODE']
353
+							. '; '
354
+							. $docheckout_response_args['L_SHORTMESSAGE']
355
+						);
356
+					} else {
357
+						$payment->set_gateway_response(
358
+							esc_html__(
359
+								'Error occurred while trying to Capture the funds.',
360
+								'event_espresso'
361
+							)
362
+						);
363
+					}
364
+					$payment->set_details($docheckout_response_args);
365
+					$payment->set_status($this->_pay_model->declined_status());
366
+				}
367
+			} else {
368
+				if (isset($cdata_response_args['L_ERRORCODE'])) {
369
+					$payment->set_gateway_response(
370
+						$cdata_response_args['L_ERRORCODE']
371
+						. '; '
372
+						. $cdata_response_args['L_SHORTMESSAGE']
373
+					);
374
+				} else {
375
+					$payment->set_gateway_response(
376
+						esc_html__(
377
+							'Error occurred while trying to get payment Details from PayPal.',
378
+							'event_espresso'
379
+						)
380
+					);
381
+				}
382
+				$payment->set_details($cdata_response_args);
383
+				$payment->set_status($this->_pay_model->failed_status());
384
+			}
385
+		} else {
386
+			$payment->set_gateway_response(
387
+				esc_html__(
388
+					'Error occurred while trying to process the payment.',
389
+					'event_espresso'
390
+				)
391
+			);
392
+			$payment->set_status($this->_pay_model->failed_status());
393
+		}
394
+		return $payment;
395
+	}
396
+
397
+
398
+
399
+	/**
400
+	 *  Make a list of items that are in the giver transaction.
401
+	 *
402
+	 * @param EEI_Payment     $payment
403
+	 * @param EEI_Transaction $transaction
404
+	 * @param array           $request_response_args Data from a previous communication with PP.
405
+	 * @return array
406
+	 */
407
+	public function itemize_list(EEI_Payment $payment, EEI_Transaction $transaction, $request_response_args = array())
408
+	{
409
+		$itemized_list = array();
410
+		$gateway_formatter = $this->_get_gateway_formatter();
411
+		// If we have data from a previous communication with PP (on this transaction) we may use that for our list...
412
+		if (
413
+			! empty($request_response_args)
414
+			&& array_key_exists('L_PAYMENTREQUEST_0_AMT0', $request_response_args)
415
+			&& array_key_exists('PAYMENTREQUEST_0_ITEMAMT', $request_response_args)
416
+		) {
417
+			foreach ($request_response_args as $arg_key => $arg_val) {
418
+				if (
419
+					strpos($arg_key, 'PAYMENTREQUEST_') !== false
420
+					&& strpos($arg_key, 'NOTIFYURL') === false
421
+				) {
422
+					$itemized_list[ $arg_key ] = $arg_val;
423
+				}
424
+			}
425
+			// If we got only a few Items then something is not right.
426
+			if (count($itemized_list) > 2) {
427
+				return $itemized_list;
428
+			} else {
429
+				if (WP_DEBUG) {
430
+					throw new EE_Error(
431
+						sprintf(
432
+							esc_html__(
433
+								// @codingStandardsIgnoreStart
434
+								'Unable to continue with the checkout because a proper purchase list could not be generated. The purchased list we could have sent was %1$s',
435
+								// @codingStandardsIgnoreEnd
436
+								'event_espresso'
437
+							),
438
+							wp_json_encode($itemized_list)
439
+						)
440
+					);
441
+				}
442
+				// Reset the list and log an error, maybe allow to try and generate a new list (below).
443
+				$itemized_list = array();
444
+				$this->log(
445
+					array(
446
+						(string) esc_html__(
447
+							'Could not generate a proper item list with:',
448
+							'event_espresso'
449
+						) => $request_response_args
450
+					),
451
+					$payment
452
+				);
453
+			}
454
+		}
455
+		// ...otherwise we generate a new list for this transaction.
456
+		if ($this->_money->compare_floats($payment->amount(), $transaction->total(), '==')) {
457
+			$item_num = 0;
458
+			$itemized_sum = 0;
459
+			$total_line_items = $transaction->total_line_item();
460
+			// Go through each item in the list.
461
+			foreach ($total_line_items->get_items() as $line_item) {
462
+				if ($line_item instanceof EE_Line_Item) {
463
+					// PayPal doesn't like line items with 0.00 amount, so we may skip those.
464
+					if (EEH_Money::compare_floats($line_item->pretaxTotal(), '0.00', '==')) {
465
+						continue;
466
+					}
467
+					$unit_price = $line_item->unit_price();
468
+					$line_item_quantity = $line_item->quantity();
469
+					// This is a discount.
470
+					if ($line_item->is_percent()) {
471
+						$unit_price = $line_item->pretaxTotal();
472
+						$line_item_quantity = 1;
473
+					}
474
+					// Item Name.
475
+					$itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
476
+						$gateway_formatter->formatLineItemName($line_item, $payment),
477
+						0,
478
+						127
479
+					);
480
+					// Item description.
481
+					$itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = mb_strcut(
482
+						$gateway_formatter->formatLineItemDesc($line_item, $payment),
483
+						0,
484
+						127
485
+					);
486
+					// Cost of individual item.
487
+					$itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
488
+						$unit_price,
489
+						$this->decimal_precision
490
+					);
491
+					// Item Number.
492
+					$itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
493
+					// Item quantity.
494
+					$itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = $line_item_quantity;
495
+					// Digital item is sold.
496
+					$itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
497
+					$itemized_sum += $line_item->pretaxTotal();
498
+					++$item_num;
499
+				}
500
+			}
501
+			// Item's sales S/H and tax amount.
502
+			$itemized_list['PAYMENTREQUEST_0_ITEMAMT'] = $total_line_items->get_items_total();
503
+			$itemized_list['PAYMENTREQUEST_0_TAXAMT'] = $total_line_items->get_total_tax();
504
+			$itemized_list['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0';
505
+			$itemized_list['PAYMENTREQUEST_0_HANDLINGAMT'] = '0';
506
+			$itemized_sum_diff_from_txn_total = round(
507
+				$transaction->total() - $itemized_sum - $total_line_items->get_total_tax(),
508
+				$this->decimal_precision
509
+			);
510
+			// If we were not able to recognize some item like promotion, surcharge or cancellation,
511
+			// add the difference as an extra line item.
512
+			if ($this->_money->compare_floats($itemized_sum_diff_from_txn_total, 0, '!=')) {
513
+				// Item Name.
514
+				$itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
515
+					esc_html__(
516
+						'Other (promotion/surcharge/cancellation)',
517
+						'event_espresso'
518
+					),
519
+					0,
520
+					127
521
+				);
522
+				// Item description.
523
+				$itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = '';
524
+				// Cost of individual item.
525
+				$itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
526
+					$itemized_sum_diff_from_txn_total,
527
+					$this->decimal_precision
528
+				);
529
+				// Item Number.
530
+				$itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
531
+				// Item quantity.
532
+				$itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = 1;
533
+				// Digital item is sold.
534
+				$itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
535
+				$item_num++;
536
+			}
537
+		} else {
538
+			// Just one Item.
539
+			// Item Name.
540
+			$itemized_list['L_PAYMENTREQUEST_0_NAME0'] = mb_strcut(
541
+				$gateway_formatter->formatPartialPaymentLineItemName($payment),
542
+				0,
543
+				127
544
+			);
545
+			// Item description.
546
+			$itemized_list['L_PAYMENTREQUEST_0_DESC0'] = mb_strcut(
547
+				$gateway_formatter->formatPartialPaymentLineItemDesc($payment),
548
+				0,
549
+				127
550
+			);
551
+			// Cost of individual item.
552
+			$itemized_list['L_PAYMENTREQUEST_0_AMT0'] = $gateway_formatter->formatCurrency(
553
+				$payment->amount(),
554
+				$this->decimal_precision
555
+			);
556
+			// Item Number.
557
+			$itemized_list['L_PAYMENTREQUEST_0_NUMBER0'] = 1;
558
+			// Item quantity.
559
+			$itemized_list['L_PAYMENTREQUEST_0_QTY0'] = 1;
560
+			// Digital item is sold.
561
+			$itemized_list['L_PAYMENTREQUEST_0_ITEMCATEGORY0'] = 'Physical';
562
+			// Item's sales S/H and tax amount.
563
+			$itemized_list['PAYMENTREQUEST_0_ITEMAMT'] = $gateway_formatter->formatCurrency(
564
+				$payment->amount(),
565
+				$this->decimal_precision
566
+			);
567
+			$itemized_list['PAYMENTREQUEST_0_TAXAMT'] = '0';
568
+			$itemized_list['PAYMENTREQUEST_0_SHIPPINGAMT'] = '0';
569
+			$itemized_list['PAYMENTREQUEST_0_HANDLINGAMT'] = '0';
570
+		}
571
+		return $itemized_list;
572
+	}
573
+
574
+
575
+
576
+	/**
577
+	 *  Make the Express checkout request.
578
+	 *
579
+	 * @param array       $request_params
580
+	 * @param string      $request_text
581
+	 * @param EEI_Payment $payment
582
+	 * @return mixed
583
+	 */
584
+	public function _ppExpress_request($request_params, $request_text, $payment)
585
+	{
586
+		$request_dtls = array(
587
+			'VERSION' => '204.0',
588
+			'USER' => $this->_api_username,
589
+			'PWD' => $this->_api_password,
590
+			'SIGNATURE' => $this->_api_signature,
591
+			// EE will blow up if you change this
592
+			'BUTTONSOURCE' => 'EventEspresso_SP',
593
+		);
594
+		$dtls = array_merge($request_dtls, $request_params);
595
+		$this->_log_clean_request($dtls, $payment, $request_text . ' Request');
596
+		// Request Customer Details.
597
+		$request_response = wp_remote_post(
598
+			$this->_base_gateway_url,
599
+			array(
600
+				'method'      => 'POST',
601
+				'timeout'     => 45,
602
+				'httpversion' => '1.1',
603
+				'cookies'     => array(),
604
+				'headers'     => array(),
605
+				'body'        => http_build_query($dtls, '', '&'),
606
+			)
607
+		);
608
+		// Log the response.
609
+		$this->log(array($request_text . ' Response' => $request_response), $payment);
610
+		return $request_response;
611
+	}
612
+
613
+
614
+
615
+	/**
616
+	 *  Check the response status.
617
+	 *
618
+	 * @param mixed $request_response
619
+	 * @return array
620
+	 */
621
+	public function _ppExpress_check_response($request_response)
622
+	{
623
+		if (is_wp_error($request_response) || empty($request_response['body'])) {
624
+			// If we got here then there was an error in this request.
625
+			return array('status' => false, 'args' => $request_response);
626
+		}
627
+		$response_args = array();
628
+		parse_str(urldecode($request_response['body']), $response_args);
629
+		if (! isset($response_args['ACK'])) {
630
+			return array('status' => false, 'args' => $request_response);
631
+		}
632
+		if (
633
+			(
634
+				isset($response_args['PAYERID'])
635
+				|| isset($response_args['TOKEN'])
636
+				|| isset($response_args['PAYMENTINFO_0_TRANSACTIONID'])
637
+				|| (isset($response_args['PAYMENTSTATUS']) && $response_args['PAYMENTSTATUS'] === 'Completed')
638
+			)
639
+			&& in_array($response_args['ACK'], array('Success', 'SuccessWithWarning'), true)
640
+		) {
641
+			// Response status OK, return response parameters for further processing.
642
+			return array('status' => true, 'args' => $response_args);
643
+		}
644
+		$errors = $this->_get_errors($response_args);
645
+		return array('status' => false, 'args' => $errors);
646
+	}
647
+
648
+
649
+
650
+	/**
651
+	 *  Log a "Cleared" request.
652
+	 *
653
+	 * @param array       $request
654
+	 * @param EEI_Payment $payment
655
+	 * @param string      $info
656
+	 * @return void
657
+	 */
658
+	private function _log_clean_request($request, $payment, $info)
659
+	{
660
+		$cleaned_request_data = $request;
661
+		unset($cleaned_request_data['PWD'], $cleaned_request_data['USER'], $cleaned_request_data['SIGNATURE']);
662
+		$this->log(array($info => $cleaned_request_data), $payment);
663
+	}
664
+
665
+
666
+
667
+	/**
668
+	 *  Get error from the response data.
669
+	 *
670
+	 * @param array $data_array
671
+	 * @return array
672
+	 */
673
+	private function _get_errors($data_array)
674
+	{
675
+		$errors = array();
676
+		$n = 0;
677
+		while (isset($data_array[ "L_ERRORCODE{$n}" ])) {
678
+			$l_error_code = isset($data_array[ "L_ERRORCODE{$n}" ])
679
+				? $data_array[ "L_ERRORCODE{$n}" ]
680
+				: '';
681
+			$l_severity_code = isset($data_array[ "L_SEVERITYCODE{$n}" ])
682
+				? $data_array[ "L_SEVERITYCODE{$n}" ]
683
+				: '';
684
+			$l_short_message = isset($data_array[ "L_SHORTMESSAGE{$n}" ])
685
+				? $data_array[ "L_SHORTMESSAGE{$n}" ]
686
+				: '';
687
+			$l_long_message = isset($data_array[ "L_LONGMESSAGE{$n}" ])
688
+				? $data_array[ "L_LONGMESSAGE{$n}" ]
689
+				: '';
690
+			if ($n === 0) {
691
+				$errors = array(
692
+					'L_ERRORCODE'    => $l_error_code,
693
+					'L_SHORTMESSAGE' => $l_short_message,
694
+					'L_LONGMESSAGE'  => $l_long_message,
695
+					'L_SEVERITYCODE' => $l_severity_code,
696
+				);
697
+			} else {
698
+				$errors['L_ERRORCODE'] .= ', ' . $l_error_code;
699
+				$errors['L_SHORTMESSAGE'] .= ', ' . $l_short_message;
700
+				$errors['L_LONGMESSAGE'] .= ', ' . $l_long_message;
701
+				$errors['L_SEVERITYCODE'] .= ', ' . $l_severity_code;
702
+			}
703
+			$n++;
704
+		}
705
+		return $errors;
706
+	}
707 707
 }
Please login to merge, or discard this patch.
Spacing   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -12,7 +12,7 @@  discard block
 block discarded – undo
12 12
  */
13 13
 
14 14
 // Quickfix to address https://events.codebasehq.com/projects/event-espresso/tickets/11089 ASAP
15
-if (! function_exists('mb_strcut')) {
15
+if ( ! function_exists('mb_strcut')) {
16 16
     /**
17 17
      * Very simple mimic of mb_substr (which WP ensures exists in wp-includes/compat.php). Still has all the problems of mb_substr
18 18
      * (namely, that we might send too many characters to PayPal; however in this case they just issue a warning but nothing breaks)
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
         $notify_url = null,
152 152
         $cancel_url = null
153 153
     ) {
154
-        if (! $payment instanceof EEI_Payment) {
154
+        if ( ! $payment instanceof EEI_Payment) {
155 155
             $payment->set_gateway_response(
156 156
                 esc_html__(
157 157
                     'Error. No associated payment was found.',
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
             return $payment;
163 163
         }
164 164
         $transaction = $payment->transaction();
165
-        if (! $transaction instanceof EEI_Transaction) {
165
+        if ( ! $transaction instanceof EEI_Transaction) {
166 166
             $payment->set_gateway_response(
167 167
                 esc_html__(
168 168
                     'Could not process this payment because it has no associated transaction.',
@@ -209,13 +209,13 @@  discard block
 block discarded – undo
209 209
             $token_request_dtls['PAYMENTREQUEST_0_SHIPTOZIP'] = $primary_attendee->zip();
210 210
             $token_request_dtls['PAYMENTREQUEST_0_EMAIL'] = $primary_attendee->email();
211 211
             $token_request_dtls['PAYMENTREQUEST_0_SHIPTOPHONENUM'] = $primary_attendee->phone();
212
-        } elseif (! $this->_request_shipping_addr) {
212
+        } elseif ( ! $this->_request_shipping_addr) {
213 213
             // Do not request shipping details on the PP Checkout page.
214 214
             $token_request_dtls['NOSHIPPING'] = '1';
215 215
             $token_request_dtls['REQCONFIRMSHIPPING'] = '0';
216 216
         }
217 217
         // Used a business/personal logo on the PayPal page.
218
-        if (! empty($this->_image_url)) {
218
+        if ( ! empty($this->_image_url)) {
219 219
             $token_request_dtls['LOGOIMG'] = $this->_image_url;
220 220
         }
221 221
         $token_request_dtls = apply_filters(
@@ -240,7 +240,7 @@  discard block
 block discarded – undo
240 240
             );
241 241
         } else {
242 242
             if (isset($response_args['L_ERRORCODE'])) {
243
-                $payment->set_gateway_response($response_args['L_ERRORCODE'] . '; ' . $response_args['L_SHORTMESSAGE']);
243
+                $payment->set_gateway_response($response_args['L_ERRORCODE'].'; '.$response_args['L_SHORTMESSAGE']);
244 244
             } else {
245 245
                 $payment->set_gateway_response(
246 246
                     esc_html__(
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
         if ($payment instanceof EEI_Payment) {
272 272
             $this->log(array('Return from Authorization' => $update_info), $payment);
273 273
             $transaction = $payment->transaction();
274
-            if (! $transaction instanceof EEI_Transaction) {
274
+            if ( ! $transaction instanceof EEI_Transaction) {
275 275
                 $payment->set_gateway_response(
276 276
                     esc_html__(
277 277
                         'Could not process this payment because it has no associated transaction.',
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
             $primary_registrant = $transaction->primary_registration();
285 285
             $payment_details = $payment->details();
286 286
             // Check if we still have the token.
287
-            if (! isset($payment_details['TOKEN']) || empty($payment_details['TOKEN'])) {
287
+            if ( ! isset($payment_details['TOKEN']) || empty($payment_details['TOKEN'])) {
288 288
                 $payment->set_status($this->_pay_model->failed_status());
289 289
                 return $payment;
290 290
             }
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
419 419
                     strpos($arg_key, 'PAYMENTREQUEST_') !== false
420 420
                     && strpos($arg_key, 'NOTIFYURL') === false
421 421
                 ) {
422
-                    $itemized_list[ $arg_key ] = $arg_val;
422
+                    $itemized_list[$arg_key] = $arg_val;
423 423
                 }
424 424
             }
425 425
             // If we got only a few Items then something is not right.
@@ -472,28 +472,28 @@  discard block
 block discarded – undo
472 472
                         $line_item_quantity = 1;
473 473
                     }
474 474
                     // Item Name.
475
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
475
+                    $itemized_list['L_PAYMENTREQUEST_0_NAME'.$item_num] = mb_strcut(
476 476
                         $gateway_formatter->formatLineItemName($line_item, $payment),
477 477
                         0,
478 478
                         127
479 479
                     );
480 480
                     // Item description.
481
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = mb_strcut(
481
+                    $itemized_list['L_PAYMENTREQUEST_0_DESC'.$item_num] = mb_strcut(
482 482
                         $gateway_formatter->formatLineItemDesc($line_item, $payment),
483 483
                         0,
484 484
                         127
485 485
                     );
486 486
                     // Cost of individual item.
487
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
487
+                    $itemized_list['L_PAYMENTREQUEST_0_AMT'.$item_num] = $gateway_formatter->formatCurrency(
488 488
                         $unit_price,
489 489
                         $this->decimal_precision
490 490
                     );
491 491
                     // Item Number.
492
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
492
+                    $itemized_list['L_PAYMENTREQUEST_0_NUMBER'.$item_num] = $item_num + 1;
493 493
                     // Item quantity.
494
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = $line_item_quantity;
494
+                    $itemized_list['L_PAYMENTREQUEST_0_QTY'.$item_num] = $line_item_quantity;
495 495
                     // Digital item is sold.
496
-                    $itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
496
+                    $itemized_list['L_PAYMENTREQUEST_0_ITEMCATEGORY'.$item_num] = 'Physical';
497 497
                     $itemized_sum += $line_item->pretaxTotal();
498 498
                     ++$item_num;
499 499
                 }
@@ -511,7 +511,7 @@  discard block
 block discarded – undo
511 511
             // add the difference as an extra line item.
512 512
             if ($this->_money->compare_floats($itemized_sum_diff_from_txn_total, 0, '!=')) {
513 513
                 // Item Name.
514
-                $itemized_list[ 'L_PAYMENTREQUEST_0_NAME' . $item_num ] = mb_strcut(
514
+                $itemized_list['L_PAYMENTREQUEST_0_NAME'.$item_num] = mb_strcut(
515 515
                     esc_html__(
516 516
                         'Other (promotion/surcharge/cancellation)',
517 517
                         'event_espresso'
@@ -520,18 +520,18 @@  discard block
 block discarded – undo
520 520
                     127
521 521
                 );
522 522
                 // Item description.
523
-                $itemized_list[ 'L_PAYMENTREQUEST_0_DESC' . $item_num ] = '';
523
+                $itemized_list['L_PAYMENTREQUEST_0_DESC'.$item_num] = '';
524 524
                 // Cost of individual item.
525
-                $itemized_list[ 'L_PAYMENTREQUEST_0_AMT' . $item_num ] = $gateway_formatter->formatCurrency(
525
+                $itemized_list['L_PAYMENTREQUEST_0_AMT'.$item_num] = $gateway_formatter->formatCurrency(
526 526
                     $itemized_sum_diff_from_txn_total,
527 527
                     $this->decimal_precision
528 528
                 );
529 529
                 // Item Number.
530
-                $itemized_list[ 'L_PAYMENTREQUEST_0_NUMBER' . $item_num ] = $item_num + 1;
530
+                $itemized_list['L_PAYMENTREQUEST_0_NUMBER'.$item_num] = $item_num + 1;
531 531
                 // Item quantity.
532
-                $itemized_list[ 'L_PAYMENTREQUEST_0_QTY' . $item_num ] = 1;
532
+                $itemized_list['L_PAYMENTREQUEST_0_QTY'.$item_num] = 1;
533 533
                 // Digital item is sold.
534
-                $itemized_list[ 'L_PAYMENTREQUEST_0_ITEMCATEGORY' . $item_num ] = 'Physical';
534
+                $itemized_list['L_PAYMENTREQUEST_0_ITEMCATEGORY'.$item_num] = 'Physical';
535 535
                 $item_num++;
536 536
             }
537 537
         } else {
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
             'BUTTONSOURCE' => 'EventEspresso_SP',
593 593
         );
594 594
         $dtls = array_merge($request_dtls, $request_params);
595
-        $this->_log_clean_request($dtls, $payment, $request_text . ' Request');
595
+        $this->_log_clean_request($dtls, $payment, $request_text.' Request');
596 596
         // Request Customer Details.
597 597
         $request_response = wp_remote_post(
598 598
             $this->_base_gateway_url,
@@ -606,7 +606,7 @@  discard block
 block discarded – undo
606 606
             )
607 607
         );
608 608
         // Log the response.
609
-        $this->log(array($request_text . ' Response' => $request_response), $payment);
609
+        $this->log(array($request_text.' Response' => $request_response), $payment);
610 610
         return $request_response;
611 611
     }
612 612
 
@@ -626,7 +626,7 @@  discard block
 block discarded – undo
626 626
         }
627 627
         $response_args = array();
628 628
         parse_str(urldecode($request_response['body']), $response_args);
629
-        if (! isset($response_args['ACK'])) {
629
+        if ( ! isset($response_args['ACK'])) {
630 630
             return array('status' => false, 'args' => $request_response);
631 631
         }
632 632
         if (
@@ -674,18 +674,18 @@  discard block
 block discarded – undo
674 674
     {
675 675
         $errors = array();
676 676
         $n = 0;
677
-        while (isset($data_array[ "L_ERRORCODE{$n}" ])) {
678
-            $l_error_code = isset($data_array[ "L_ERRORCODE{$n}" ])
679
-                ? $data_array[ "L_ERRORCODE{$n}" ]
677
+        while (isset($data_array["L_ERRORCODE{$n}"])) {
678
+            $l_error_code = isset($data_array["L_ERRORCODE{$n}"])
679
+                ? $data_array["L_ERRORCODE{$n}"]
680 680
                 : '';
681
-            $l_severity_code = isset($data_array[ "L_SEVERITYCODE{$n}" ])
682
-                ? $data_array[ "L_SEVERITYCODE{$n}" ]
681
+            $l_severity_code = isset($data_array["L_SEVERITYCODE{$n}"])
682
+                ? $data_array["L_SEVERITYCODE{$n}"]
683 683
                 : '';
684
-            $l_short_message = isset($data_array[ "L_SHORTMESSAGE{$n}" ])
685
-                ? $data_array[ "L_SHORTMESSAGE{$n}" ]
684
+            $l_short_message = isset($data_array["L_SHORTMESSAGE{$n}"])
685
+                ? $data_array["L_SHORTMESSAGE{$n}"]
686 686
                 : '';
687
-            $l_long_message = isset($data_array[ "L_LONGMESSAGE{$n}" ])
688
-                ? $data_array[ "L_LONGMESSAGE{$n}" ]
687
+            $l_long_message = isset($data_array["L_LONGMESSAGE{$n}"])
688
+                ? $data_array["L_LONGMESSAGE{$n}"]
689 689
                 : '';
690 690
             if ($n === 0) {
691 691
                 $errors = array(
@@ -695,10 +695,10 @@  discard block
 block discarded – undo
695 695
                     'L_SEVERITYCODE' => $l_severity_code,
696 696
                 );
697 697
             } else {
698
-                $errors['L_ERRORCODE'] .= ', ' . $l_error_code;
699
-                $errors['L_SHORTMESSAGE'] .= ', ' . $l_short_message;
700
-                $errors['L_LONGMESSAGE'] .= ', ' . $l_long_message;
701
-                $errors['L_SEVERITYCODE'] .= ', ' . $l_severity_code;
698
+                $errors['L_ERRORCODE'] .= ', '.$l_error_code;
699
+                $errors['L_SHORTMESSAGE'] .= ', '.$l_short_message;
700
+                $errors['L_LONGMESSAGE'] .= ', '.$l_long_message;
701
+                $errors['L_SEVERITYCODE'] .= ', '.$l_severity_code;
702 702
             }
703 703
             $n++;
704 704
         }
Please login to merge, or discard this patch.
payment_methods/Paypal_Standard/EEG_Paypal_Standard.gateway.php 2 patches
Indentation   +603 added lines, -603 removed lines patch added patch discarded remove patch
@@ -19,608 +19,608 @@
 block discarded – undo
19 19
 class EEG_Paypal_Standard extends EE_Offsite_Gateway
20 20
 {
21 21
 
22
-    /**
23
-     * Name for the wp option used to save the itemized payment
24
-     */
25
-    const itemized_payment_option_name = '_itemized_payment';
26
-
27
-    protected $_paypal_id;
28
-
29
-    protected $_image_url;
30
-
31
-    protected $_shipping_details;
32
-
33
-    protected $_paypal_shipping;
34
-
35
-    protected $_paypal_taxes;
36
-
37
-    protected $_gateway_url;
38
-
39
-    protected $_currencies_supported = array(
40
-        'USD',
41
-        'GBP',
42
-        'CAD',
43
-        'AUD',
44
-        'BRL',
45
-        'CHF',
46
-        'CZK',
47
-        'DKK',
48
-        'EUR',
49
-        'HKD',
50
-        'HUF',
51
-        'ILS',
52
-        'JPY',
53
-        'MXN',
54
-        'MYR',
55
-        'NOK',
56
-        'NZD',
57
-        'PHP',
58
-        'PLN',
59
-        'SEK',
60
-        'SGD',
61
-        'THB',
62
-        'TRY',
63
-        'TWD',
64
-        'RUB'
65
-    );
66
-
67
-
68
-    /**
69
-     * EEG_Paypal_Standard constructor.
70
-     *
71
-     * @return EEG_Paypal_Standard
72
-     */
73
-    public function __construct()
74
-    {
75
-        $this->set_uses_separate_IPN_request(true);
76
-        parent::__construct();
77
-    }
78
-
79
-
80
-    /**
81
-     * Also sets the gateway url class variable based on whether debug mode is enabled or not.
82
-     *
83
-     * @param array $settings_array
84
-     */
85
-    public function set_settings($settings_array)
86
-    {
87
-        parent::set_settings($settings_array);
88
-        $this->_gateway_url = $this->_debug_mode
89
-            ? 'https://www.sandbox.paypal.com/cgi-bin/webscr'
90
-            : 'https://www.paypal.com/cgi-bin/webscr';
91
-    }
92
-
93
-
94
-    /**
95
-     * @param EEI_Payment $payment      the payment to process
96
-     * @param array       $billing_info but should be empty for this gateway
97
-     * @param string      $return_url   URL to send the user to after payment on the payment provider's website
98
-     * @param string      $notify_url   URL to send the instant payment notification
99
-     * @param string      $cancel_url   URL to send the user to after a cancelled payment attempt
100
-     *                                  on the payment provider's website
101
-     * @return EEI_Payment
102
-     * @throws \EE_Error
103
-     */
104
-    public function set_redirection_info(
105
-        $payment,
106
-        $billing_info = array(),
107
-        $return_url = null,
108
-        $notify_url = null,
109
-        $cancel_url = null
110
-    ) {
111
-        $redirect_args = array();
112
-        $transaction = $payment->transaction();
113
-        $gateway_formatter = $this->_get_gateway_formatter();
114
-        $item_num = 1;
115
-        /** @type EE_Line_Item $total_line_item */
116
-        $total_line_item = $transaction->total_line_item();
117
-
118
-        $total_discounts_to_cart_total = $transaction->paid();
119
-        // only itemize the order if we're paying for the rest of the order's amount
120
-        if (EEH_Money::compare_floats($payment->amount(), $transaction->total(), '==')) {
121
-            $payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true);
122
-            // this payment is for the remaining transaction amount,
123
-            // keep track of exactly how much the itemized order amount equals
124
-            $itemized_sum = 0;
125
-            $shipping_previously_added = 0;
126
-            // so let's show all the line items
127
-            foreach ($total_line_item->get_items() as $line_item) {
128
-                if ($line_item instanceof EE_Line_Item) {
129
-                    // it's some kind of discount
130
-                    if ($line_item->pretaxTotal() < 0) {
131
-                        $total_discounts_to_cart_total += abs($line_item->pretaxTotal());
132
-                        $itemized_sum += $line_item->pretaxTotal();
133
-                        continue;
134
-                    }
135
-                    // dont include shipping again.
136
-                    if (strpos($line_item->code(), 'paypal_shipping_') === 0) {
137
-                        $shipping_previously_added = $line_item->pretaxTotal();
138
-                        continue;
139
-                    }
140
-                    $redirect_args[ 'item_name_' . $item_num ] = substr(
141
-                        $gateway_formatter->formatLineItemName($line_item, $payment),
142
-                        0,
143
-                        127
144
-                    );
145
-                    $redirect_args[ 'amount_' . $item_num ] = $line_item->unit_price();
146
-                    $redirect_args[ 'quantity_' . $item_num ] = $line_item->quantity();
147
-                    // if we're not letting PayPal calculate shipping, tell them its 0
148
-                    if (! $this->_paypal_shipping) {
149
-                        $redirect_args[ 'shipping_' . $item_num ] = '0';
150
-                        $redirect_args[ 'shipping2_' . $item_num ] = '0';
151
-                    }
152
-                    $item_num++;
153
-                    $itemized_sum += $line_item->pretaxTotal();
154
-                }
155
-            }
156
-            $taxes_li = $this->_line_item->get_taxes_subtotal($total_line_item);
157
-            // ideally itemized sum equals the transaction total. but if not (which is weird)
158
-            // and the itemized sum is LESS than the transaction total
159
-            // add another line item
160
-            // if the itemized sum is MORE than the transaction total,
161
-            // add the difference it to the discounts
162
-            $itemized_sum_diff_from_txn_total = round(
163
-                $transaction->total() - $itemized_sum - $taxes_li->total() - $shipping_previously_added,
164
-                2
165
-            );
166
-            if ($itemized_sum_diff_from_txn_total < 0) {
167
-                // itemized sum is too big
168
-                $total_discounts_to_cart_total += abs($itemized_sum_diff_from_txn_total);
169
-            } elseif ($itemized_sum_diff_from_txn_total > 0) {
170
-                $redirect_args[ 'item_name_' . $item_num ] = substr(
171
-                    __('Other charges', 'event_espresso'),
172
-                    0,
173
-                    127
174
-                );
175
-                $redirect_args[ 'amount_' . $item_num ] = $gateway_formatter->formatCurrency(
176
-                    $itemized_sum_diff_from_txn_total
177
-                );
178
-                $redirect_args[ 'quantity_' . $item_num ] = 1;
179
-                $item_num++;
180
-            }
181
-            if ($total_discounts_to_cart_total > 0) {
182
-                $redirect_args['discount_amount_cart'] = $gateway_formatter->formatCurrency(
183
-                    $total_discounts_to_cart_total
184
-                );
185
-            }
186
-            // add our taxes to the order if we're NOT using PayPal's
187
-            if (! $this->_paypal_taxes) {
188
-                $redirect_args['tax_cart'] = $total_line_item->get_total_tax();
189
-            }
190
-        } else {
191
-            $payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, false);
192
-            // partial payment that's not for the remaining amount, so we can't send an itemized list
193
-            $redirect_args[ 'item_name_' . $item_num ] = substr(
194
-                $gateway_formatter->formatPartialPaymentLineItemName($payment),
195
-                0,
196
-                127
197
-            );
198
-            $redirect_args[ 'amount_' . $item_num ] = $payment->amount();
199
-            $redirect_args[ 'shipping_' . $item_num ] = '0';
200
-            $redirect_args[ 'shipping2_' . $item_num ] = '0';
201
-            $redirect_args['tax_cart'] = '0';
202
-            $item_num++;
203
-        }
204
-
205
-        if ($this->_debug_mode) {
206
-            $redirect_args[ 'item_name_' . $item_num ] = 'DEBUG INFO (this item only added in sandbox mode';
207
-            $redirect_args[ 'amount_' . $item_num ] = 0;
208
-            $redirect_args[ 'on0_' . $item_num ] = 'NOTIFY URL';
209
-            $redirect_args[ 'os0_' . $item_num ] = $notify_url;
210
-            $redirect_args[ 'on1_' . $item_num ] = 'RETURN URL';
211
-            $redirect_args[ 'os1_' . $item_num ] = $return_url;
22
+	/**
23
+	 * Name for the wp option used to save the itemized payment
24
+	 */
25
+	const itemized_payment_option_name = '_itemized_payment';
26
+
27
+	protected $_paypal_id;
28
+
29
+	protected $_image_url;
30
+
31
+	protected $_shipping_details;
32
+
33
+	protected $_paypal_shipping;
34
+
35
+	protected $_paypal_taxes;
36
+
37
+	protected $_gateway_url;
38
+
39
+	protected $_currencies_supported = array(
40
+		'USD',
41
+		'GBP',
42
+		'CAD',
43
+		'AUD',
44
+		'BRL',
45
+		'CHF',
46
+		'CZK',
47
+		'DKK',
48
+		'EUR',
49
+		'HKD',
50
+		'HUF',
51
+		'ILS',
52
+		'JPY',
53
+		'MXN',
54
+		'MYR',
55
+		'NOK',
56
+		'NZD',
57
+		'PHP',
58
+		'PLN',
59
+		'SEK',
60
+		'SGD',
61
+		'THB',
62
+		'TRY',
63
+		'TWD',
64
+		'RUB'
65
+	);
66
+
67
+
68
+	/**
69
+	 * EEG_Paypal_Standard constructor.
70
+	 *
71
+	 * @return EEG_Paypal_Standard
72
+	 */
73
+	public function __construct()
74
+	{
75
+		$this->set_uses_separate_IPN_request(true);
76
+		parent::__construct();
77
+	}
78
+
79
+
80
+	/**
81
+	 * Also sets the gateway url class variable based on whether debug mode is enabled or not.
82
+	 *
83
+	 * @param array $settings_array
84
+	 */
85
+	public function set_settings($settings_array)
86
+	{
87
+		parent::set_settings($settings_array);
88
+		$this->_gateway_url = $this->_debug_mode
89
+			? 'https://www.sandbox.paypal.com/cgi-bin/webscr'
90
+			: 'https://www.paypal.com/cgi-bin/webscr';
91
+	}
92
+
93
+
94
+	/**
95
+	 * @param EEI_Payment $payment      the payment to process
96
+	 * @param array       $billing_info but should be empty for this gateway
97
+	 * @param string      $return_url   URL to send the user to after payment on the payment provider's website
98
+	 * @param string      $notify_url   URL to send the instant payment notification
99
+	 * @param string      $cancel_url   URL to send the user to after a cancelled payment attempt
100
+	 *                                  on the payment provider's website
101
+	 * @return EEI_Payment
102
+	 * @throws \EE_Error
103
+	 */
104
+	public function set_redirection_info(
105
+		$payment,
106
+		$billing_info = array(),
107
+		$return_url = null,
108
+		$notify_url = null,
109
+		$cancel_url = null
110
+	) {
111
+		$redirect_args = array();
112
+		$transaction = $payment->transaction();
113
+		$gateway_formatter = $this->_get_gateway_formatter();
114
+		$item_num = 1;
115
+		/** @type EE_Line_Item $total_line_item */
116
+		$total_line_item = $transaction->total_line_item();
117
+
118
+		$total_discounts_to_cart_total = $transaction->paid();
119
+		// only itemize the order if we're paying for the rest of the order's amount
120
+		if (EEH_Money::compare_floats($payment->amount(), $transaction->total(), '==')) {
121
+			$payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true);
122
+			// this payment is for the remaining transaction amount,
123
+			// keep track of exactly how much the itemized order amount equals
124
+			$itemized_sum = 0;
125
+			$shipping_previously_added = 0;
126
+			// so let's show all the line items
127
+			foreach ($total_line_item->get_items() as $line_item) {
128
+				if ($line_item instanceof EE_Line_Item) {
129
+					// it's some kind of discount
130
+					if ($line_item->pretaxTotal() < 0) {
131
+						$total_discounts_to_cart_total += abs($line_item->pretaxTotal());
132
+						$itemized_sum += $line_item->pretaxTotal();
133
+						continue;
134
+					}
135
+					// dont include shipping again.
136
+					if (strpos($line_item->code(), 'paypal_shipping_') === 0) {
137
+						$shipping_previously_added = $line_item->pretaxTotal();
138
+						continue;
139
+					}
140
+					$redirect_args[ 'item_name_' . $item_num ] = substr(
141
+						$gateway_formatter->formatLineItemName($line_item, $payment),
142
+						0,
143
+						127
144
+					);
145
+					$redirect_args[ 'amount_' . $item_num ] = $line_item->unit_price();
146
+					$redirect_args[ 'quantity_' . $item_num ] = $line_item->quantity();
147
+					// if we're not letting PayPal calculate shipping, tell them its 0
148
+					if (! $this->_paypal_shipping) {
149
+						$redirect_args[ 'shipping_' . $item_num ] = '0';
150
+						$redirect_args[ 'shipping2_' . $item_num ] = '0';
151
+					}
152
+					$item_num++;
153
+					$itemized_sum += $line_item->pretaxTotal();
154
+				}
155
+			}
156
+			$taxes_li = $this->_line_item->get_taxes_subtotal($total_line_item);
157
+			// ideally itemized sum equals the transaction total. but if not (which is weird)
158
+			// and the itemized sum is LESS than the transaction total
159
+			// add another line item
160
+			// if the itemized sum is MORE than the transaction total,
161
+			// add the difference it to the discounts
162
+			$itemized_sum_diff_from_txn_total = round(
163
+				$transaction->total() - $itemized_sum - $taxes_li->total() - $shipping_previously_added,
164
+				2
165
+			);
166
+			if ($itemized_sum_diff_from_txn_total < 0) {
167
+				// itemized sum is too big
168
+				$total_discounts_to_cart_total += abs($itemized_sum_diff_from_txn_total);
169
+			} elseif ($itemized_sum_diff_from_txn_total > 0) {
170
+				$redirect_args[ 'item_name_' . $item_num ] = substr(
171
+					__('Other charges', 'event_espresso'),
172
+					0,
173
+					127
174
+				);
175
+				$redirect_args[ 'amount_' . $item_num ] = $gateway_formatter->formatCurrency(
176
+					$itemized_sum_diff_from_txn_total
177
+				);
178
+				$redirect_args[ 'quantity_' . $item_num ] = 1;
179
+				$item_num++;
180
+			}
181
+			if ($total_discounts_to_cart_total > 0) {
182
+				$redirect_args['discount_amount_cart'] = $gateway_formatter->formatCurrency(
183
+					$total_discounts_to_cart_total
184
+				);
185
+			}
186
+			// add our taxes to the order if we're NOT using PayPal's
187
+			if (! $this->_paypal_taxes) {
188
+				$redirect_args['tax_cart'] = $total_line_item->get_total_tax();
189
+			}
190
+		} else {
191
+			$payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, false);
192
+			// partial payment that's not for the remaining amount, so we can't send an itemized list
193
+			$redirect_args[ 'item_name_' . $item_num ] = substr(
194
+				$gateway_formatter->formatPartialPaymentLineItemName($payment),
195
+				0,
196
+				127
197
+			);
198
+			$redirect_args[ 'amount_' . $item_num ] = $payment->amount();
199
+			$redirect_args[ 'shipping_' . $item_num ] = '0';
200
+			$redirect_args[ 'shipping2_' . $item_num ] = '0';
201
+			$redirect_args['tax_cart'] = '0';
202
+			$item_num++;
203
+		}
204
+
205
+		if ($this->_debug_mode) {
206
+			$redirect_args[ 'item_name_' . $item_num ] = 'DEBUG INFO (this item only added in sandbox mode';
207
+			$redirect_args[ 'amount_' . $item_num ] = 0;
208
+			$redirect_args[ 'on0_' . $item_num ] = 'NOTIFY URL';
209
+			$redirect_args[ 'os0_' . $item_num ] = $notify_url;
210
+			$redirect_args[ 'on1_' . $item_num ] = 'RETURN URL';
211
+			$redirect_args[ 'os1_' . $item_num ] = $return_url;
212 212
 //          $redirect_args['option_index_' . $item_num] = 1; // <-- dunno if this is needed ?
213
-            $redirect_args[ 'shipping_' . $item_num ] = '0';
214
-            $redirect_args[ 'shipping2_' . $item_num ] = '0';
215
-        }
216
-
217
-        $redirect_args['business'] = $this->_paypal_id;
218
-        $redirect_args['return'] = $return_url;
219
-        $redirect_args['cancel_return'] = $cancel_url;
220
-        $redirect_args['notify_url'] = $notify_url;
221
-        $redirect_args['cmd'] = '_cart';
222
-        $redirect_args['upload'] = 1;
223
-        $redirect_args['currency_code'] = $payment->currency_code();
224
-        $redirect_args['rm'] = 2;// makes the user return with method=POST
225
-        if ($this->_image_url) {
226
-            $redirect_args['image_url'] = $this->_image_url;
227
-        }
228
-        $redirect_args['no_shipping'] = $this->_shipping_details;
229
-        $redirect_args['bn'] = 'EventEspresso_SP';// EE will blow up if you change this
230
-
231
-        $redirect_args = apply_filters("FHEE__EEG_Paypal_Standard__set_redirection_info__arguments", $redirect_args, $this);
232
-
233
-        $payment->set_redirect_url($this->_gateway_url);
234
-        $payment->set_redirect_args($redirect_args);
235
-        // log the results
236
-        $this->log(
237
-            array(
238
-                'message'     => sprintf(
239
-                    __('PayPal payment request initiated.', 'event_espresso')
240
-                ),
241
-                'transaction' => $transaction->model_field_array(),
242
-            ),
243
-            $payment
244
-        );
245
-        return $payment;
246
-    }
247
-
248
-
249
-    /**
250
-     * Often used for IPNs. But applies the info in $update_info to the payment.
251
-     * What is $update_info? Often the contents of $_REQUEST, but not necessarily. Whatever
252
-     * the payment method passes in.
253
-     *
254
-     * @param array $update_info like $_POST
255
-     * @param EEI_Transaction $transaction
256
-     * @return \EEI_Payment updated
257
-     * @throws \EE_Error, IpnException
258
-     */
259
-    public function handle_payment_update($update_info, $transaction)
260
-    {
261
-        // verify there's payment data that's been sent
262
-        if (empty($update_info['payment_status']) || empty($update_info['txn_id'])) {
263
-            // log the results
264
-            $this->log(
265
-                array(
266
-                    'message'     => sprintf(
267
-                        // @codingStandardsIgnoreStart
268
-                        __('PayPal IPN response is missing critical payment data. This may indicate a PDT request and require your PayPal account settings to be corrected.', 'event_espresso')
269
-                        // @codingStandardsIgnoreEnd
270
-                    ),
271
-                    'update_info' => $update_info,
272
-                ),
273
-                $transaction
274
-            );
275
-            // waaaait... is this a PDT request? (see https://developer.paypal.com/docs/classic/products/payment-data-transfer/)
276
-            // indicated by the "tx" argument? If so, we don't need it. We'll just use the IPN data when it comes
277
-            if (isset($update_info['tx'])) {
278
-                return $transaction->last_payment();
279
-            } else {
280
-                return null;
281
-            }
282
-        }
283
-        $payment = $this->_pay_model->get_payment_by_txn_id_chq_nmbr($update_info['txn_id']);
284
-        if (! $payment instanceof EEI_Payment) {
285
-            $payment = $transaction->last_payment();
286
-        }
287
-        // ok, then validate the IPN. Even if we've already processed this payment,
288
-        // let PayPal know we don't want to hear from them anymore!
289
-        if (! $this->validate_ipn($update_info, $payment)) {
290
-            return $payment;
291
-        }
292
-        // kill request here if this is a refund, we don't support them yet (we'd need to adjust the transaction,
293
-        // registrations, ticket counts, etc)
294
-        if (
295
-            (
296
-                $update_info['payment_status'] === 'Refunded'
297
-                || $update_info['payment_status'] === 'Partially_Refunded'
298
-            )
299
-            && apply_filters('FHEE__EEG_Paypal_Standard__handle_payment_update__kill_refund_request', true)
300
-        ) {
301
-            throw new EventEspresso\core\exceptions\IpnException(
302
-                sprintf(
303
-                    esc_html__('Event Espresso does not yet support %1$s IPNs from PayPal', 'event_espresso'),
304
-                    $update_info['payment_status']
305
-                ),
306
-                EventEspresso\core\exceptions\IpnException::UNSUPPORTED,
307
-                null,
308
-                $payment,
309
-                $update_info
310
-            );
311
-        }
312
-        // ok, well let's process this payment then!
313
-        switch ($update_info['payment_status']) {
314
-            case 'Completed':
315
-                $status = $this->_pay_model->approved_status();
316
-                $gateway_response = esc_html__('The payment is approved.', 'event_espresso');
317
-                break;
318
-
319
-            case 'Pending':
320
-                $status = $this->_pay_model->pending_status();
321
-                $gateway_response = esc_html__(
322
-                    'The payment is in progress. Another message will be sent when payment is approved.',
323
-                    'event_espresso'
324
-                );
325
-                break;
326
-
327
-            case 'Denied':
328
-                $status = $this->_pay_model->declined_status();
329
-                $gateway_response = esc_html__('The payment has been declined.', 'event_espresso');
330
-                break;
331
-
332
-            case 'Expired':
333
-            case 'Failed':
334
-                $status = $this->_pay_model->failed_status();
335
-                $gateway_response = esc_html__('The payment failed for technical reasons or expired.', 'event_espresso');
336
-                break;
337
-
338
-            case 'Refunded':
339
-            case 'Partially_Refunded':
340
-                // even though it's a refund, we consider the payment as approved, it just has a negative value
341
-                $status = $this->_pay_model->approved_status();
342
-                $gateway_response = esc_html__(
343
-                    'The payment has been refunded. Please update registrations accordingly.',
344
-                    'event_espresso'
345
-                );
346
-                break;
347
-
348
-            case 'Voided':
349
-            case 'Reversed':
350
-            case 'Canceled_Reversal':
351
-            default:
352
-                $status = $this->_pay_model->cancelled_status();
353
-                $gateway_response = esc_html__(
354
-                    'The payment was cancelled, reversed, or voided. Please update registrations accordingly.',
355
-                    'event_espresso'
356
-                );
357
-                break;
358
-        }
359
-
360
-        // check if we've already processed this payment
361
-        if ($payment instanceof EEI_Payment) {
362
-            // payment exists. if this has the exact same status and amount, don't bother updating. just return
363
-            if ($payment->status() === $status && (float) $payment->amount() === (float) $update_info['mc_gross']) {
364
-                // DUPLICATED IPN! don't bother updating transaction
365
-                throw new IpnException(
366
-                    sprintf(
367
-                        esc_html__(
368
-                            'It appears we have received a duplicate IPN from PayPal for payment %d',
369
-                            'event_espresso'
370
-                        ),
371
-                        $payment->ID()
372
-                    ),
373
-                    IpnException::DUPLICATE,
374
-                    null,
375
-                    $payment,
376
-                    $update_info
377
-                );
378
-            } else {
379
-                // new payment yippee !!!
380
-                $payment->set_status($status);
381
-                $payment->set_amount((float) $update_info['mc_gross']);
382
-                $payment->set_gateway_response($gateway_response);
383
-                $payment->set_details($update_info);
384
-                $payment->set_txn_id_chq_nmbr($update_info['txn_id']);
385
-                $this->log(
386
-                    array(
387
-                        'message'  => esc_html__(
388
-                            'Updated payment either from IPN or as part of POST from PayPal',
389
-                            'event_espresso'
390
-                        ),
391
-                        'url'      => $this->_process_response_url(),
392
-                        'payment'  => $payment->model_field_array(),
393
-                        'IPN_data' => $update_info
394
-                    ),
395
-                    $payment
396
-                );
397
-            }
398
-        }
399
-        do_action('FHEE__EEG_Paypal_Standard__handle_payment_update__payment_processed', $payment, $this);
400
-        return $payment;
401
-    }
402
-
403
-
404
-    /**
405
-     * Validate the IPN notification.
406
-     *
407
-     * @param array                  $update_info like $_REQUEST
408
-     * @param EE_Payment|EEI_Payment $payment
409
-     * @return boolean
410
-     * @throws \EE_Error
411
-     */
412
-    public function validate_ipn($update_info, $payment)
413
-    {
414
-        // allow us to skip validating IPNs with PayPal (useful for testing)
415
-        if (apply_filters('FHEE__EEG_Paypal_Standard__validate_ipn__skip', false)) {
416
-            return true;
417
-        }
418
-        // ...otherwise, we actually don't care what the $update_info is, we need to look
419
-        // at the request directly because we can't use $update_info because it has issues with quotes
420
-        // Reading POSTed data directly from $_POST causes serialization issues with array data in the POST.
421
-        // Instead, read raw POST data from the input stream.
422
-        // @see https://gist.github.com/xcommerce-gists/3440401
423
-        $raw_post_data = file_get_contents('php://input');
424
-        $raw_post_array = explode('&', $raw_post_data);
425
-        $update_info = array();
426
-        foreach ($raw_post_array as $keyval) {
427
-            $keyval = explode('=', $keyval);
428
-            if (count($keyval) === 2) {
429
-                $update_info[ $keyval[0] ] = urldecode($keyval[1]);
430
-            }
431
-        }
432
-        // read the IPN message sent from PayPal and prepend 'cmd=_notify-validate'
433
-        $req = 'cmd=_notify-validate';
434
-        $uses_get_magic_quotes = function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() === 1
435
-            ? true
436
-            : false;
437
-        foreach ($update_info as $key => $value) {
438
-            $value = $uses_get_magic_quotes ? urlencode(stripslashes($value)) : urlencode($value);
439
-            $req .= "&$key=$value";
440
-        }
441
-        // HTTP POST the complete, unaltered IPN back to PayPal
442
-        $response = wp_remote_post(
443
-            $this->_gateway_url,
444
-            array(
445
-                'body'              => $req,
446
-                'sslverify'         => false,
447
-                'timeout'           => 60,
448
-                // make sure to set a site specific unique "user-agent" string since the WordPres default gets declined by PayPal
449
-                // plz see: https://github.com/websharks/s2member/issues/610
450
-                'user-agent'        => 'Event Espresso v' . EVENT_ESPRESSO_VERSION . '; ' . home_url(),
451
-                'httpversion'       => '1.1'
452
-            )
453
-        );
454
-        // then check the response
455
-        if (
456
-            array_key_exists('body', $response)
457
-            && ! is_wp_error($response)
458
-            && strcmp($response['body'], "VERIFIED") === 0
459
-        ) {
460
-            return true;
461
-        }
462
-        // huh, something's wack... the IPN didn't validate. We must have replied to the IPN incorrectly,
463
-        // or their API must have changed: http://www.paypalobjects.com/en_US/ebook/PP_OrderManagement_IntegrationGuide/ipn.html
464
-        if ($response instanceof WP_Error) {
465
-            $error_msg = sprintf(
466
-                esc_html__('WP Error. Code: "%1$s", Message: "%2$s", Data: "%3$s"', 'event_espresso'),
467
-                $response->get_error_code(),
468
-                $response->get_error_message(),
469
-                print_r($response->get_error_data(), true)
470
-            );
471
-        } elseif (is_array($response) && isset($response['body'])) {
472
-            $error_msg = $response['body'];
473
-        } else {
474
-            $error_msg = print_r($response, true);
475
-        }
476
-        $payment->set_gateway_response(
477
-            sprintf(
478
-                esc_html__("IPN Validation failed! Paypal responded with '%s'", "event_espresso"),
479
-                $error_msg
480
-            )
481
-        );
482
-        $payment->set_details(array('REQUEST' => $update_info, 'VALIDATION_RESPONSE' => $response));
483
-        $payment->set_status(EEM_Payment::status_id_failed);
484
-        // log the results
485
-        $this->log(
486
-            array(
487
-                'url'     => $this->_process_response_url(),
488
-                'message' => $payment->gateway_response(),
489
-                'details' => $payment->details(),
490
-            ),
491
-            $payment
492
-        );
493
-        return false;
494
-    }
495
-
496
-
497
-    /**
498
-     * _process_response_url
499
-     * @return string
500
-     */
501
-    protected function _process_response_url()
502
-    {
503
-        if (isset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])) {
504
-            $url = is_ssl() ? 'https://' : 'http://';
505
-            $url .= EEH_URL::filter_input_server_url('HTTP_HOST');
506
-            $url .= EEH_URL::filter_input_server_url();
507
-        } else {
508
-            $url = 'unknown';
509
-        }
510
-        return $url;
511
-    }
512
-
513
-
514
-    /**
515
-     * Updates the transaction and line items based on the payment IPN data from PayPal,
516
-     * like the taxes or shipping
517
-     *
518
-     * @param EEI_Payment $payment
519
-     * @throws \EE_Error
520
-     */
521
-    public function update_txn_based_on_payment($payment)
522
-    {
523
-        $update_info = $payment->details();
524
-        /** @var EE_Transaction $transaction */
525
-        $transaction = $payment->transaction();
526
-        $payment_was_itemized = $payment->get_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true, false);
527
-        if (! $transaction) {
528
-            $this->log(
529
-                esc_html__(
530
-                    // @codingStandardsIgnoreStart
531
-                    'Payment with ID %d has no related transaction, and so update_txn_based_on_payment couldn\'t be executed properly',
532
-                    // @codingStandardsIgnoreEnd
533
-                    'event_espresso'
534
-                ),
535
-                $payment
536
-            );
537
-            return;
538
-        }
539
-        if (
540
-            ! is_array($update_info)
541
-            || ! isset($update_info['mc_shipping'])
542
-            || ! isset($update_info['tax'])
543
-        ) {
544
-            $this->log(
545
-                array(
546
-                    'message' => esc_html__(
547
-                        // @codingStandardsIgnoreStart
548
-                        'Could not update transaction based on payment because the payment details have not yet been put on the payment. This normally happens during the IPN or returning from PayPal',
549
-                        // @codingStandardsIgnoreEnd
550
-                        'event_espresso'
551
-                    ),
552
-                    'url'     => $this->_process_response_url(),
553
-                    'payment' => $payment->model_field_array()
554
-                ),
555
-                $payment
556
-            );
557
-            return;
558
-        }
559
-        if ($payment->status() !== $this->_pay_model->approved_status()) {
560
-            $this->log(
561
-                array(
562
-                    'message' => esc_html__(
563
-                        'We shouldn\'t update transactions taxes or shipping data from non-approved payments',
564
-                        'event_espresso'
565
-                    ),
566
-                    'url'     => $this->_process_response_url(),
567
-                    'payment' => $payment->model_field_array()
568
-                ),
569
-                $payment
570
-            );
571
-            return;
572
-        }
573
-        $grand_total_needs_resaving = false;
574
-        /** @var EE_Line_Item $transaction_total_line_item */
575
-        $transaction_total_line_item = $transaction->total_line_item();
576
-
577
-        // might paypal have changed the taxes?
578
-        if ($this->_paypal_taxes && $payment_was_itemized) {
579
-            // note that we're doing this BEFORE adding shipping;
580
-            // we actually want PayPal's shipping to remain non-taxable
581
-            $this->_line_item->set_line_items_taxable($transaction_total_line_item, true, 'paypal_shipping');
582
-            $this->_line_item->set_total_tax_to(
583
-                $transaction_total_line_item,
584
-                (float) $update_info['tax'],
585
-                esc_html__('Taxes', 'event_espresso'),
586
-                esc_html__('Calculated by Paypal', 'event_espresso'),
587
-                'paypal_tax'
588
-            );
589
-            $grand_total_needs_resaving = true;
590
-        }
591
-
592
-        $shipping_amount = (float) $update_info['mc_shipping'];
593
-        // might paypal have added shipping?
594
-        if ($this->_paypal_shipping && $shipping_amount && $payment_was_itemized) {
595
-            $this->_line_item->add_unrelated_item(
596
-                $transaction_total_line_item,
597
-                sprintf(esc_html__('Shipping for transaction %1$s', 'event_espresso'), $transaction->ID()),
598
-                $shipping_amount,
599
-                esc_html__('Shipping charges calculated by Paypal', 'event_espresso'),
600
-                1,
601
-                false,
602
-                'paypal_shipping_' . $transaction->ID()
603
-            );
604
-            $grand_total_needs_resaving = true;
605
-        }
606
-
607
-        if ($grand_total_needs_resaving) {
608
-            $transaction_total_line_item->save_this_and_descendants_to_txn($transaction->ID());
609
-            /** @var EE_Registration_Processor $registration_processor */
610
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
611
-            $registration_processor->update_registration_final_prices($transaction);
612
-        }
613
-        $this->log(
614
-            array(
615
-                'message'                     => esc_html__('Updated transaction related to payment', 'event_espresso'),
616
-                'url'                         => $this->_process_response_url(),
617
-                'transaction (updated)'       => $transaction->model_field_array(),
618
-                'payment (updated)'           => $payment->model_field_array(),
619
-                'use_paypal_shipping'         => $this->_paypal_shipping,
620
-                'use_paypal_tax'              => $this->_paypal_taxes,
621
-                'grand_total_needed_resaving' => $grand_total_needs_resaving,
622
-            ),
623
-            $payment
624
-        );
625
-    }
213
+			$redirect_args[ 'shipping_' . $item_num ] = '0';
214
+			$redirect_args[ 'shipping2_' . $item_num ] = '0';
215
+		}
216
+
217
+		$redirect_args['business'] = $this->_paypal_id;
218
+		$redirect_args['return'] = $return_url;
219
+		$redirect_args['cancel_return'] = $cancel_url;
220
+		$redirect_args['notify_url'] = $notify_url;
221
+		$redirect_args['cmd'] = '_cart';
222
+		$redirect_args['upload'] = 1;
223
+		$redirect_args['currency_code'] = $payment->currency_code();
224
+		$redirect_args['rm'] = 2;// makes the user return with method=POST
225
+		if ($this->_image_url) {
226
+			$redirect_args['image_url'] = $this->_image_url;
227
+		}
228
+		$redirect_args['no_shipping'] = $this->_shipping_details;
229
+		$redirect_args['bn'] = 'EventEspresso_SP';// EE will blow up if you change this
230
+
231
+		$redirect_args = apply_filters("FHEE__EEG_Paypal_Standard__set_redirection_info__arguments", $redirect_args, $this);
232
+
233
+		$payment->set_redirect_url($this->_gateway_url);
234
+		$payment->set_redirect_args($redirect_args);
235
+		// log the results
236
+		$this->log(
237
+			array(
238
+				'message'     => sprintf(
239
+					__('PayPal payment request initiated.', 'event_espresso')
240
+				),
241
+				'transaction' => $transaction->model_field_array(),
242
+			),
243
+			$payment
244
+		);
245
+		return $payment;
246
+	}
247
+
248
+
249
+	/**
250
+	 * Often used for IPNs. But applies the info in $update_info to the payment.
251
+	 * What is $update_info? Often the contents of $_REQUEST, but not necessarily. Whatever
252
+	 * the payment method passes in.
253
+	 *
254
+	 * @param array $update_info like $_POST
255
+	 * @param EEI_Transaction $transaction
256
+	 * @return \EEI_Payment updated
257
+	 * @throws \EE_Error, IpnException
258
+	 */
259
+	public function handle_payment_update($update_info, $transaction)
260
+	{
261
+		// verify there's payment data that's been sent
262
+		if (empty($update_info['payment_status']) || empty($update_info['txn_id'])) {
263
+			// log the results
264
+			$this->log(
265
+				array(
266
+					'message'     => sprintf(
267
+						// @codingStandardsIgnoreStart
268
+						__('PayPal IPN response is missing critical payment data. This may indicate a PDT request and require your PayPal account settings to be corrected.', 'event_espresso')
269
+						// @codingStandardsIgnoreEnd
270
+					),
271
+					'update_info' => $update_info,
272
+				),
273
+				$transaction
274
+			);
275
+			// waaaait... is this a PDT request? (see https://developer.paypal.com/docs/classic/products/payment-data-transfer/)
276
+			// indicated by the "tx" argument? If so, we don't need it. We'll just use the IPN data when it comes
277
+			if (isset($update_info['tx'])) {
278
+				return $transaction->last_payment();
279
+			} else {
280
+				return null;
281
+			}
282
+		}
283
+		$payment = $this->_pay_model->get_payment_by_txn_id_chq_nmbr($update_info['txn_id']);
284
+		if (! $payment instanceof EEI_Payment) {
285
+			$payment = $transaction->last_payment();
286
+		}
287
+		// ok, then validate the IPN. Even if we've already processed this payment,
288
+		// let PayPal know we don't want to hear from them anymore!
289
+		if (! $this->validate_ipn($update_info, $payment)) {
290
+			return $payment;
291
+		}
292
+		// kill request here if this is a refund, we don't support them yet (we'd need to adjust the transaction,
293
+		// registrations, ticket counts, etc)
294
+		if (
295
+			(
296
+				$update_info['payment_status'] === 'Refunded'
297
+				|| $update_info['payment_status'] === 'Partially_Refunded'
298
+			)
299
+			&& apply_filters('FHEE__EEG_Paypal_Standard__handle_payment_update__kill_refund_request', true)
300
+		) {
301
+			throw new EventEspresso\core\exceptions\IpnException(
302
+				sprintf(
303
+					esc_html__('Event Espresso does not yet support %1$s IPNs from PayPal', 'event_espresso'),
304
+					$update_info['payment_status']
305
+				),
306
+				EventEspresso\core\exceptions\IpnException::UNSUPPORTED,
307
+				null,
308
+				$payment,
309
+				$update_info
310
+			);
311
+		}
312
+		// ok, well let's process this payment then!
313
+		switch ($update_info['payment_status']) {
314
+			case 'Completed':
315
+				$status = $this->_pay_model->approved_status();
316
+				$gateway_response = esc_html__('The payment is approved.', 'event_espresso');
317
+				break;
318
+
319
+			case 'Pending':
320
+				$status = $this->_pay_model->pending_status();
321
+				$gateway_response = esc_html__(
322
+					'The payment is in progress. Another message will be sent when payment is approved.',
323
+					'event_espresso'
324
+				);
325
+				break;
326
+
327
+			case 'Denied':
328
+				$status = $this->_pay_model->declined_status();
329
+				$gateway_response = esc_html__('The payment has been declined.', 'event_espresso');
330
+				break;
331
+
332
+			case 'Expired':
333
+			case 'Failed':
334
+				$status = $this->_pay_model->failed_status();
335
+				$gateway_response = esc_html__('The payment failed for technical reasons or expired.', 'event_espresso');
336
+				break;
337
+
338
+			case 'Refunded':
339
+			case 'Partially_Refunded':
340
+				// even though it's a refund, we consider the payment as approved, it just has a negative value
341
+				$status = $this->_pay_model->approved_status();
342
+				$gateway_response = esc_html__(
343
+					'The payment has been refunded. Please update registrations accordingly.',
344
+					'event_espresso'
345
+				);
346
+				break;
347
+
348
+			case 'Voided':
349
+			case 'Reversed':
350
+			case 'Canceled_Reversal':
351
+			default:
352
+				$status = $this->_pay_model->cancelled_status();
353
+				$gateway_response = esc_html__(
354
+					'The payment was cancelled, reversed, or voided. Please update registrations accordingly.',
355
+					'event_espresso'
356
+				);
357
+				break;
358
+		}
359
+
360
+		// check if we've already processed this payment
361
+		if ($payment instanceof EEI_Payment) {
362
+			// payment exists. if this has the exact same status and amount, don't bother updating. just return
363
+			if ($payment->status() === $status && (float) $payment->amount() === (float) $update_info['mc_gross']) {
364
+				// DUPLICATED IPN! don't bother updating transaction
365
+				throw new IpnException(
366
+					sprintf(
367
+						esc_html__(
368
+							'It appears we have received a duplicate IPN from PayPal for payment %d',
369
+							'event_espresso'
370
+						),
371
+						$payment->ID()
372
+					),
373
+					IpnException::DUPLICATE,
374
+					null,
375
+					$payment,
376
+					$update_info
377
+				);
378
+			} else {
379
+				// new payment yippee !!!
380
+				$payment->set_status($status);
381
+				$payment->set_amount((float) $update_info['mc_gross']);
382
+				$payment->set_gateway_response($gateway_response);
383
+				$payment->set_details($update_info);
384
+				$payment->set_txn_id_chq_nmbr($update_info['txn_id']);
385
+				$this->log(
386
+					array(
387
+						'message'  => esc_html__(
388
+							'Updated payment either from IPN or as part of POST from PayPal',
389
+							'event_espresso'
390
+						),
391
+						'url'      => $this->_process_response_url(),
392
+						'payment'  => $payment->model_field_array(),
393
+						'IPN_data' => $update_info
394
+					),
395
+					$payment
396
+				);
397
+			}
398
+		}
399
+		do_action('FHEE__EEG_Paypal_Standard__handle_payment_update__payment_processed', $payment, $this);
400
+		return $payment;
401
+	}
402
+
403
+
404
+	/**
405
+	 * Validate the IPN notification.
406
+	 *
407
+	 * @param array                  $update_info like $_REQUEST
408
+	 * @param EE_Payment|EEI_Payment $payment
409
+	 * @return boolean
410
+	 * @throws \EE_Error
411
+	 */
412
+	public function validate_ipn($update_info, $payment)
413
+	{
414
+		// allow us to skip validating IPNs with PayPal (useful for testing)
415
+		if (apply_filters('FHEE__EEG_Paypal_Standard__validate_ipn__skip', false)) {
416
+			return true;
417
+		}
418
+		// ...otherwise, we actually don't care what the $update_info is, we need to look
419
+		// at the request directly because we can't use $update_info because it has issues with quotes
420
+		// Reading POSTed data directly from $_POST causes serialization issues with array data in the POST.
421
+		// Instead, read raw POST data from the input stream.
422
+		// @see https://gist.github.com/xcommerce-gists/3440401
423
+		$raw_post_data = file_get_contents('php://input');
424
+		$raw_post_array = explode('&', $raw_post_data);
425
+		$update_info = array();
426
+		foreach ($raw_post_array as $keyval) {
427
+			$keyval = explode('=', $keyval);
428
+			if (count($keyval) === 2) {
429
+				$update_info[ $keyval[0] ] = urldecode($keyval[1]);
430
+			}
431
+		}
432
+		// read the IPN message sent from PayPal and prepend 'cmd=_notify-validate'
433
+		$req = 'cmd=_notify-validate';
434
+		$uses_get_magic_quotes = function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() === 1
435
+			? true
436
+			: false;
437
+		foreach ($update_info as $key => $value) {
438
+			$value = $uses_get_magic_quotes ? urlencode(stripslashes($value)) : urlencode($value);
439
+			$req .= "&$key=$value";
440
+		}
441
+		// HTTP POST the complete, unaltered IPN back to PayPal
442
+		$response = wp_remote_post(
443
+			$this->_gateway_url,
444
+			array(
445
+				'body'              => $req,
446
+				'sslverify'         => false,
447
+				'timeout'           => 60,
448
+				// make sure to set a site specific unique "user-agent" string since the WordPres default gets declined by PayPal
449
+				// plz see: https://github.com/websharks/s2member/issues/610
450
+				'user-agent'        => 'Event Espresso v' . EVENT_ESPRESSO_VERSION . '; ' . home_url(),
451
+				'httpversion'       => '1.1'
452
+			)
453
+		);
454
+		// then check the response
455
+		if (
456
+			array_key_exists('body', $response)
457
+			&& ! is_wp_error($response)
458
+			&& strcmp($response['body'], "VERIFIED") === 0
459
+		) {
460
+			return true;
461
+		}
462
+		// huh, something's wack... the IPN didn't validate. We must have replied to the IPN incorrectly,
463
+		// or their API must have changed: http://www.paypalobjects.com/en_US/ebook/PP_OrderManagement_IntegrationGuide/ipn.html
464
+		if ($response instanceof WP_Error) {
465
+			$error_msg = sprintf(
466
+				esc_html__('WP Error. Code: "%1$s", Message: "%2$s", Data: "%3$s"', 'event_espresso'),
467
+				$response->get_error_code(),
468
+				$response->get_error_message(),
469
+				print_r($response->get_error_data(), true)
470
+			);
471
+		} elseif (is_array($response) && isset($response['body'])) {
472
+			$error_msg = $response['body'];
473
+		} else {
474
+			$error_msg = print_r($response, true);
475
+		}
476
+		$payment->set_gateway_response(
477
+			sprintf(
478
+				esc_html__("IPN Validation failed! Paypal responded with '%s'", "event_espresso"),
479
+				$error_msg
480
+			)
481
+		);
482
+		$payment->set_details(array('REQUEST' => $update_info, 'VALIDATION_RESPONSE' => $response));
483
+		$payment->set_status(EEM_Payment::status_id_failed);
484
+		// log the results
485
+		$this->log(
486
+			array(
487
+				'url'     => $this->_process_response_url(),
488
+				'message' => $payment->gateway_response(),
489
+				'details' => $payment->details(),
490
+			),
491
+			$payment
492
+		);
493
+		return false;
494
+	}
495
+
496
+
497
+	/**
498
+	 * _process_response_url
499
+	 * @return string
500
+	 */
501
+	protected function _process_response_url()
502
+	{
503
+		if (isset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])) {
504
+			$url = is_ssl() ? 'https://' : 'http://';
505
+			$url .= EEH_URL::filter_input_server_url('HTTP_HOST');
506
+			$url .= EEH_URL::filter_input_server_url();
507
+		} else {
508
+			$url = 'unknown';
509
+		}
510
+		return $url;
511
+	}
512
+
513
+
514
+	/**
515
+	 * Updates the transaction and line items based on the payment IPN data from PayPal,
516
+	 * like the taxes or shipping
517
+	 *
518
+	 * @param EEI_Payment $payment
519
+	 * @throws \EE_Error
520
+	 */
521
+	public function update_txn_based_on_payment($payment)
522
+	{
523
+		$update_info = $payment->details();
524
+		/** @var EE_Transaction $transaction */
525
+		$transaction = $payment->transaction();
526
+		$payment_was_itemized = $payment->get_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true, false);
527
+		if (! $transaction) {
528
+			$this->log(
529
+				esc_html__(
530
+					// @codingStandardsIgnoreStart
531
+					'Payment with ID %d has no related transaction, and so update_txn_based_on_payment couldn\'t be executed properly',
532
+					// @codingStandardsIgnoreEnd
533
+					'event_espresso'
534
+				),
535
+				$payment
536
+			);
537
+			return;
538
+		}
539
+		if (
540
+			! is_array($update_info)
541
+			|| ! isset($update_info['mc_shipping'])
542
+			|| ! isset($update_info['tax'])
543
+		) {
544
+			$this->log(
545
+				array(
546
+					'message' => esc_html__(
547
+						// @codingStandardsIgnoreStart
548
+						'Could not update transaction based on payment because the payment details have not yet been put on the payment. This normally happens during the IPN or returning from PayPal',
549
+						// @codingStandardsIgnoreEnd
550
+						'event_espresso'
551
+					),
552
+					'url'     => $this->_process_response_url(),
553
+					'payment' => $payment->model_field_array()
554
+				),
555
+				$payment
556
+			);
557
+			return;
558
+		}
559
+		if ($payment->status() !== $this->_pay_model->approved_status()) {
560
+			$this->log(
561
+				array(
562
+					'message' => esc_html__(
563
+						'We shouldn\'t update transactions taxes or shipping data from non-approved payments',
564
+						'event_espresso'
565
+					),
566
+					'url'     => $this->_process_response_url(),
567
+					'payment' => $payment->model_field_array()
568
+				),
569
+				$payment
570
+			);
571
+			return;
572
+		}
573
+		$grand_total_needs_resaving = false;
574
+		/** @var EE_Line_Item $transaction_total_line_item */
575
+		$transaction_total_line_item = $transaction->total_line_item();
576
+
577
+		// might paypal have changed the taxes?
578
+		if ($this->_paypal_taxes && $payment_was_itemized) {
579
+			// note that we're doing this BEFORE adding shipping;
580
+			// we actually want PayPal's shipping to remain non-taxable
581
+			$this->_line_item->set_line_items_taxable($transaction_total_line_item, true, 'paypal_shipping');
582
+			$this->_line_item->set_total_tax_to(
583
+				$transaction_total_line_item,
584
+				(float) $update_info['tax'],
585
+				esc_html__('Taxes', 'event_espresso'),
586
+				esc_html__('Calculated by Paypal', 'event_espresso'),
587
+				'paypal_tax'
588
+			);
589
+			$grand_total_needs_resaving = true;
590
+		}
591
+
592
+		$shipping_amount = (float) $update_info['mc_shipping'];
593
+		// might paypal have added shipping?
594
+		if ($this->_paypal_shipping && $shipping_amount && $payment_was_itemized) {
595
+			$this->_line_item->add_unrelated_item(
596
+				$transaction_total_line_item,
597
+				sprintf(esc_html__('Shipping for transaction %1$s', 'event_espresso'), $transaction->ID()),
598
+				$shipping_amount,
599
+				esc_html__('Shipping charges calculated by Paypal', 'event_espresso'),
600
+				1,
601
+				false,
602
+				'paypal_shipping_' . $transaction->ID()
603
+			);
604
+			$grand_total_needs_resaving = true;
605
+		}
606
+
607
+		if ($grand_total_needs_resaving) {
608
+			$transaction_total_line_item->save_this_and_descendants_to_txn($transaction->ID());
609
+			/** @var EE_Registration_Processor $registration_processor */
610
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
611
+			$registration_processor->update_registration_final_prices($transaction);
612
+		}
613
+		$this->log(
614
+			array(
615
+				'message'                     => esc_html__('Updated transaction related to payment', 'event_espresso'),
616
+				'url'                         => $this->_process_response_url(),
617
+				'transaction (updated)'       => $transaction->model_field_array(),
618
+				'payment (updated)'           => $payment->model_field_array(),
619
+				'use_paypal_shipping'         => $this->_paypal_shipping,
620
+				'use_paypal_tax'              => $this->_paypal_taxes,
621
+				'grand_total_needed_resaving' => $grand_total_needs_resaving,
622
+			),
623
+			$payment
624
+		);
625
+	}
626 626
 }
Please login to merge, or discard this patch.
Spacing   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -137,17 +137,17 @@  discard block
 block discarded – undo
137 137
                         $shipping_previously_added = $line_item->pretaxTotal();
138 138
                         continue;
139 139
                     }
140
-                    $redirect_args[ 'item_name_' . $item_num ] = substr(
140
+                    $redirect_args['item_name_'.$item_num] = substr(
141 141
                         $gateway_formatter->formatLineItemName($line_item, $payment),
142 142
                         0,
143 143
                         127
144 144
                     );
145
-                    $redirect_args[ 'amount_' . $item_num ] = $line_item->unit_price();
146
-                    $redirect_args[ 'quantity_' . $item_num ] = $line_item->quantity();
145
+                    $redirect_args['amount_'.$item_num] = $line_item->unit_price();
146
+                    $redirect_args['quantity_'.$item_num] = $line_item->quantity();
147 147
                     // if we're not letting PayPal calculate shipping, tell them its 0
148
-                    if (! $this->_paypal_shipping) {
149
-                        $redirect_args[ 'shipping_' . $item_num ] = '0';
150
-                        $redirect_args[ 'shipping2_' . $item_num ] = '0';
148
+                    if ( ! $this->_paypal_shipping) {
149
+                        $redirect_args['shipping_'.$item_num] = '0';
150
+                        $redirect_args['shipping2_'.$item_num] = '0';
151 151
                     }
152 152
                     $item_num++;
153 153
                     $itemized_sum += $line_item->pretaxTotal();
@@ -167,15 +167,15 @@  discard block
 block discarded – undo
167 167
                 // itemized sum is too big
168 168
                 $total_discounts_to_cart_total += abs($itemized_sum_diff_from_txn_total);
169 169
             } elseif ($itemized_sum_diff_from_txn_total > 0) {
170
-                $redirect_args[ 'item_name_' . $item_num ] = substr(
170
+                $redirect_args['item_name_'.$item_num] = substr(
171 171
                     __('Other charges', 'event_espresso'),
172 172
                     0,
173 173
                     127
174 174
                 );
175
-                $redirect_args[ 'amount_' . $item_num ] = $gateway_formatter->formatCurrency(
175
+                $redirect_args['amount_'.$item_num] = $gateway_formatter->formatCurrency(
176 176
                     $itemized_sum_diff_from_txn_total
177 177
                 );
178
-                $redirect_args[ 'quantity_' . $item_num ] = 1;
178
+                $redirect_args['quantity_'.$item_num] = 1;
179 179
                 $item_num++;
180 180
             }
181 181
             if ($total_discounts_to_cart_total > 0) {
@@ -184,34 +184,34 @@  discard block
 block discarded – undo
184 184
                 );
185 185
             }
186 186
             // add our taxes to the order if we're NOT using PayPal's
187
-            if (! $this->_paypal_taxes) {
187
+            if ( ! $this->_paypal_taxes) {
188 188
                 $redirect_args['tax_cart'] = $total_line_item->get_total_tax();
189 189
             }
190 190
         } else {
191 191
             $payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, false);
192 192
             // partial payment that's not for the remaining amount, so we can't send an itemized list
193
-            $redirect_args[ 'item_name_' . $item_num ] = substr(
193
+            $redirect_args['item_name_'.$item_num] = substr(
194 194
                 $gateway_formatter->formatPartialPaymentLineItemName($payment),
195 195
                 0,
196 196
                 127
197 197
             );
198
-            $redirect_args[ 'amount_' . $item_num ] = $payment->amount();
199
-            $redirect_args[ 'shipping_' . $item_num ] = '0';
200
-            $redirect_args[ 'shipping2_' . $item_num ] = '0';
198
+            $redirect_args['amount_'.$item_num] = $payment->amount();
199
+            $redirect_args['shipping_'.$item_num] = '0';
200
+            $redirect_args['shipping2_'.$item_num] = '0';
201 201
             $redirect_args['tax_cart'] = '0';
202 202
             $item_num++;
203 203
         }
204 204
 
205 205
         if ($this->_debug_mode) {
206
-            $redirect_args[ 'item_name_' . $item_num ] = 'DEBUG INFO (this item only added in sandbox mode';
207
-            $redirect_args[ 'amount_' . $item_num ] = 0;
208
-            $redirect_args[ 'on0_' . $item_num ] = 'NOTIFY URL';
209
-            $redirect_args[ 'os0_' . $item_num ] = $notify_url;
210
-            $redirect_args[ 'on1_' . $item_num ] = 'RETURN URL';
211
-            $redirect_args[ 'os1_' . $item_num ] = $return_url;
206
+            $redirect_args['item_name_'.$item_num] = 'DEBUG INFO (this item only added in sandbox mode';
207
+            $redirect_args['amount_'.$item_num] = 0;
208
+            $redirect_args['on0_'.$item_num] = 'NOTIFY URL';
209
+            $redirect_args['os0_'.$item_num] = $notify_url;
210
+            $redirect_args['on1_'.$item_num] = 'RETURN URL';
211
+            $redirect_args['os1_'.$item_num] = $return_url;
212 212
 //          $redirect_args['option_index_' . $item_num] = 1; // <-- dunno if this is needed ?
213
-            $redirect_args[ 'shipping_' . $item_num ] = '0';
214
-            $redirect_args[ 'shipping2_' . $item_num ] = '0';
213
+            $redirect_args['shipping_'.$item_num] = '0';
214
+            $redirect_args['shipping2_'.$item_num] = '0';
215 215
         }
216 216
 
217 217
         $redirect_args['business'] = $this->_paypal_id;
@@ -221,12 +221,12 @@  discard block
 block discarded – undo
221 221
         $redirect_args['cmd'] = '_cart';
222 222
         $redirect_args['upload'] = 1;
223 223
         $redirect_args['currency_code'] = $payment->currency_code();
224
-        $redirect_args['rm'] = 2;// makes the user return with method=POST
224
+        $redirect_args['rm'] = 2; // makes the user return with method=POST
225 225
         if ($this->_image_url) {
226 226
             $redirect_args['image_url'] = $this->_image_url;
227 227
         }
228 228
         $redirect_args['no_shipping'] = $this->_shipping_details;
229
-        $redirect_args['bn'] = 'EventEspresso_SP';// EE will blow up if you change this
229
+        $redirect_args['bn'] = 'EventEspresso_SP'; // EE will blow up if you change this
230 230
 
231 231
         $redirect_args = apply_filters("FHEE__EEG_Paypal_Standard__set_redirection_info__arguments", $redirect_args, $this);
232 232
 
@@ -281,12 +281,12 @@  discard block
 block discarded – undo
281 281
             }
282 282
         }
283 283
         $payment = $this->_pay_model->get_payment_by_txn_id_chq_nmbr($update_info['txn_id']);
284
-        if (! $payment instanceof EEI_Payment) {
284
+        if ( ! $payment instanceof EEI_Payment) {
285 285
             $payment = $transaction->last_payment();
286 286
         }
287 287
         // ok, then validate the IPN. Even if we've already processed this payment,
288 288
         // let PayPal know we don't want to hear from them anymore!
289
-        if (! $this->validate_ipn($update_info, $payment)) {
289
+        if ( ! $this->validate_ipn($update_info, $payment)) {
290 290
             return $payment;
291 291
         }
292 292
         // kill request here if this is a refund, we don't support them yet (we'd need to adjust the transaction,
@@ -426,7 +426,7 @@  discard block
 block discarded – undo
426 426
         foreach ($raw_post_array as $keyval) {
427 427
             $keyval = explode('=', $keyval);
428 428
             if (count($keyval) === 2) {
429
-                $update_info[ $keyval[0] ] = urldecode($keyval[1]);
429
+                $update_info[$keyval[0]] = urldecode($keyval[1]);
430 430
             }
431 431
         }
432 432
         // read the IPN message sent from PayPal and prepend 'cmd=_notify-validate'
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
                 'timeout'           => 60,
448 448
                 // make sure to set a site specific unique "user-agent" string since the WordPres default gets declined by PayPal
449 449
                 // plz see: https://github.com/websharks/s2member/issues/610
450
-                'user-agent'        => 'Event Espresso v' . EVENT_ESPRESSO_VERSION . '; ' . home_url(),
450
+                'user-agent'        => 'Event Espresso v'.EVENT_ESPRESSO_VERSION.'; '.home_url(),
451 451
                 'httpversion'       => '1.1'
452 452
             )
453 453
         );
@@ -524,7 +524,7 @@  discard block
 block discarded – undo
524 524
         /** @var EE_Transaction $transaction */
525 525
         $transaction = $payment->transaction();
526 526
         $payment_was_itemized = $payment->get_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true, false);
527
-        if (! $transaction) {
527
+        if ( ! $transaction) {
528 528
             $this->log(
529 529
                 esc_html__(
530 530
                     // @codingStandardsIgnoreStart
@@ -599,7 +599,7 @@  discard block
 block discarded – undo
599 599
                 esc_html__('Shipping charges calculated by Paypal', 'event_espresso'),
600 600
                 1,
601 601
                 false,
602
-                'paypal_shipping_' . $transaction->ID()
602
+                'paypal_shipping_'.$transaction->ID()
603 603
             );
604 604
             $grand_total_needs_resaving = true;
605 605
         }
Please login to merge, or discard this patch.