Completed
Branch FET/11450/reserved-instance-in... (8a8133)
by
unknown
125:36 queued 112:25
created

EEM_Transaction::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 43
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 34
nc 1
nop 1
dl 0
loc 43
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
use EventEspresso\core\services\loaders\LoaderFactory;
6
7
if ( ! defined('EVENT_ESPRESSO_VERSION')) {
8
    exit('No direct script access allowed');
9
}
10
require_once(EE_MODELS . 'EEM_Base.model.php');
11
12
/**
13
 *
14
 * Transaction Model
15
 *
16
 * @package            Event Espresso
17
 * @subpackage         includes/models/
18
 * @author             Brent Christensen
19
 *
20
 */
21
class EEM_Transaction extends EEM_Base
22
{
23
24
    // private instance of the Transaction object
25
    protected static $_instance;
26
27
    /**
28
     * Status ID(STS_ID on esp_status table) to indicate the transaction is complete,
29
     * but payment is pending. This is the state for transactions where payment is promised
30
     * from an offline gateway.
31
     */
32
    //	const open_status_code = 'TPN';
33
34
    /**
35
     * Status ID(STS_ID on esp_status table) to indicate the transaction failed,
36
     * either due to a technical reason (server or computer crash during registration),
37
     *  or some other reason that prevent the collection of any useful contact information from any of the registrants
38
     */
39
    const failed_status_code = 'TFL';
40
41
    /**
42
     * Status ID(STS_ID on esp_status table) to indicate the transaction was abandoned,
43
     * either due to a technical reason (server or computer crash during registration),
44
     * or due to an abandoned cart after registrant chose not to complete the registration process
45
     * HOWEVER...
46
     * an abandoned TXN differs from a failed TXN in that it was able to capture contact information for at least one
47
     * registrant
48
     */
49
    const abandoned_status_code = 'TAB';
50
51
    /**
52
     * Status ID(STS_ID on esp_status table) to indicate an incomplete transaction,
53
     * meaning that monies are still owing: TXN_paid < TXN_total
54
     */
55
    const incomplete_status_code = 'TIN';
56
57
    /**
58
     * Status ID (STS_ID on esp_status table) to indicate a complete transaction.
59
     * meaning that NO monies are owing: TXN_paid == TXN_total
60
     */
61
    const complete_status_code = 'TCM';
62
63
    /**
64
     *  Status ID(STS_ID on esp_status table) to indicate the transaction is overpaid.
65
     *  This is the same as complete, but site admins actually owe clients the moneys!  TXN_paid > TXN_total
66
     */
67
    const overpaid_status_code = 'TOP';
68
69
70
    /**
71
     *    private constructor to prevent direct creation
72
     *
73
     * @Constructor
74
     * @access protected
75
     *
76
     * @param string $timezone string representing the timezone we want to set for returned Date Time Strings (and any
77
     *                         incoming timezone data that gets saved). Note this just sends the timezone info to the
78
     *                         date time model field objects.  Default is NULL (and will be assumed using the set
79
     *                         timezone in the 'timezone_string' wp option)
80
     *
81
     * @return EEM_Transaction
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
82
     * @throws \EE_Error
83
     */
84
    protected function __construct($timezone)
85
    {
86
        $this->singular_item = __('Transaction', 'event_espresso');
87
        $this->plural_item   = __('Transactions', 'event_espresso');
88
89
        $this->_tables                 = array(
90
            'TransactionTable' => new EE_Primary_Table('esp_transaction', 'TXN_ID')
91
        );
92
        $this->_fields                 = array(
93
            'TransactionTable' => array(
94
                'TXN_ID'           => new EE_Primary_Key_Int_Field('TXN_ID', __('Transaction ID', 'event_espresso')),
95
                'TXN_timestamp'    => new EE_Datetime_Field('TXN_timestamp',
96
                    __('date when transaction was created', 'event_espresso'), false, EE_Datetime_Field::now,
97
                    $timezone),
98
                'TXN_total'        => new EE_Money_Field('TXN_total',
99
                    __('Total value of Transaction', 'event_espresso'), false, 0),
100
                'TXN_paid'         => new EE_Money_Field('TXN_paid',
101
                    __('Amount paid towards transaction to date', 'event_espresso'), false, 0),
102
                'STS_ID'           => new EE_Foreign_Key_String_Field('STS_ID', __('Status ID', 'event_espresso'),
103
                    false, EEM_Transaction::failed_status_code, 'Status'),
104
                'TXN_session_data' => new EE_Serialized_Text_Field('TXN_session_data',
105
                    __('Serialized session data', 'event_espresso'), true, ''),
106
                'TXN_hash_salt'    => new EE_Plain_Text_Field('TXN_hash_salt',
107
                    __('Transaction Hash Salt', 'event_espresso'), true, ''),
108
                'PMD_ID'           => new EE_Foreign_Key_Int_Field('PMD_ID',
109
                    __("Last Used Payment Method", 'event_espresso'), true, null, 'Payment_Method'),
110
                'TXN_reg_steps'    => new EE_Serialized_Text_Field('TXN_reg_steps',
111
                    __('Registration Steps', 'event_espresso'), false, array()),
112
            )
113
        );
114
        $this->_model_relations        = array(
115
            'Registration'   => new EE_Has_Many_Relation(),
116
            'Payment'        => new EE_Has_Many_Relation(),
117
            'Status'         => new EE_Belongs_To_Relation(),
118
            'Line_Item'      => new EE_Has_Many_Relation(false),
119
            //you can delete a transaction without needing to delete its line items
120
            'Payment_Method' => new EE_Belongs_To_Relation(),
121
            'Message'        => new EE_Has_Many_Relation()
122
        );
123
        $this->_model_chain_to_wp_user = 'Registration.Event';
124
        parent::__construct($timezone);
125
126
    }
127
128
129
    /**
130
     *    txn_status_array
131
     * get list of transaction statuses
132
     *
133
     * @access public
134
     * @return array
135
     */
136
    public static function txn_status_array()
137
    {
138
        return apply_filters(
139
            'FHEE__EEM_Transaction__txn_status_array',
140
            array(
141
                EEM_Transaction::overpaid_status_code,
142
                EEM_Transaction::complete_status_code,
143
                EEM_Transaction::incomplete_status_code,
144
                EEM_Transaction::abandoned_status_code,
145
                EEM_Transaction::failed_status_code,
146
            )
147
        );
148
    }
149
150
    /**
151
     *        get the revenue per day  for the Transaction Admin page Reports Tab
152
     *
153
     * @access        public
154
     *
155
     * @param string $period
156
     *
157
     * @return \stdClass[]
158
     */
159
    public function get_revenue_per_day_report($period = '-1 month')
160
    {
161
        $sql_date = $this->convert_datetime_for_query('TXN_timestamp', date('Y-m-d H:i:s', strtotime($period)),
162
            'Y-m-d H:i:s', 'UTC');
163
164
        $query_interval = EEH_DTT_Helper::get_sql_query_interval_for_offset($this->get_timezone(), 'TXN_timestamp');
165
166
        return $this->_get_all_wpdb_results(
167
            array(
168
                array(
169
                    'TXN_timestamp' => array('>=', $sql_date)
170
                ),
171
                'group_by' => 'txnDate',
172
                'order_by' => array('TXN_timestamp' => 'ASC')
173
            ),
174
            OBJECT,
175
            array(
176
                'txnDate' => array('DATE(' . $query_interval . ')', '%s'),
177
                'revenue' => array('SUM(TransactionTable.TXN_paid)', '%d')
178
            )
179
        );
180
    }
181
182
183
    /**
184
     *        get the revenue per event  for the Transaction Admin page Reports Tab
185
     *
186
     * @access        public
187
     *
188
     * @param string $period
189
     *
190
     * @throws \EE_Error
191
     * @return mixed
192
     */
193
    public function get_revenue_per_event_report($period = '-1 month')
194
    {
195
        global $wpdb;
196
        $transaction_table          = $wpdb->prefix . 'esp_transaction';
197
        $registration_table         = $wpdb->prefix . 'esp_registration';
198
        $registration_payment_table = $wpdb->prefix . 'esp_registration_payment';
199
        $event_table                = $wpdb->posts;
200
        $payment_table              = $wpdb->prefix . 'esp_payment';
201
        $sql_date                   = date('Y-m-d H:i:s', strtotime($period));
202
        $approved_payment_status    = EEM_Payment::status_id_approved;
203
        $extra_event_on_join        = '';
204
        //exclude events not authored by user if permissions in effect
205
        if ( ! EE_Registry::instance()->CAP->current_user_can('ee_read_others_registrations', 'reg_per_event_report')) {
206
            $extra_event_on_join = ' AND Event.post_author = ' . get_current_user_id();
207
        }
208
209
        return $wpdb->get_results(
210
            "SELECT
211
			Transaction_Event.event_name AS event_name,
212
			SUM(Transaction_Event.paid) AS revenue
213
			FROM
214
				(
215
				    SELECT
216
				        DISTINCT(Registration.REG_ID),
217
                        Event.post_title AS event_name,
218
                        Registration_Payment.RPY_amount AS paid
219
                    FROM
220
                        $registration_payment_table as Registration_Payment
221
                    JOIN
222
                        $registration_table as Registration
223
                            ON Registration.REG_ID = Registration_Payment.REG_ID
224
                    JOIN
225
                        $transaction_table as TransactionTable
226
                            ON Registration.TXN_ID = TransactionTable.TXN_ID
227
                    JOIN
228
                        $payment_table as Payment
229
                            ON Payment.TXN_ID = Registration.TXN_ID
230
                            AND Payment.PAY_timestamp > '$sql_date'
231
                            AND Payment.STS_ID = '$approved_payment_status'
232
                    JOIN
233
                        $event_table AS Event
234
                            ON Registration.EVT_ID = Event.ID
235
					$extra_event_on_join
236
				) AS Transaction_Event
237
			GROUP BY event_name",
238
            OBJECT
239
        );
240
    }
241
242
243
    /**
244
     * Gets the current transaction given the reg_url_link, or assumes the reg_url_link is in the
245
     * $_REQUEST global variable. Either way, tries to find the current transaction (through
246
     * the registration pointed to by reg_url_link), if not returns null
247
     *
248
     * @param string $reg_url_link
249
     *
250
     * @return EE_Transaction
251
     */
252
    public function get_transaction_from_reg_url_link($reg_url_link = '')
253
    {
254
        return $this->get_one(array(
255
            array(
256
                'Registration.REG_url_link' => ! empty($reg_url_link) ? $reg_url_link : EE_Registry::instance()->REQ->get('e_reg_url_link',
257
                    '')
258
            )
259
        ));
260
    }
261
262
263
    /**
264
     * Updates the provided EE_Transaction with all the applicable payments
265
     * (or fetch the EE_Transaction from its ID)
266
     *
267
     * @deprecated
268
     *
269
     * @param EE_Transaction|int $transaction_obj_or_id
270
     * @param boolean            $save_txn whether or not to save the transaction during this function call
271
     *
272
     * @return boolean
273
     * @throws \EE_Error
274
     */
275 View Code Duplication
    public function update_based_on_payments($transaction_obj_or_id, $save_txn = true)
276
    {
277
        EE_Error::doing_it_wrong(
278
            __CLASS__ . '::' . __FUNCTION__,
279
            sprintf(__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
280
                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'),
281
            '4.6.0'
282
        );
283
        /** @type EE_Transaction_Processor $transaction_processor */
284
        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
285
286
        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment(
287
            $this->ensure_is_obj($transaction_obj_or_id)
288
        );
289
    }
290
291
    /**
292
     * Deletes "junk" transactions that were probably added by bots. There might be TONS
293
     * of these, so we are very careful to NOT select (which the models do even when deleting),
294
     * and so we only use wpdb directly and only do minimal joins.
295
     * Transactions are considered "junk" if they're failed for longer than a week.
296
     * Also, there is an extra check for payments related to the transaction, because if a transaction has a payment on
297
     * it, it's probably not junk (regardless of what status it has).
298
     * The downside to this approach is that is addons are listening for object deletions
299
     * on EEM_Base::delete() they won't be notified of this.  However, there is an action that plugins can hook into
300
     * to catch these types of deletions.
301
     *
302
     * @global WPDB $wpdb
303
     * @return mixed
304
     */
305
    public function delete_junk_transactions()
306
    {
307
        /** @type WPDB $wpdb */
308
        global $wpdb;
309
        $deleted             = false;
310
        $time_to_leave_alone = apply_filters(
311
            'FHEE__EEM_Transaction__delete_junk_transactions__time_to_leave_alone'
312
            , WEEK_IN_SECONDS
313
        );
314
315
316
        /**
317
         * This allows code to filter the query arguments used for retrieving the transaction IDs to delete.
318
         * Useful for plugins that want to exclude transactions matching certain query parameters.
319
         * The query parameters should be in the format accepted by the EEM_Base model queries.
320
         */
321
        $ids_query = apply_filters(
322
            'FHEE__EEM_Transaction__delete_junk_transactions__initial_query_args',
323
            array(
324
                0 => array(
325
                    'STS_ID'        => EEM_Transaction::failed_status_code,
326
                    'Payment.PAY_ID' => array( 'IS NULL' ),
327
                    'TXN_timestamp' => array('<', time() - $time_to_leave_alone)
328
                )
329
            ),
330
            $time_to_leave_alone
331
        );
332
333
334
        /**
335
         * This filter is for when code needs to filter the list of transaction ids that represent transactions
336
         * about to be deleted based on some other criteria that isn't easily done via the query args filter.
337
         */
338
        $txn_ids = apply_filters(
339
            'FHEE__EEM_Transaction__delete_junk_transactions__transaction_ids_to_delete',
340
            EEM_Transaction::instance()->get_col($ids_query, 'TXN_ID'),
341
            $time_to_leave_alone
342
        );
343
        //now that we have the ids to delete
344 View Code Duplication
        if (! empty($txn_ids) && is_array($txn_ids)) {
345
            // first, make sure these TXN's are removed the "ee_locked_transactions" array
346
            EEM_Transaction::unset_locked_transactions($txn_ids);
347
            // let's get deletin'...
348
            // Why no wpdb->prepare?  Because the data is trusted.
349
            // We got the ids from the original query to get them FROM
350
            // the db (which is sanitized) so no need to prepare them again.
351
            $query   = '
352
				DELETE
353
				FROM ' . $this->table() . '
354
				WHERE
355
					TXN_ID IN ( ' . implode(",", $txn_ids) . ')';
356
            $deleted = $wpdb->query($query);
357
        }
358
        if ($deleted) {
359
            /**
360
             * Allows code to do something after the transactions have been deleted.
361
             */
362
            do_action('AHEE__EEM_Transaction__delete_junk_transactions__successful_deletion', $txn_ids);
363
        }
364
365
        return $deleted;
366
    }
367
368
369
    /**
370
     * @param array $transaction_IDs
371
     *
372
     * @return bool
373
     */
374
    public static function unset_locked_transactions(array $transaction_IDs)
375
    {
376
        $locked_transactions = get_option('ee_locked_transactions', array());
377
        $update              = false;
378
        foreach ($transaction_IDs as $TXN_ID) {
379
            if (isset($locked_transactions[$TXN_ID])) {
380
                unset($locked_transactions[$TXN_ID]);
381
                $update = true;
382
            }
383
        }
384
        if ($update) {
385
            update_option('ee_locked_transactions', $locked_transactions);
386
        }
387
388
        return $update;
389
    }
390
391
392
393
    /**
394
     * returns an array of EE_Transaction objects whose timestamp is greater than
395
     * the current time minus the session lifespan, which defaults to 60 minutes
396
     *
397
     * @return EE_Base_Class[]|EE_Transaction[]
398
     * @throws EE_Error
399
     * @throws InvalidArgumentException
400
     * @throws InvalidDataTypeException
401
     * @throws InvalidInterfaceException
402
     */
403
    public function get_transactions_in_progress()
404
    {
405
        return $this->_get_transactions_in_progress();
406
    }
407
408
409
410
    /**
411
     * returns an array of EE_Transaction objects whose timestamp is less than
412
     * the current time minus the session lifespan, which defaults to 60 minutes
413
     *
414
     * @return EE_Base_Class[]|EE_Transaction[]
415
     * @throws EE_Error
416
     * @throws InvalidArgumentException
417
     * @throws InvalidDataTypeException
418
     * @throws InvalidInterfaceException
419
     */
420
    public function get_transactions_not_in_progress()
421
    {
422
        return $this->_get_transactions_in_progress('<=');
423
    }
424
425
426
427
    /**
428
     * @param string $comparison
429
     * @return EE_Base_Class[]|EE_Transaction[]
430
     * @throws EE_Error
431
     * @throws InvalidArgumentException
432
     * @throws InvalidDataTypeException
433
     * @throws InvalidInterfaceException
434
     */
435
    private function _get_transactions_in_progress($comparison = '>=')
436
    {
437
        $comparison = $comparison === '>=' || $comparison === '<='
438
            ? $comparison
439
            : '>=';
440
        /** @var EventEspresso\core\domain\values\session\SessionLifespan $session_lifespan */
441
        $session_lifespan = LoaderFactory::getLoader()->getShared(
442
            'EventEspresso\core\domain\values\session\SessionLifespan'
443
        );
444
        return $this->get_all(
445
            array(
446
                array(
447
                    'TXN_timestamp' => array(
448
                        $comparison,
449
                        $session_lifespan->expiration()
450
                    ),
451
                    'STS_ID' => array(
452
                        '!=',
453
                        EEM_Transaction::complete_status_code
454
                    ),
455
                )
456
            )
457
        );
458
    }
459
460
461
}
462
// End of file EEM_Transaction.model.php
463
// Location: /includes/models/EEM_Transaction.model.php
464