Completed
Branch BUG-9548-transaction-completio... (b10ae2)
by
unknown
558:48 queued 538:36
created

EE_Transaction::get_lock()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 3
rs 10
c 1
b 0
f 1
cc 1
eloc 2
nc 1
nop 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
	 * @param array  $props_n_values          incoming values
21
	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
22
	 *                                        used.)
23
	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
24
	 *                                        date_format and the second value is the time format
25
	 * @return EE_Transaction
26
	 * @throws \EE_Error
27
	 */
28
	public static function new_instance( $props_n_values = array(), $timezone = null, $date_formats = array() ) {
29
		$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 28 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...
30
		return $has_object
31
			? $has_object
32
			: new self( $props_n_values, false, $timezone, $date_formats );
33
	}
34
35
36
37
	/**
38
	 * @param array  $props_n_values  incoming values from the database
39
	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
40
	 *                                the website will be used.
41
	 * @return EE_Transaction
42
	 * @throws \EE_Error
43
	 */
44
	public static function new_instance_from_db( $props_n_values = array(), $timezone = null ) {
45
		return new self( $props_n_values, TRUE, $timezone );
46
	}
47
48
49
50
	/**
51
	 * lock
52
	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
53
	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
54
	 * If that also fails, then an exception is thrown.
55
	 *
56
	 * @access public
57
	 * @return boolean
58
	 * @throws \EE_Error
59
	 */
60
	public function lock() {
61
		// attempt to set lock, but if that fails...
62
		if ( ! $this->add_extra_meta( 'lock', time(), true )  ) {
63
			// then attempt to remove the lock in case it is expired
64
			if ( $this->_remove_expired_lock() ) {
65
				// if removal was successful, then try setting lock again
66
				$this->lock();
67
			} else {
68
				// but if the lock can not be removed, then throw an exception
69
				throw new EE_Error(
70
					sprintf(
71
						__( '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' ),
72
						$this->ID()
73
					)
74
				);
75
			}
76
		}
77
	}
78
79
80
81
	/**
82
	 * unlock
83
	 * removes transaction lock applied in EE_Transaction::lock()
84
	 *
85
	 * @access public
86
	 * @return int
87
	 * @throws \EE_Error
88
	 */
89
	public function unlock() {
90
		return $this->delete_extra_meta( 'lock' );
91
	}
92
93
94
95
	/**
96
	 * is_locked
97
	 * Decides whether or not now is the right time to update the transaction.
98
	 * This is useful because we don't always know if it is safe to update the transaction
99
	 * and its related data. why?
100
	 * because it's possible that the transaction is being used in another
101
	 * request and could overwrite anything we save.
102
	 * So we want to only update the txn once we know that won't happen.
103
	 * We also check that the lock isn't expired, and remove it if it is
104
	 *
105
	 * @access public
106
	 * @return int
107
	 * @throws \EE_Error
108
	 */
109
	public function is_locked() {
110
		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
111
		// and a positive number if the lock was removed (ie: number of locks deleted),
112
		// so we use ! to return the opposite
113
		return ! $this->_remove_expired_lock();
114
	}
115
116
117
118
	/**
119
	 * get_lock
120
	 * Gets the meta field indicating that this TXN is locked
121
	 *
122
	 * @access protected
123
	 * @return int
124
	 * @throws \EE_Error
125
	 */
126
	protected function get_lock() {
127
		return (int)$this->get_extra_meta( 'lock', true, 0 );
128
	}
129
130
131
132
	/**
133
	 * remove_expired_lock
134
	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
135
136
	 *
137
*@access public
138
	 * @return int
139
	 * @throws \EE_Error
140
	 */
141
	protected function _remove_expired_lock() {
142
		$locked = $this->get_lock();
143
		if ( $locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked ) {
144
			return $this->unlock();
145
		}
146
		return 0;
147
	}
148
149
150
151
	/**
152
	 *        Set transaction total
153
	 *
154
	 * @access        public
155
	 * @param        float $total total value of transaction
156
	 * @throws \EE_Error
157
	 */
158
	public function set_total( $total = 0.00 ) {
159
		$this->set( 'TXN_total', (float)$total );
160
	}
161
162
163
164
	/**
165
	 *        Set Total Amount Paid to Date
166
	 *
167
	 * @access        public
168
	 * @param        float $total_paid total amount paid to date (sum of all payments)
169
	 * @throws \EE_Error
170
	 */
171
	public function set_paid( $total_paid = 0.00 ) {
172
		$this->set( 'TXN_paid', (float)$total_paid );
173
	}
174
175
176
177
	/**
178
	 *        Set transaction status
179
	 *
180
	 * @access        public
181
	 * @param        string $status whether the transaction is open, declined, accepted, or any number of custom values that can be set
182
	 * @throws \EE_Error
183
	 */
184
	public function set_status( $status = '' ) {
185
		$this->set( 'STS_ID', $status );
186
	}
187
188
189
190
	/**
191
	 *        Set hash salt
192
	 *
193
	 * @access        public
194
	 * @param        string $hash_salt required for some payment gateways
195
	 * @throws \EE_Error
196
	 */
197
	public function set_hash_salt( $hash_salt = '' ) {
198
		$this->set( 'TXN_hash_salt', $hash_salt );
199
	}
200
201
202
203
	/**
204
	 * Sets TXN_reg_steps array
205
	 *
206
	 * @param array $txn_reg_steps
207
	 * @throws \EE_Error
208
	 */
209
	public function set_reg_steps( array $txn_reg_steps ) {
210
		$this->set( 'TXN_reg_steps', $txn_reg_steps );
211
	}
212
213
214
215
	/**
216
	 * Gets TXN_reg_steps
217
	 *
218
	 * @return array
219
	 * @throws \EE_Error
220
	 */
221
	public function reg_steps() {
222
		$TXN_reg_steps = $this->get( 'TXN_reg_steps' );
223
		return is_array( $TXN_reg_steps ) ? (array)$TXN_reg_steps : array();
224
	}
225
226
227
228
	/**
229
	 * @return string of transaction's total cost, with currency symbol and decimal
230
	 * @throws \EE_Error
231
	 */
232
	public function pretty_total() {
233
		return $this->get_pretty( 'TXN_total' );
234
	}
235
236
237
238
	/**
239
	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
240
	 *
241
	 * @return string
242
	 * @throws \EE_Error
243
	 */
244
	public function pretty_paid() {
245
		return $this->get_pretty( 'TXN_paid' );
246
	}
247
248
249
250
	/**
251
	 * calculate the amount remaining for this transaction and return;
252
	 *
253
	 * @access public
254
	 * @return float amount remaining
255
	 * @throws \EE_Error
256
	 */
257
	public function remaining() {
258
		return (float)( $this->total() - $this->paid() );
259
	}
260
261
262
263
	/**
264
	 *        get Transaction Total
265
	 *
266
	 * @access        public
267
	 * @return float
268
	 * @throws \EE_Error
269
	 */
270
	public function total() {
271
		return (float)$this->get( 'TXN_total' );
272
	}
273
274
275
276
	/**
277
	 *        get Total Amount Paid to Date
278
	 *
279
	 * @access        public
280
	 * @return float
281
	 * @throws \EE_Error
282
	 */
283
	public function paid() {
284
		return (float)$this->get( 'TXN_paid' );
285
	}
286
287
288
289
	/**
290
	 *    get_cart_session
291
	 *
292
	 * @access        public
293
	 * @throws \EE_Error
294
	 */
295
	public function get_cart_session() {
296
		$session_data = (array)$this->get( 'TXN_session_data' );
297
		return isset( $session_data[ 'cart' ] ) && $session_data[ 'cart' ] instanceof EE_Cart
298
			? $session_data[ 'cart' ]
299
			: null;
300
	}
301
302
303
304
	/**
305
	 *        get Transaction session data
306
	 *
307
	 * @access        public
308
	 * @throws \EE_Error
309
	 */
310
	public function session_data() {
311
		$session_data = $this->get( 'TXN_session_data' );
312
		if ( empty( $session_data ) ) {
313
			$session_data = array(
314
				'id'            => null,
315
				'user_id'       => null,
316
				'ip_address'    => null,
317
				'user_agent'    => null,
318
				'init_access'   => null,
319
				'last_access'   => null,
320
				'pages_visited' => array()
321
			);
322
		}
323
		return $session_data;
324
	}
325
326
327
328
	/**
329
	 *        Set session data within the TXN object
330
	 *
331
	 * @access        public
332
	 * @param        EE_Session|array $session_data
333
	 * @throws \EE_Error
334
	 */
335
	public function set_txn_session_data( $session_data ) {
336
		if ( $session_data instanceof EE_Session ) {
337
			$this->set( 'TXN_session_data', $session_data->get_session_data( NULL, TRUE ));
338
		} else {
339
			$this->set( 'TXN_session_data', $session_data );
340
		}
341
	}
342
343
344
345
	/**
346
	 *        get Transaction hash salt
347
	 *
348
	 * @access        public
349
	 * @throws \EE_Error
350
	 */
351
	public function hash_salt_() {
352
		return $this->get( 'TXN_hash_salt' );
353
	}
354
355
356
357
	/**
358
	 *    datetime
359
	 *    Returns the transaction datetime as either:
360
	 *            - unix timestamp format ($format = false, $gmt = true)
361
	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
362
	 *              has no affect with this option)), this also may include a timezone abbreviation if the
363
	 *              set timezone in this class differs from what the timezone is on the blog.
364
	 *            - formatted date string including the UTC (timezone) offset (default).
365
	 *
366
	 * @access    public
367
	 * @param    boolean $format - whether to return a unix timestamp (default) or formatted date string
368
	 * @param    boolean $gmt    - whether to return a unix timestamp with UTC offset applied (default) or no UTC offset applied
369
	 * @return    string | int
370
	 * @throws \EE_Error
371
	 */
372
	public function datetime( $format = FALSE, $gmt = FALSE ) {
373
		if ( $format ) {
374
			return $this->get_pretty( 'TXN_timestamp' );
375
		} else if ( $gmt ) {
376
			return $this->get_raw( 'TXN_timestamp' );
377
		} else {
378
			return $this->get( 'TXN_timestamp' );
379
		}
380
	}
381
382
383
384
	/**
385
	 *    Gets registrations on this transaction
386
	 *
387
	 * @param        array   $query_params array of query parameters
388
	 * @param        boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
389
	 * @return EE_Registration[]
390
	 * @throws \EE_Error
391
	 */
392
	public function registrations( $query_params = array(), $get_cached = FALSE ) {
393
		$query_params = ( empty( $query_params ) || ! is_array( $query_params ) )
394
			? array(
395
				'order_by' => array(
396
					'Event.EVT_name' => 'ASC',
397
					'Attendee.ATT_lname' => 'ASC',
398
					'Attendee.ATT_fname' => 'ASC'
399
				)
400
			)
401
			: $query_params;
402
		$query_params = $get_cached ? array() : $query_params;
403
		return $this->get_many_related( 'Registration', $query_params );
404
	}
405
406
407
408
	/**
409
	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event function
410
	 * for getting attendees and how many registrations they each have for an event)
411
	 *
412
	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
413
	 * @throws \EE_Error
414
	 */
415
	public function attendees() {
416
		return $this->get_many_related( 'Attendee', array( array( 'Registration.Transaction.TXN_ID' => $this->ID() ) ) );
417
	}
418
419
420
421
	/**
422
	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
423
	 *
424
	 * @param array $query_params like EEM_Base::get_all
425
	 * @return EE_Payment[]
426
	 * @throws \EE_Error
427
	 */
428
	public function payments( $query_params = array() ) {
429
		return $this->get_many_related( 'Payment', $query_params );
430
	}
431
432
433
434
	/**
435
	 * gets only approved payments for this transaction
436
	 *
437
	 * @return EE_Payment[]
438
	 * @throws \EE_Error
439
	 */
440
	public function approved_payments() {
441
		EE_Registry::instance()->load_model( 'Payment' );
442
		return $this->get_many_related( 'Payment', array( array( 'STS_ID' => EEM_Payment::status_id_approved ), 'order_by' => array( 'PAY_timestamp' => 'DESC' ) ) );
443
	}
444
445
446
447
	/**
448
	 * echoes $this->pretty_status()
449
	 *
450
	 * @param bool $show_icons
451
	 * @return string
452
	 * @throws \EE_Error
453
	 */
454
	public function e_pretty_status( $show_icons = FALSE ) {
455
		echo $this->pretty_status( $show_icons );
456
	}
457
458
459
460
	/**
461
	 * returns a pretty version of the status, good for displaying to users
462
	 *
463
	 * @param bool $show_icons
464
	 * @return string
465
	 * @throws \EE_Error
466
	 */
467
	public function pretty_status( $show_icons = FALSE ) {
468
		$status = EEM_Status::instance()->localized_status( array( $this->status_ID() => __( 'unknown', 'event_espresso' ) ), FALSE, 'sentence' );
469
		$icon = '';
470
		switch ( $this->status_ID() ) {
471
			case EEM_Transaction::complete_status_code:
472
				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
473
				break;
474
			case EEM_Transaction::incomplete_status_code:
475
				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>' : '';
476
				break;
477
			case EEM_Transaction::abandoned_status_code:
478
				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
479
				break;
480
			case EEM_Transaction::failed_status_code:
481
				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
482
				break;
483
			case EEM_Transaction::overpaid_status_code:
484
				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
485
				break;
486
		}
487
		return $icon . $status[ $this->status_ID() ];
488
	}
489
490
491
492
	/**
493
	 *        get Transaction Status
494
	 *
495
	 * @access        public
496
	 * @throws \EE_Error
497
	 */
498
	public function status_ID() {
499
		return $this->get( 'STS_ID' );
500
	}
501
502
503
504
	/**
505
	 * Returns TRUE or FALSE for whether or not this transaction cost any money
506
	 *
507
	 * @return boolean
508
	 * @throws \EE_Error
509
	 */
510
	public function is_free() {
511
		return (float)$this->get( 'TXN_total' ) === (float)0 ? TRUE : FALSE;
512
	}
513
514
515
516
	/**
517
	 * Returns whether this transaction is complete
518
	 * Useful in templates and other logic for deciding if we should ask for another payment...
519
	 *
520
	 * @return boolean
521
	 * @throws \EE_Error
522
	 */
523
	public function is_completed() {
524
		return $this->status_ID() === EEM_Transaction::complete_status_code ? TRUE : FALSE;
525
	}
526
527
528
529
	/**
530
	 * Returns whether this transaction is incomplete
531
	 * Useful in templates and other logic for deciding if we should ask for another payment...
532
	 *
533
	 * @return boolean
534
	 * @throws \EE_Error
535
	 */
536
	public function is_incomplete() {
537
		return $this->status_ID() === EEM_Transaction::incomplete_status_code ? TRUE : FALSE;
538
	}
539
540
541
542
	/**
543
	 * Returns whether this transaction is overpaid
544
	 * Useful in templates and other logic for deciding if monies need to be refunded
545
	 *
546
	 * @return boolean
547
	 * @throws \EE_Error
548
	 */
549
	public function is_overpaid() {
550
		return $this->status_ID() === EEM_Transaction::overpaid_status_code ? TRUE : FALSE;
551
	}
552
553
554
555
	/**
556
	 * Returns whether this transaction was abandoned
557
	 * meaning that the transaction/registration process was somehow interrupted and never completed
558
	 * but that contact information exists for at least one registrant
559
	 *
560
	 * @return boolean
561
	 * @throws \EE_Error
562
	 */
563
	public function is_abandoned() {
564
		return $this->status_ID() === EEM_Transaction::abandoned_status_code ? TRUE : FALSE;
565
	}
566
567
568
569
	/**
570
	 * Returns whether this transaction failed
571
	 * meaning that the transaction/registration process was somehow interrupted and never completed
572
	 * and that NO contact information exists for any registrants
573
	 *
574
	 * @return boolean
575
	 * @throws \EE_Error
576
	 */
577
	public function failed() {
578
		return $this->status_ID() === EEM_Transaction::failed_status_code ? TRUE : FALSE;
579
	}
580
581
582
583
	/**
584
	 * This returns the url for the invoice of this transaction
585
	 *
586
	 * @param string $type 'html' or 'pdf' (default is pdf)
587
	 * @access public
588
	 * @return string
589
	 * @throws \EE_Error
590
	 */
591
	public function invoice_url( $type = 'html' ) {
592
		$REG = $this->primary_registration();
593
		if ( ! $REG instanceof EE_Registration ) {
594
			return '';
595
		}
596
		return $REG->invoice_url( $type );
597
	}
598
599
600
601
	/**
602
	 * Gets the primary registration only
603
	 *
604
	 * @return EE_Registration
605
	 * @throws \EE_Error
606
	 */
607
	public function primary_registration() {
608
		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...
609
	}
610
611
612
613
	/**
614
	 * Gets the URL for viewing the receipt
615
	 *
616
	 * @param string $type 'pdf' or 'html' (default is 'html')
617
	 * @return string
618
	 * @throws \EE_Error
619
	 */
620
	public function receipt_url( $type = 'html' ) {
621
		$REG = $this->primary_registration();
622
		if ( ! $REG instanceof EE_Registration ) {
623
			return '';
624
		}
625
		return $REG->receipt_url( $type );
626
	}
627
628
629
630
	/**
631
	 * Gets the URL of the thank you page with this registration REG_url_link added as
632
	 * a query parameter
633
	 *
634
	 * @access public
635
	 * @return string
636
	 * @throws \EE_Error
637
	 */
638
	public function payment_overview_url() {
639
		$primary_registration = $this->primary_registration();
640
		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : FALSE;
641
	}
642
643
644
645
	/**
646
	 * Updates the transaction's status and total_paid based on all the payments
647
	 * that apply to it
648
	 *
649
	 * @deprecated
650
	 * @return boolean
651
	 * @throws \EE_Error
652
	 */
653 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...
654
		EE_Error::doing_it_wrong(
655
			__CLASS__ . '::' . __FUNCTION__,
656
			sprintf( __( 'This method is deprecated. Please use "%s" instead', 'event_espresso' ), 'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()' ),
657
			'4.6.0'
658
		);
659
		/** @type EE_Transaction_Processor $transaction_processor */
660
		$transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
661
		return  $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment( $this );
662
	}
663
664
665
666
	/**
667
	 * @return string
668
	 * @throws \EE_Error
669
	 */
670
	public function gateway_response_on_transaction() {
671
		$payment = $this->get_first_related( 'Payment' );
672
		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
673
	}
674
675
676
677
	/**
678
	 * Get the status object of this object
679
	 *
680
	 * @return EE_Status
681
	 * @throws \EE_Error
682
	 */
683
	public function status_obj() {
684
		return $this->get_first_related( 'Status' );
685
	}
686
687
688
689
	/**
690
	 * Gets all the extra meta info on this payment
691
	 *
692
	 * @param array $query_params like EEM_Base::get_all
693
	 * @return EE_Extra_Meta
694
	 * @throws \EE_Error
695
	 */
696
	public function extra_meta( $query_params = array() ) {
697
		return $this->get_many_related( 'Extra_Meta', $query_params );
698
	}
699
700
701
702
	/**
703
	 * Wrapper for _add_relation_to
704
	 *
705
	 * @param EE_Registration $registration
706
	 * @return EE_Base_Class the relation was added to
707
	 * @throws \EE_Error
708
	 */
709
	public function add_registration( EE_Registration $registration ) {
710
		return $this->_add_relation_to( $registration, 'Registration' );
711
	}
712
713
714
715
	/**
716
	 * Removes the given registration from being related (even before saving this transaction).
717
	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
718
	 *
719
	 * @param int $registration_or_id
720
	 * @return EE_Base_Class that was removed from being related
721
	 * @throws \EE_Error
722
	 */
723
	public function remove_registration_with_id( $registration_or_id ) {
724
		return $this->_remove_relation_to( $registration_or_id, 'Registration' );
725
	}
726
727
728
729
	/**
730
	 * Gets all the line items which are for ACTUAL items
731
	 *
732
	 * @return EE_Line_Item[]
733
	 * @throws \EE_Error
734
	 */
735
	public function items_purchased() {
736
		return $this->line_items( array( array( 'LIN_type' => EEM_Line_Item::type_line_item ) ) );
737
	}
738
739
740
741
	/**
742
	 * Wrapper for _add_relation_to
743
	 *
744
	 * @param EE_Line_Item $line_item
745
	 * @return EE_Base_Class the relation was added to
746
	 * @throws \EE_Error
747
	 */
748
	public function add_line_item( EE_Line_Item $line_item ) {
749
		return $this->_add_relation_to( $line_item, 'Line_Item' );
750
	}
751
752
753
754
	/**
755
	 * Gets ALL the line items related to this transaction (unstructured)
756
	 *
757
	 * @param array $query_params
758
	 * @return EE_Line_Item[]
759
	 * @throws \EE_Error
760
	 */
761
	public function line_items( $query_params = array() ) {
762
		return $this->get_many_related( 'Line_Item', $query_params );
763
	}
764
765
766
767
	/**
768
	 * Gets all the line items which are taxes on the total
769
	 *
770
	 * @return EE_Line_Item[]
771
	 * @throws \EE_Error
772
	 */
773
	public function tax_items() {
774
		return $this->line_items( array( array( 'LIN_type' => EEM_Line_Item::type_tax ) ) );
775
	}
776
777
778
779
	/**
780
	 * Gets the total line item (which is a parent of all other related line items,
781
	 * meaning it takes them all into account on its total)
782
	 *
783
	 * @return EE_Line_Item
784
	 * @throws \EE_Error
785
	 */
786
	public function total_line_item() {
787
		$item =  $this->get_first_related( 'Line_Item', array( array( 'LIN_type' => EEM_Line_Item::type_total ) ) );
788
		if( ! $item ){
789
			EE_Registry::instance()->load_helper( 'Line_Item' );
790
			$item = EEH_Line_Item::create_total_line_item( $this );
791
		}
792
		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...
793
	}
794
795
796
797
	/**
798
	 * Returns the total amount of tax on this transaction
799
	 * (assumes there's only one tax subtotal line item)
800
	 *
801
	 * @return float
802
	 * @throws \EE_Error
803
	 */
804
	public function tax_total() {
805
		$tax_line_item = $this->tax_total_line_item();
806
		if ( $tax_line_item ) {
807
			return (float)$tax_line_item->total();
808
		} else {
809
			return (float)0;
810
		}
811
	}
812
813
814
815
	/**
816
	 * Gets the tax subtotal line item (assumes there's only one)
817
	 *
818
	 * @return EE_Line_Item
819
	 * @throws \EE_Error
820
	 */
821
	public function tax_total_line_item() {
822
		return EEH_Line_Item::get_taxes_subtotal( $this->total_line_item() );
823
	}
824
825
826
827
	/**
828
	 *  Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
829
	 *
830
	 * @return EE_Form_Section_Proper
831
	 * @throws \EE_Error
832
	 */
833
	public function billing_info(){
834
		$payment_method = $this->payment_method();
835
		if ( !$payment_method){
836
			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__);
837
			return false;
838
		}
839
		$primary_reg = $this->primary_registration();
840
		if ( ! $primary_reg ) {
841
			EE_Error::add_error( __( "Cannot get billing info for gateway %s on transaction because no primary registration exists", "event_espresso" ), __FILE__, __FUNCTION__, __LINE__ );
842
			return FALSE;
843
		}
844
		$attendee = $primary_reg->attendee();
845
		if ( ! $attendee ) {
846
			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__ );
847
			return FALSE;
848
		}
849
		return $attendee->billing_info_for_payment_method($payment_method);
850
	}
851
852
853
854
	/**
855
	 * Gets PMD_ID
856
	 *
857
	 * @return int
858
	 * @throws \EE_Error
859
	 */
860
	public function payment_method_ID() {
861
		return $this->get('PMD_ID');
862
	}
863
864
865
866
	/**
867
	 * Sets PMD_ID
868
	 *
869
	 * @param int $PMD_ID
870
	 * @return boolean
871
	 * @throws \EE_Error
872
	 */
873
	public function set_payment_method_ID($PMD_ID) {
874
		$this->set('PMD_ID', $PMD_ID);
875
	}
876
877
878
879
	/**
880
	 * Gets the last-used payment method on this transaction
881
	 * (we COULD just use the last-made payment, but some payment methods, namely
882
	 * offline ones, dont' create payments)
883
	 *
884
	 * @return EE_Payment_Method
885
	 * @throws \EE_Error
886
	 */
887
	public function payment_method(){
888
		$pm = $this->get_first_related('Payment_Method');
889
		if( $pm instanceof EE_Payment_Method ){
890
			return $pm;
891
		}else{
892
			$last_payment = $this->last_payment();
893
			if( $last_payment instanceof EE_Payment && $last_payment->payment_method() ){
894
				return $last_payment->payment_method();
895
			}else{
896
				return NULL;
897
			}
898
		}
899
	}
900
901
902
903
	/**
904
	 * Gets the last payment made
905
	 *
906
	 * @return EE_Payment
907
	 * @throws \EE_Error
908
	 */
909
	public function last_payment() {
910
		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...
911
	}
912
913
914
915
	/**
916
	 * Gets all the line items which are unrelated to tickets on this transaction
917
	 *
918
	 * @return EE_Line_Item[]
919
	 * @throws \EE_Error
920
	 */
921
	public function non_ticket_line_items(){
922
		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction( $this->ID() );
923
	}
924
925
926
927
928
}/* End of file EE_Transaction.class.php */
929
/* Location: includes/classes/EE_Transaction.class.php */
930