Completed
Branch BUG-10042-attendee-mover-confl... (090c9d)
by
unknown
16:23
created

EE_Transaction::txn_status_updated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 2
nc 4
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php if ( ! defined( 'EVENT_ESPRESSO_VERSION' ) ) {
2
	exit( 'No direct script access allowed' );
3
}
4
/**
5
 * EE_Transaction class
6
 *
7
 * @package     Event Espresso
8
 * @subpackage 	includes/classes/EE_Transaction.class.php
9
 * @author      Brent Christensen
10
 */
11
class EE_Transaction extends EE_Base_Class implements EEI_Transaction {
12
13
	/**
14
	 * The length of time in seconds that a lock is applied before being considered expired.
15
	 * It is not long because a transaction should only be locked for the duration of the request that locked it
16
	 */
17
	const LOCK_EXPIRATION = 2;
18
19
	/**
20
	 * txn status upon initial construction.
21
	 *
22
	 * @var string
23
	 */
24
	protected $_old_txn_status;
25
26
27
28
	/**
29
	 * @param array  $props_n_values          incoming values
30
	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
31
	 *                                        used.)
32
	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
33
	 *                                        date_format and the second value is the time format
34
	 * @return EE_Transaction
35
	 * @throws \EE_Error
36
	 */
37
	public static function new_instance( $props_n_values = array(), $timezone = null, $date_formats = array() ) {
38
		$has_object = parent::_check_for_object( $props_n_values, __CLASS__, $timezone, $date_formats );
0 ignored issues
show
Bug introduced by
It seems like $timezone defined by parameter $timezone on line 37 can also be of type string; however, EE_Base_Class::_check_for_object() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_check_for_object() instead of new_instance()). Are you sure this is correct? If so, you might want to change this to $this->_check_for_object().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
39
		$txn = $has_object
40
			? $has_object
41
			: new self( $props_n_values, false, $timezone, $date_formats );
42
		if ( ! $has_object ) {
43
			$txn->set_old_txn_status( $txn->status_ID() );
44
		}
45
		return $txn;
46
	}
47
48
49
50
	/**
51
	 * @param array  $props_n_values  incoming values from the database
52
	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
53
	 *                                the website will be used.
54
	 * @return EE_Transaction
55
	 * @throws \EE_Error
56
	 */
57
	public static function new_instance_from_db( $props_n_values = array(), $timezone = null ) {
58
		$txn = new self( $props_n_values, TRUE, $timezone );
59
		$txn->set_old_txn_status( $txn->status_ID() );
60
		return $txn;
61
	}
62
63
64
65
	/**
66
	 * lock
67
	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
68
	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
69
	 * If that also fails, then an exception is thrown.
70
	 *
71
	 * @access public
72
	 * @throws \EE_Error
73
	 */
74
	public function lock() {
75
		// attempt to set lock, but if that fails...
76
		if ( ! $this->add_extra_meta( 'lock', time(), true )  ) {
77
			// then attempt to remove the lock in case it is expired
78
			if ( $this->_remove_expired_lock() ) {
79
				// if removal was successful, then try setting lock again
80
				$this->lock();
81
			} else {
82
				// but if the lock can not be removed, then throw an exception
83
				throw new EE_Error(
84
					sprintf(
85
						__( 'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.', 'event_espresso' ),
86
						$this->ID()
87
					)
88
				);
89
			}
90
		}
91
	}
92
93
94
95
	/**
96
	 * unlock
97
	 * removes transaction lock applied in EE_Transaction::lock()
98
	 *
99
	 * @access public
100
	 * @return int
101
	 * @throws \EE_Error
102
	 */
103
	public function unlock() {
104
		return $this->delete_extra_meta( 'lock' );
105
	}
106
107
108
109
	/**
110
	 * is_locked
111
	 * Decides whether or not now is the right time to update the transaction.
112
	 * This is useful because we don't always know if it is safe to update the transaction
113
	 * and its related data. why?
114
	 * because it's possible that the transaction is being used in another
115
	 * request and could overwrite anything we save.
116
	 * So we want to only update the txn once we know that won't happen.
117
	 * We also check that the lock isn't expired, and remove it if it is
118
	 *
119
	 * @access public
120
	 * @return boolean
121
	 * @throws \EE_Error
122
	 */
123
	public function is_locked() {
124
		// if TXN is not locked, then return false immediately
125
		if ( ! $this->_get_lock() ) {
126
			return false;
127
		}
128
		// if not, then let's try and remove the lock in case it's expired...
129
		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
130
		// and a positive number if the lock was removed (ie: number of locks deleted),
131
		// so we need to return the opposite
132
		return ! $this->_remove_expired_lock() ? true : false;
133
	}
134
135
136
137
	/**
138
	 * _get_lock
139
	 * Gets the meta field indicating that this TXN is locked
140
	 *
141
	 * @access protected
142
	 * @return int
143
	 * @throws \EE_Error
144
	 */
145
	protected function _get_lock() {
146
		return (int)$this->get_extra_meta( 'lock', true, 0 );
147
	}
148
149
150
151
	/**
152
	 * remove_expired_lock
153
	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
154
	 *
155
	 * @access public
156
	 * @return int
157
	 * @throws \EE_Error
158
	 */
159
	protected function _remove_expired_lock() {
160
		$locked = $this->_get_lock();
161
		if ( $locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked ) {
162
			return $this->unlock();
163
		}
164
		return 0;
165
	}
166
167
168
169
	/**
170
	 *        Set transaction total
171
	 *
172
	 * @access        public
173
	 * @param        float $total total value of transaction
174
	 * @throws \EE_Error
175
	 */
176
	public function set_total( $total = 0.00 ) {
177
		$this->set( 'TXN_total', (float)$total );
178
	}
179
180
181
182
	/**
183
	 *        Set Total Amount Paid to Date
184
	 *
185
	 * @access        public
186
	 * @param        float $total_paid total amount paid to date (sum of all payments)
187
	 * @throws \EE_Error
188
	 */
189
	public function set_paid( $total_paid = 0.00 ) {
190
		$this->set( 'TXN_paid', (float)$total_paid );
191
	}
192
193
194
195
	/**
196
	 *        Set transaction status
197
	 *
198
	 * @access        public
199
	 * @param        string $status whether the transaction is open, declined, accepted, or any number of custom values that can be set
200
	 * @throws \EE_Error
201
	 */
202
	public function set_status( $status = '' ) {
203
		$this->set( 'STS_ID', $status );
204
	}
205
206
207
208
	/**
209
	 *        Set hash salt
210
	 *
211
	 * @access        public
212
	 * @param        string $hash_salt required for some payment gateways
213
	 * @throws \EE_Error
214
	 */
215
	public function set_hash_salt( $hash_salt = '' ) {
216
		$this->set( 'TXN_hash_salt', $hash_salt );
217
	}
218
219
220
221
	/**
222
	 * Sets TXN_reg_steps array
223
	 *
224
	 * @param array $txn_reg_steps
225
	 * @throws \EE_Error
226
	 */
227
	public function set_reg_steps( array $txn_reg_steps ) {
228
		$this->set( 'TXN_reg_steps', $txn_reg_steps );
229
	}
230
231
232
233
	/**
234
	 * Gets TXN_reg_steps
235
	 *
236
	 * @return array
237
	 * @throws \EE_Error
238
	 */
239
	public function reg_steps() {
240
		$TXN_reg_steps = $this->get( 'TXN_reg_steps' );
241
		return is_array( $TXN_reg_steps ) ? (array)$TXN_reg_steps : array();
242
	}
243
244
245
246
	/**
247
	 * @return string of transaction's total cost, with currency symbol and decimal
248
	 * @throws \EE_Error
249
	 */
250
	public function pretty_total() {
251
		return $this->get_pretty( 'TXN_total' );
252
	}
253
254
255
256
	/**
257
	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
258
	 *
259
	 * @return string
260
	 * @throws \EE_Error
261
	 */
262
	public function pretty_paid() {
263
		return $this->get_pretty( 'TXN_paid' );
264
	}
265
266
267
268
	/**
269
	 * calculate the amount remaining for this transaction and return;
270
	 *
271
	 * @access public
272
	 * @return float amount remaining
273
	 * @throws \EE_Error
274
	 */
275
	public function remaining() {
276
		return (float)( $this->total() - $this->paid() );
277
	}
278
279
280
281
	/**
282
	 *        get Transaction Total
283
	 *
284
	 * @access        public
285
	 * @return float
286
	 * @throws \EE_Error
287
	 */
288
	public function total() {
289
		return (float)$this->get( 'TXN_total' );
290
	}
291
292
293
294
	/**
295
	 *        get Total Amount Paid to Date
296
	 *
297
	 * @access        public
298
	 * @return float
299
	 * @throws \EE_Error
300
	 */
301
	public function paid() {
302
		return (float)$this->get( 'TXN_paid' );
303
	}
304
305
306
307
	/**
308
	 *    get_cart_session
309
	 *
310
	 * @access        public
311
	 * @throws \EE_Error
312
	 */
313
	public function get_cart_session() {
314
		$session_data = (array)$this->get( 'TXN_session_data' );
315
		return isset( $session_data[ 'cart' ] ) && $session_data[ 'cart' ] instanceof EE_Cart
316
			? $session_data[ 'cart' ]
317
			: null;
318
	}
319
320
321
322
	/**
323
	 *        get Transaction session data
324
	 *
325
	 * @access        public
326
	 * @throws \EE_Error
327
	 */
328
	public function session_data() {
329
		$session_data = $this->get( 'TXN_session_data' );
330
		if ( empty( $session_data ) ) {
331
			$session_data = array(
332
				'id'            => null,
333
				'user_id'       => null,
334
				'ip_address'    => null,
335
				'user_agent'    => null,
336
				'init_access'   => null,
337
				'last_access'   => null,
338
				'pages_visited' => array()
339
			);
340
		}
341
		return $session_data;
342
	}
343
344
345
346
	/**
347
	 *        Set session data within the TXN object
348
	 *
349
	 * @access        public
350
	 * @param        EE_Session|array $session_data
351
	 * @throws \EE_Error
352
	 */
353
	public function set_txn_session_data( $session_data ) {
354
		if ( $session_data instanceof EE_Session ) {
355
			$this->set( 'TXN_session_data', $session_data->get_session_data( NULL, TRUE ));
356
		} else {
357
			$this->set( 'TXN_session_data', $session_data );
358
		}
359
	}
360
361
362
363
	/**
364
	 *        get Transaction hash salt
365
	 *
366
	 * @access        public
367
	 * @throws \EE_Error
368
	 */
369
	public function hash_salt_() {
370
		return $this->get( 'TXN_hash_salt' );
371
	}
372
373
374
375
	/**
376
	 *    datetime
377
	 *    Returns the transaction datetime as either:
378
	 *            - unix timestamp format ($format = false, $gmt = true)
379
	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
380
	 *              has no affect with this option)), this also may include a timezone abbreviation if the
381
	 *              set timezone in this class differs from what the timezone is on the blog.
382
	 *            - formatted date string including the UTC (timezone) offset (default).
383
	 *
384
	 * @access    public
385
	 * @param    boolean $format - whether to return a unix timestamp (default) or formatted date string
386
	 * @param    boolean $gmt    - whether to return a unix timestamp with UTC offset applied (default) or no UTC offset applied
387
	 * @return    string | int
388
	 * @throws \EE_Error
389
	 */
390
	public function datetime( $format = FALSE, $gmt = FALSE ) {
391
		if ( $format ) {
392
			return $this->get_pretty( 'TXN_timestamp' );
393
		} else if ( $gmt ) {
394
			return $this->get_raw( 'TXN_timestamp' );
395
		} else {
396
			return $this->get( 'TXN_timestamp' );
397
		}
398
	}
399
400
401
402
	/**
403
	 *    Gets registrations on this transaction
404
	 *
405
	 * @param        array   $query_params array of query parameters
406
	 * @param        boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
407
	 * @return EE_Registration[]
408
	 * @throws \EE_Error
409
	 */
410
	public function registrations( $query_params = array(), $get_cached = FALSE ) {
411
		$query_params = ( empty( $query_params ) || ! is_array( $query_params ) )
412
			? array(
413
				'order_by' => array(
414
					'Event.EVT_name' => 'ASC',
415
					'Attendee.ATT_lname' => 'ASC',
416
					'Attendee.ATT_fname' => 'ASC'
417
				)
418
			)
419
			: $query_params;
420
		$query_params = $get_cached ? array() : $query_params;
421
		return $this->get_many_related( 'Registration', $query_params );
422
	}
423
424
425
426
	/**
427
	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event function
428
	 * for getting attendees and how many registrations they each have for an event)
429
	 *
430
	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
431
	 * @throws \EE_Error
432
	 */
433
	public function attendees() {
434
		return $this->get_many_related( 'Attendee', array( array( 'Registration.Transaction.TXN_ID' => $this->ID() ) ) );
435
	}
436
437
438
439
	/**
440
	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
441
	 *
442
	 * @param array $query_params like EEM_Base::get_all
443
	 * @return EE_Payment[]
444
	 * @throws \EE_Error
445
	 */
446
	public function payments( $query_params = array() ) {
447
		return $this->get_many_related( 'Payment', $query_params );
448
	}
449
450
451
452
	/**
453
	 * gets only approved payments for this transaction
454
	 *
455
	 * @return EE_Payment[]
456
	 * @throws \EE_Error
457
	 */
458
	public function approved_payments() {
459
		EE_Registry::instance()->load_model( 'Payment' );
460
		return $this->get_many_related( 'Payment', array( array( 'STS_ID' => EEM_Payment::status_id_approved ), 'order_by' => array( 'PAY_timestamp' => 'DESC' ) ) );
461
	}
462
463
464
465
	/**
466
	 * echoes $this->pretty_status()
467
	 *
468
	 * @param bool $show_icons
469
	 * @return string
470
	 * @throws \EE_Error
471
	 */
472
	public function e_pretty_status( $show_icons = FALSE ) {
473
		echo $this->pretty_status( $show_icons );
474
	}
475
476
477
478
	/**
479
	 * returns a pretty version of the status, good for displaying to users
480
	 *
481
	 * @param bool $show_icons
482
	 * @return string
483
	 * @throws \EE_Error
484
	 */
485
	public function pretty_status( $show_icons = FALSE ) {
486
		$status = EEM_Status::instance()->localized_status( array( $this->status_ID() => __( 'unknown', 'event_espresso' ) ), FALSE, 'sentence' );
487
		$icon = '';
488
		switch ( $this->status_ID() ) {
489
			case EEM_Transaction::complete_status_code:
490
				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
491
				break;
492
			case EEM_Transaction::incomplete_status_code:
493
				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>' : '';
494
				break;
495
			case EEM_Transaction::abandoned_status_code:
496
				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
497
				break;
498
			case EEM_Transaction::failed_status_code:
499
				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
500
				break;
501
			case EEM_Transaction::overpaid_status_code:
502
				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
503
				break;
504
		}
505
		return $icon . $status[ $this->status_ID() ];
506
	}
507
508
509
510
	/**
511
	 *        get Transaction Status
512
	 *
513
	 * @access        public
514
	 * @throws \EE_Error
515
	 */
516
	public function status_ID() {
517
		return $this->get( 'STS_ID' );
518
	}
519
520
521
522
	/**
523
	 * Returns TRUE or FALSE for whether or not this transaction cost any money
524
	 *
525
	 * @return boolean
526
	 * @throws \EE_Error
527
	 */
528
	public function is_free() {
529
		return EEH_Money::compare_floats( $this->get( 'TXN_total' ), 0, '==' );
530
	}
531
532
533
534
	/**
535
	 * Returns whether this transaction is complete
536
	 * Useful in templates and other logic for deciding if we should ask for another payment...
537
	 *
538
	 * @return boolean
539
	 * @throws \EE_Error
540
	 */
541
	public function is_completed() {
542
		return $this->status_ID() === EEM_Transaction::complete_status_code ? TRUE : FALSE;
543
	}
544
545
546
547
	/**
548
	 * Returns whether this transaction is incomplete
549
	 * Useful in templates and other logic for deciding if we should ask for another payment...
550
	 *
551
	 * @return boolean
552
	 * @throws \EE_Error
553
	 */
554
	public function is_incomplete() {
555
		return $this->status_ID() === EEM_Transaction::incomplete_status_code ? TRUE : FALSE;
556
	}
557
558
559
560
	/**
561
	 * Returns whether this transaction is overpaid
562
	 * Useful in templates and other logic for deciding if monies need to be refunded
563
	 *
564
	 * @return boolean
565
	 * @throws \EE_Error
566
	 */
567
	public function is_overpaid() {
568
		return $this->status_ID() === EEM_Transaction::overpaid_status_code ? TRUE : FALSE;
569
	}
570
571
572
573
	/**
574
	 * Returns whether this transaction was abandoned
575
	 * meaning that the transaction/registration process was somehow interrupted and never completed
576
	 * but that contact information exists for at least one registrant
577
	 *
578
	 * @return boolean
579
	 * @throws \EE_Error
580
	 */
581
	public function is_abandoned() {
582
		return $this->status_ID() === EEM_Transaction::abandoned_status_code ? TRUE : FALSE;
583
	}
584
585
586
587
	/**
588
	 * Returns whether this transaction failed
589
	 * meaning that the transaction/registration process was somehow interrupted and never completed
590
	 * and that NO contact information exists for any registrants
591
	 *
592
	 * @return boolean
593
	 * @throws \EE_Error
594
	 */
595
	public function failed() {
596
		return $this->status_ID() === EEM_Transaction::failed_status_code ? TRUE : FALSE;
597
	}
598
599
600
601
	/**
602
	 * This returns the url for the invoice of this transaction
603
	 *
604
	 * @param string $type 'html' or 'pdf' (default is pdf)
605
	 * @access public
606
	 * @return string
607
	 * @throws \EE_Error
608
	 */
609
	public function invoice_url( $type = 'html' ) {
610
		$REG = $this->primary_registration();
611
		if ( ! $REG instanceof EE_Registration ) {
612
			return '';
613
		}
614
		return $REG->invoice_url( $type );
615
	}
616
617
618
619
	/**
620
	 * Gets the primary registration only
621
	 *
622
	 * @return EE_Registration
623
	 * @throws \EE_Error
624
	 */
625
	public function primary_registration() {
626
		return $this->get_first_related( 'Registration', array( array( 'REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT ) ) );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->get_first_...RY_REGISTRANT_COUNT))); (EE_Base_Class) is incompatible with the return type declared by the interface EEI_Transaction::primary_registration of type EEI_Registration.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
627
	}
628
629
630
631
	/**
632
	 * Gets the URL for viewing the receipt
633
	 *
634
	 * @param string $type 'pdf' or 'html' (default is 'html')
635
	 * @return string
636
	 * @throws \EE_Error
637
	 */
638
	public function receipt_url( $type = 'html' ) {
639
		$REG = $this->primary_registration();
640
		if ( ! $REG instanceof EE_Registration ) {
641
			return '';
642
		}
643
		return $REG->receipt_url( $type );
644
	}
645
646
647
648
	/**
649
	 * Gets the URL of the thank you page with this registration REG_url_link added as
650
	 * a query parameter
651
	 *
652
	 * @access public
653
	 * @return string
654
	 * @throws \EE_Error
655
	 */
656
	public function payment_overview_url() {
657
		$primary_registration = $this->primary_registration();
658
		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : FALSE;
659
	}
660
661
662
663
	/**
664
	 * @return string
665
	 * @throws \EE_Error
666
	 */
667
	public function gateway_response_on_transaction() {
668
		$payment = $this->get_first_related( 'Payment' );
669
		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
670
	}
671
672
673
674
	/**
675
	 * Get the status object of this object
676
	 *
677
	 * @return EE_Status
678
	 * @throws \EE_Error
679
	 */
680
	public function status_obj() {
681
		return $this->get_first_related( 'Status' );
682
	}
683
684
685
686
	/**
687
	 * Gets all the extra meta info on this payment
688
	 *
689
	 * @param array $query_params like EEM_Base::get_all
690
	 * @return EE_Extra_Meta
691
	 * @throws \EE_Error
692
	 */
693
	public function extra_meta( $query_params = array() ) {
694
		return $this->get_many_related( 'Extra_Meta', $query_params );
695
	}
696
697
698
699
	/**
700
	 * Wrapper for _add_relation_to
701
	 *
702
	 * @param EE_Registration $registration
703
	 * @return EE_Base_Class the relation was added to
704
	 * @throws \EE_Error
705
	 */
706
	public function add_registration( EE_Registration $registration ) {
707
		return $this->_add_relation_to( $registration, 'Registration' );
708
	}
709
710
711
712
	/**
713
	 * Removes the given registration from being related (even before saving this transaction).
714
	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
715
	 *
716
	 * @param int $registration_or_id
717
	 * @return EE_Base_Class that was removed from being related
718
	 * @throws \EE_Error
719
	 */
720
	public function remove_registration_with_id( $registration_or_id ) {
721
		return $this->_remove_relation_to( $registration_or_id, 'Registration' );
722
	}
723
724
725
726
	/**
727
	 * Gets all the line items which are for ACTUAL items
728
	 *
729
	 * @return EE_Line_Item[]
730
	 * @throws \EE_Error
731
	 */
732
	public function items_purchased() {
733
		return $this->line_items( array( array( 'LIN_type' => EEM_Line_Item::type_line_item ) ) );
734
	}
735
736
737
738
	/**
739
	 * Wrapper for _add_relation_to
740
	 *
741
	 * @param EE_Line_Item $line_item
742
	 * @return EE_Base_Class the relation was added to
743
	 * @throws \EE_Error
744
	 */
745
	public function add_line_item( EE_Line_Item $line_item ) {
746
		return $this->_add_relation_to( $line_item, 'Line_Item' );
747
	}
748
749
750
751
	/**
752
	 * Gets ALL the line items related to this transaction (unstructured)
753
	 *
754
	 * @param array $query_params
755
	 * @return EE_Line_Item[]
756
	 * @throws \EE_Error
757
	 */
758
	public function line_items( $query_params = array() ) {
759
		return $this->get_many_related( 'Line_Item', $query_params );
760
	}
761
762
763
764
	/**
765
	 * Gets all the line items which are taxes on the total
766
	 *
767
	 * @return EE_Line_Item[]
768
	 * @throws \EE_Error
769
	 */
770
	public function tax_items() {
771
		return $this->line_items( array( array( 'LIN_type' => EEM_Line_Item::type_tax ) ) );
772
	}
773
774
775
776
	/**
777
	 * Gets the total line item (which is a parent of all other related line items,
778
	 * meaning it takes them all into account on its total)
779
	 *
780
	 * @param bool $create_if_not_found
781
	 * @return \EE_Line_Item
782
	 * @throws \EE_Error
783
	 */
784
	public function total_line_item( $create_if_not_found = true ) {
785
		$item =  $this->get_first_related( 'Line_Item', array( array( 'LIN_type' => EEM_Line_Item::type_total ) ) );
786
		if( ! $item && $create_if_not_found ){
787
			$item = EEH_Line_Item::create_total_line_item( $this );
788
		}
789
		return $item;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $item; (EE_Base_Class) is incompatible with the return type declared by the interface EEI_Transaction::total_line_item of type EEI_Line_Item.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
790
	}
791
792
793
794
	/**
795
	 * Returns the total amount of tax on this transaction
796
	 * (assumes there's only one tax subtotal line item)
797
	 *
798
	 * @return float
799
	 * @throws \EE_Error
800
	 */
801
	public function tax_total() {
802
		$tax_line_item = $this->tax_total_line_item();
803
		if ( $tax_line_item ) {
804
			return (float)$tax_line_item->total();
805
		} else {
806
			return (float)0;
807
		}
808
	}
809
810
811
812
	/**
813
	 * Gets the tax subtotal line item (assumes there's only one)
814
	 *
815
	 * @return EE_Line_Item
816
	 * @throws \EE_Error
817
	 */
818
	public function tax_total_line_item() {
819
		return EEH_Line_Item::get_taxes_subtotal( $this->total_line_item() );
820
	}
821
822
823
824
	/**
825
	 *  Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
826
	 *
827
	 * @return EE_Form_Section_Proper
828
	 * @throws \EE_Error
829
	 */
830
	public function billing_info(){
831
		$payment_method = $this->payment_method();
832
		if ( !$payment_method){
833
			EE_Error::add_error(__("Could not find billing info for transaction because no gateway has been used for it yet", "event_espresso"), __FILE__, __FUNCTION__, __LINE__);
834
			return false;
835
		}
836
		$primary_reg = $this->primary_registration();
837
		if ( ! $primary_reg ) {
838
			EE_Error::add_error( __( "Cannot get billing info for gateway %s on transaction because no primary registration exists", "event_espresso" ), __FILE__, __FUNCTION__, __LINE__ );
839
			return FALSE;
840
		}
841
		$attendee = $primary_reg->attendee();
842
		if ( ! $attendee ) {
843
			EE_Error::add_error( __( "Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists", "event_espresso" ), __FILE__, __FUNCTION__, __LINE__ );
844
			return FALSE;
845
		}
846
		return $attendee->billing_info_for_payment_method($payment_method);
847
	}
848
849
850
851
	/**
852
	 * Gets PMD_ID
853
	 *
854
	 * @return int
855
	 * @throws \EE_Error
856
	 */
857
	public function payment_method_ID() {
858
		return $this->get('PMD_ID');
859
	}
860
861
862
863
	/**
864
	 * Sets PMD_ID
865
	 *
866
	 * @param int $PMD_ID
867
	 * @return boolean
868
	 * @throws \EE_Error
869
	 */
870
	public function set_payment_method_ID($PMD_ID) {
871
		$this->set('PMD_ID', $PMD_ID);
872
	}
873
874
875
876
	/**
877
	 * Gets the last-used payment method on this transaction
878
	 * (we COULD just use the last-made payment, but some payment methods, namely
879
	 * offline ones, dont' create payments)
880
	 *
881
	 * @return EE_Payment_Method
882
	 * @throws \EE_Error
883
	 */
884
	public function payment_method(){
885
		$pm = $this->get_first_related('Payment_Method');
886
		if( $pm instanceof EE_Payment_Method ){
887
			return $pm;
888
		}else{
889
			$last_payment = $this->last_payment();
890
			if( $last_payment instanceof EE_Payment && $last_payment->payment_method() ){
891
				return $last_payment->payment_method();
892
			}else{
893
				return NULL;
894
			}
895
		}
896
	}
897
898
899
900
	/**
901
	 * Gets the last payment made
902
	 *
903
	 * @return EE_Payment
904
	 * @throws \EE_Error
905
	 */
906
	public function last_payment() {
907
		return $this->get_first_related( 'Payment', array( 'order_by' => array( 'PAY_ID' => 'desc' ) ) );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->get_first_...('PAY_ID' => 'desc'))); (EE_Base_Class) is incompatible with the return type declared by the interface EEI_Transaction::last_payment of type EEI_Payment.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
908
	}
909
910
911
912
	/**
913
	 * Gets all the line items which are unrelated to tickets on this transaction
914
	 *
915
	 * @return EE_Line_Item[]
916
	 * @throws \EE_Error
917
	 */
918
	public function non_ticket_line_items(){
919
		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction( $this->ID() );
920
	}
921
922
923
924
	/**
925
	 * possibly toggles TXN status
926
	 *
927
	 * @param  boolean $update whether to save the TXN
928
	 * @return boolean whether the TXN was saved
929
	 * @throws \RuntimeException
930
	 */
931
	public function update_status_based_on_total_paid($update = true)
932
	{
933
		// set transaction status based on comparison of TXN_paid vs TXN_total
934
		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
935
			$new_txn_status = EEM_Transaction::overpaid_status_code;
936
		} else if (EEH_Money::compare_floats($this->paid(), $this->total())) {
937
			$new_txn_status = EEM_Transaction::complete_status_code;
938
		} else if (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
939
			$new_txn_status = EEM_Transaction::incomplete_status_code;
940
		} else {
941
			throw new RuntimeException(
942
				__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
943
			);
944
		}
945
		if ($new_txn_status !== $this->status_ID()) {
946
			$this->set_status($new_txn_status);
947
			if ($update) {
948
				return $this->save() ? true : false;
949
			}
950
		}
951
		return false;
952
	}
953
954
955
956
	/**
957
	 * Updates the transaction's status and total_paid based on all the payments
958
	 * that apply to it
959
	 *
960
	 * @deprecated
961
	 * @return boolean
962
	 * @throws \EE_Error
963
	 */
964 View Code Duplication
	public function update_based_on_payments()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
965
	{
966
		EE_Error::doing_it_wrong(
967
			__CLASS__ . '::' . __FUNCTION__,
968
			sprintf(__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
969
				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'),
970
			'4.6.0'
971
		);
972
		/** @type EE_Transaction_Processor $transaction_processor */
973
		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
974
		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
975
	}
976
977
978
979
	/**
980
	 * @return string
981
	 */
982
	public function old_txn_status() {
983
		return $this->_old_txn_status;
984
	}
985
986
987
988
	/**
989
	 * @param string $old_txn_status
990
	 */
991
	public function set_old_txn_status( $old_txn_status ) {
992
		// only set the first time
993
		if ( $this->_old_txn_status === null ) {
994
			$this->_old_txn_status = $old_txn_status;
995
		}
996
	}
997
998
999
1000
	/**
1001
	 * reg_status_updated
1002
	 *
1003
	 * @return bool
1004
	 */
1005
	public function txn_status_updated() {
1006
		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null ? true : false;
1007
	}
1008
1009
1010
1011
	/**
1012
	 * _reg_steps_completed
1013
	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1014
	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1015
	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1016
	 *
1017
	 * @access private
1018
	 * @param string         $reg_step_slug
1019
	 * @param bool           $check_all
1020
	 * @return boolean | int
1021
	 */
1022
	private function _reg_steps_completed( $reg_step_slug = '', $check_all = true ) {
1023
		$reg_steps = $this->reg_steps();
1024
		if ( ! is_array( $reg_steps ) || empty( $reg_steps ) ) {
1025
			return false;
1026
		}
1027
		// loop thru reg steps array)
1028
		foreach ( $reg_steps as $slug => $reg_step_completed ) {
1029
			// if NOT checking ALL steps (only checking one step)
1030
			if ( ! $check_all ) {
1031
				// and this is the one
1032
				if ( $slug === $reg_step_slug ) {
1033
					return $reg_step_completed;
1034
				} else {
1035
					// skip to next reg step in loop
1036
					continue;
1037
				}
1038
			}
1039
			// $check_all must be true, else we would never have gotten to this point
1040
			if ( $slug === $reg_step_slug ) {
1041
				// if we reach this point, then we are testing either:
1042
				// all_reg_steps_completed_except() or
1043
				// all_reg_steps_completed_except_final_step(),
1044
				// and since this is the reg step EXCEPTION being tested
1045
				// we want to return true (yes true) if this reg step is NOT completed
1046
				// ie: "is everything completed except the final step?"
1047
				// "that is correct... the final step is not completed, but all others are."
1048
				return $reg_step_completed !== true ? true : false;
1049
			} else if ( $reg_step_completed !== true ) {
1050
				// if any reg step is NOT completed, then ALL steps are not completed
1051
				return false;
1052
			}
1053
		}
1054
		return true;
1055
	}
1056
1057
1058
1059
	/**
1060
	 * all_reg_steps_completed
1061
	 * returns:
1062
	 *    true if ALL reg steps have been marked as completed
1063
	 *        or false if any step is not completed
1064
	 *
1065
	 * @return boolean
1066
	 */
1067
	public function all_reg_steps_completed() {
1068
		return $this->_reg_steps_completed();
1069
	}
1070
1071
1072
1073
	/**
1074
	 * all_reg_steps_completed_except
1075
	 * returns:
1076
	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1077
	 *        or false if any other step is not completed
1078
	 *        or false if ALL steps are completed including the exception you are testing !!!
1079
	 *
1080
	 * @param string         $exception
1081
	 * @return boolean
1082
	 */
1083
	public function all_reg_steps_completed_except( $exception = '' ) {
1084
		return $this->_reg_steps_completed( $exception );
1085
	}
1086
1087
1088
1089
	/**
1090
	 * all_reg_steps_completed_except
1091
	 * returns:
1092
	 *        true if ALL reg steps, except the final step, have been marked as completed
1093
	 *        or false if any step is not completed
1094
	 *    or false if ALL steps are completed including the final step !!!
1095
	 *
1096
	 * @return boolean
1097
	 */
1098
	public function all_reg_steps_completed_except_final_step() {
1099
		return $this->_reg_steps_completed( 'finalize_registration' );
1100
	}
1101
1102
1103
1104
	/**
1105
	 * reg_step_completed
1106
	 * returns:
1107
	 *    true if a specific reg step has been marked as completed
1108
	 *    a Unix timestamp if it has been initialized but not yet completed,
1109
	 *    or false if it has not yet been initialized
1110
	 *
1111
	 * @param string         $reg_step_slug
1112
	 * @return boolean | int
1113
	 */
1114
	public function reg_step_completed( $reg_step_slug ) {
1115
		return $this->_reg_steps_completed( $reg_step_slug, false );
1116
	}
1117
1118
1119
1120
	/**
1121
	 * completed_final_reg_step
1122
	 * returns:
1123
	 *    true if the finalize_registration reg step has been marked as completed
1124
	 *    a Unix timestamp if it has been initialized but not yet completed,
1125
	 *    or false if it has not yet been initialized
1126
	 *
1127
	 * @return boolean | int
1128
	 */
1129
	public function final_reg_step_completed() {
1130
		return $this->_reg_steps_completed( 'finalize_registration', false );
1131
	}
1132
1133
1134
1135
	/**
1136
	 * set_reg_step_initiated
1137
	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1138
	 *
1139
	 * @access public
1140
	 * @param string          $reg_step_slug
1141
	 * @return boolean
1142
	 * @throws \EE_Error
1143
	 */
1144
	public function set_reg_step_initiated( $reg_step_slug ) {
1145
		return $this->_set_reg_step_completed_status( $reg_step_slug, time() );
1146
	}
1147
1148
1149
1150
	/**
1151
	 * set_reg_step_completed
1152
	 * given a valid TXN_reg_step, this sets the step as completed
1153
	 *
1154
	 * @access public
1155
	 * @param string          $reg_step_slug
1156
	 * @return boolean
1157
	 * @throws \EE_Error
1158
	 */
1159
	public function set_reg_step_completed( $reg_step_slug ) {
1160
		return $this->_set_reg_step_completed_status( $reg_step_slug, true );
1161
	}
1162
1163
1164
1165
	/**
1166
	 * set_reg_step_completed
1167
	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1168
	 *
1169
	 * @access public
1170
	 * @param string          $reg_step_slug
1171
	 * @return boolean
1172
	 * @throws \EE_Error
1173
	 */
1174
	public function set_reg_step_not_completed( $reg_step_slug ) {
1175
		return $this->_set_reg_step_completed_status( $reg_step_slug, false );
1176
	}
1177
1178
1179
1180
	/**
1181
	 * set_reg_step_completed
1182
	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1183
	 *
1184
	 * @access private
1185
	 * @param  string          $reg_step_slug
1186
	 * @param  boolean|int     $status
1187
	 * @return boolean
1188
	 * @throws \EE_Error
1189
	 */
1190
	private function _set_reg_step_completed_status( $reg_step_slug, $status ) {
1191
		// validate status
1192
		$status = is_bool( $status ) || is_int( $status ) ? $status : false;
1193
		// get reg steps array
1194
		$txn_reg_steps = $this->reg_steps();
1195
		// if reg step does NOT exist
1196
		if ( ! isset( $txn_reg_steps[ $reg_step_slug ] ) ) {
1197
			return false;
1198
		}
1199
		// if  we're trying to complete a step that is already completed
1200
		if ( $txn_reg_steps[ $reg_step_slug ] === true ) {
1201
			return true;
1202
		}
1203
		// if  we're trying to complete a step that hasn't even started
1204
		if ( $status === true && $txn_reg_steps[ $reg_step_slug ] === false ) {
1205
			return false;
1206
		}
1207
		// if current status value matches the incoming value (no change)
1208
		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1209
		if ( (int) $txn_reg_steps[ $reg_step_slug ] === (int) $status ) {
1210
			// this will happen in cases where multiple AJAX requests occur during the same step
1211
			return true;
1212
		}
1213
		// if we're trying to set a start time, but it has already been set...
1214
		if ( is_numeric( $status ) && is_numeric( $txn_reg_steps[ $reg_step_slug ] ) ) {
1215
			// skip the update below, but don't return FALSE so that errors won't be displayed
1216
			return true;
1217
		}
1218
		// update completed status
1219
		$txn_reg_steps[ $reg_step_slug ] = $status;
1220
		$this->set_reg_steps( $txn_reg_steps );
1221
		$this->save();
1222
		return true;
1223
	}
1224
1225
1226
1227
	/**
1228
	 * remove_reg_step
1229
	 * given a valid TXN_reg_step slug, this will remove (unset)
1230
	 * the reg step from the TXN reg step array
1231
	 *
1232
	 * @access public
1233
	 * @param string          $reg_step_slug
1234
	 * @return void
1235
	 */
1236
	public function remove_reg_step( $reg_step_slug ) {
1237
		// get reg steps array
1238
		$txn_reg_steps = $this->reg_steps();
1239
		unset( $txn_reg_steps[ $reg_step_slug ] );
1240
		$this->set_reg_steps( $txn_reg_steps );
1241
	}
1242
1243
1244
1245
	/**
1246
	 *    toggle_failed_transaction_status
1247
	 * upgrades a TXNs status from failed to abandoned,
1248
	 * meaning that contact information has been captured for at least one registrant
1249
	 *
1250
	 * @access public
1251
	 * @param bool $save
1252
	 * @return bool
1253
	 */
1254
	public function toggle_failed_transaction_status( $save = true ) {
1255
		// if TXN status is still set as "failed"...
1256
		if ( $this->status_ID() === EEM_Transaction::failed_status_code ) {
1257
			$this->set_status( EEM_Transaction::abandoned_status_code );
1258
			if ( $save ) {
1259
				$this->save();
1260
			}
1261
			return true;
1262
		}
1263
		return false;
1264
	}
1265
1266
1267
1268
	/**
1269
	 * toggle_abandoned_transaction_status
1270
	 * upgrades a TXNs status from failed or abandoned to incomplete
1271
	 *
1272
	 * @access public
1273
	 * @return boolean
1274
	 */
1275
	public function toggle_abandoned_transaction_status() {
1276
		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1277
		$txn_status = $this->status_ID();
1278
		if (
1279
			$txn_status === EEM_Transaction::failed_status_code
1280
			|| $txn_status === EEM_Transaction::abandoned_status_code
1281
		) {
1282
			// if a contact record for the primary registrant has been created
1283
			if (
1284
				$this->primary_registration() instanceof EE_Registration
1285
				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1286
			) {
1287
				$this->set_status( EEM_Transaction::incomplete_status_code );
1288
			} else {
1289
				// no contact record? yer abandoned!
1290
				$this->set_status( EEM_Transaction::abandoned_status_code );
1291
			}
1292
			return true;
1293
		}
1294
		return false;
1295
	}
1296
1297
1298
1299
	/**
1300
	 * checks if an Abandoned TXN has any related payments, and if so,
1301
	 * updates the TXN status based on the amount paid
1302
	 */
1303
	public function verify_abandoned_transaction_status() {
1304
		if ( $this->status_ID() !== EEM_Transaction::abandoned_status_code ) {
1305
			return;
1306
		}
1307
		$payments = $this->get_many_related( 'Payment' );
1308
		if ( ! empty( $payments ) ) {
1309
			foreach ( $payments as $payment ) {
1310
				if ( $payment instanceof EE_Payment ) {
1311
					// kk this TXN should NOT be abandoned
1312
					$this->update_status_based_on_total_paid();
1313
					if ( is_admin() && ! ( defined('DOING_AJAX') && DOING_AJAX ) ) {
1314
						EE_Error::add_attention(
1315
							sprintf(
1316
								esc_html__(
1317
									'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1318
									'event_espresso'
1319
								),
1320
								$this->ID(),
1321
								$this->pretty_status()
1322
							)
1323
						);
1324
					}
1325
					// get final reg step status
1326
					$finalized = $this->final_reg_step_completed();
1327
					// if the 'finalize_registration' step has been initiated (has a timestamp)
1328
					// but has not yet been fully completed (TRUE)
1329
					if ( is_int( $finalized ) && $finalized !== false && $finalized !== true ) {
1330
						$this->set_reg_step_completed( 'finalize_registration' );
1331
						$this->save();
1332
					}
1333
				}
1334
			}
1335
		}
1336
	}
1337
1338
}/* End of file EE_Transaction.class.php */
1339
/* Location: includes/classes/EE_Transaction.class.php */
1340