Completed
Branch master (d3e57f)
by
unknown
52:16 queued 43:59
created

EE_Cron_Tasks::instance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
rs 10
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\Loader;
6
use EventEspresso\core\services\loaders\LoaderFactory;
7
8
/**
9
 * Class EE_Cron_Tasks
10
 *
11
 * @package               Event Espresso
12
 * @subpackage            core
13
 * @author                Brent Christensen
14
 */
15
class EE_Cron_Tasks extends EE_Base
16
{
17
18
    /**
19
     * WordPress doesn't allow duplicate crons within 10 minutes of the original,
20
     * so we'll set our retry time for just over 10 minutes to avoid that
21
     */
22
    const reschedule_timeout = 605;
23
24
25
    /**
26
     * @var EE_Cron_Tasks
27
     */
28
    private static $_instance;
29
30
31
    /**
32
     * @return EE_Cron_Tasks
33
     * @throws ReflectionException
34
     * @throws EE_Error
35
     * @throws InvalidArgumentException
36
     * @throws InvalidInterfaceException
37
     * @throws InvalidDataTypeException
38
     */
39
    public static function instance()
40
    {
41
        if (! self::$_instance instanceof EE_Cron_Tasks) {
42
            self::$_instance = new self();
43
        }
44
        return self::$_instance;
45
    }
46
47
48
    /**
49
     * @access private
50
     * @throws InvalidDataTypeException
51
     * @throws InvalidInterfaceException
52
     * @throws InvalidArgumentException
53
     * @throws EE_Error
54
     * @throws ReflectionException
55
     */
56
    private function __construct()
57
    {
58
        do_action('AHEE_log', __CLASS__, __FUNCTION__);
59
        // verify that WP Cron is enabled
60
        if (defined('DISABLE_WP_CRON')
61
            && DISABLE_WP_CRON
62
            && is_admin()
63
            && ! get_option('ee_disabled_wp_cron_check')
64
        ) {
65
            /**
66
             * This needs to be delayed until after the config is loaded because EE_Cron_Tasks is constructed before
67
             * config is loaded.
68
             * This is intentionally using a anonymous function so that its not easily de-registered.  Client code
69
             * wanting to not have this functionality can just register its own action at a priority after this one to
70
             * reverse any changes.
71
             */
72
            add_action(
73
                'AHEE__EE_System__load_core_configuration__complete',
74
                function () {
75
                    EE_Registry::instance()->NET_CFG->core->do_messages_on_same_request = true;
76
                    EE_Registry::instance()->NET_CFG->update_config(true, false);
77
                    add_option('ee_disabled_wp_cron_check', 1, '', false);
78
                }
79
            );
80
        }
81
        // UPDATE TRANSACTION WITH PAYMENT
82
        add_action(
83
            'AHEE__EE_Cron_Tasks__update_transaction_with_payment_2',
84
            array('EE_Cron_Tasks', 'setup_update_for_transaction_with_payment'),
85
            10,
86
            2
87
        );
88
        // ABANDONED / EXPIRED TRANSACTION CHECK
89
        add_action(
90
            'AHEE__EE_Cron_Tasks__expired_transaction_check',
91
            array('EE_Cron_Tasks', 'expired_transaction_check'),
92
            10,
93
            1
94
        );
95
        // CLEAN OUT JUNK TRANSACTIONS AND RELATED DATA
96
        add_action(
97
            'AHEE__EE_Cron_Tasks__clean_up_junk_transactions',
98
            array('EE_Cron_Tasks', 'clean_out_junk_transactions')
99
        );
100
        // logging
101
        add_action(
102
            'AHEE__EE_System__load_core_configuration__complete',
103
            array('EE_Cron_Tasks', 'log_scheduled_ee_crons')
104
        );
105
        EE_Registry::instance()->load_lib('Messages_Scheduler');
106
        // clean out old gateway logs
107
        add_action(
108
            'AHEE_EE_Cron_Tasks__clean_out_old_gateway_logs',
109
            array('EE_Cron_Tasks', 'clean_out_old_gateway_logs')
110
        );
111
    }
112
113
114
    /**
115
     * @access protected
116
     * @return void
117
     */
118
    public static function log_scheduled_ee_crons()
119
    {
120
        $ee_crons = array(
121
            'AHEE__EE_Cron_Tasks__update_transaction_with_payment',
122
            'AHEE__EE_Cron_Tasks__finalize_abandoned_transactions',
123
            'AHEE__EE_Cron_Tasks__clean_up_junk_transactions',
124
        );
125
        $crons = (array) get_option('cron');
126
        if (! is_array($crons)) {
127
            return;
128
        }
129
        foreach ($crons as $timestamp => $cron) {
130
            /** @var array[] $cron */
131
            foreach ($ee_crons as $ee_cron) {
132
                if (isset($cron[ $ee_cron ]) && is_array($cron[ $ee_cron ])) {
133
                    do_action('AHEE_log', __CLASS__, __FUNCTION__, $ee_cron, 'scheduled EE cron');
134
                    foreach ($cron[ $ee_cron ] as $ee_cron_details) {
135
                        if (! empty($ee_cron_details['args'])) {
136
                            do_action(
137
                                'AHEE_log',
138
                                __CLASS__,
139
                                __FUNCTION__,
140
                                print_r($ee_cron_details['args'], true),
141
                                "{$ee_cron} args"
142
                            );
143
                        }
144
                    }
145
                }
146
            }
147
        }
148
    }
149
150
151
    /**
152
     * reschedule_cron_for_transactions_if_maintenance_mode
153
     * if Maintenance Mode is active, this will reschedule a cron to run again in 10 minutes
154
     *
155
     * @param string $cron_task
156
     * @param array  $TXN_IDs
157
     * @return bool
158
     * @throws DomainException
159
     */
160
    public static function reschedule_cron_for_transactions_if_maintenance_mode($cron_task, array $TXN_IDs)
161
    {
162
        if (! method_exists('EE_Cron_Tasks', $cron_task)) {
163
            throw new DomainException(
164
                sprintf(
165
                    __('"%1$s" is not valid method on EE_Cron_Tasks.', 'event_espresso'),
166
                    $cron_task
167
                )
168
            );
169
        }
170
        // reschedule the cron if we can't hit the db right now
171
        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
172
            foreach ($TXN_IDs as $TXN_ID => $additional_vars) {
173
                // ensure $additional_vars is an array
174
                $additional_vars = is_array($additional_vars) ? $additional_vars : array($additional_vars);
175
                // reset cron job for the TXN
176
                call_user_func_array(
177
                    array('EE_Cron_Tasks', $cron_task),
178
                    array_merge(
179
                        array(
180
                            time() + (10 * MINUTE_IN_SECONDS),
181
                            $TXN_ID,
182
                        ),
183
                        $additional_vars
184
                    )
185
                );
186
            }
187
            return true;
188
        }
189
        return false;
190
    }
191
192
193
194
195
    /****************  UPDATE TRANSACTION WITH PAYMENT ****************/
196
197
198
    /**
199
     * array of TXN IDs and the payment
200
     *
201
     * @var array
202
     */
203
    protected static $_update_transactions_with_payment = array();
204
205
206
    /**
207
     * schedule_update_transaction_with_payment
208
     * sets a wp_schedule_single_event() for updating any TXNs that may
209
     * require updating due to recently received payments
210
     *
211
     * @param int $timestamp
212
     * @param int $TXN_ID
213
     * @param int $PAY_ID
214
     */
215
    public static function schedule_update_transaction_with_payment(
216
        $timestamp,
217
        $TXN_ID,
218
        $PAY_ID
219
    ) {
220
        do_action('AHEE_log', __CLASS__, __FUNCTION__);
221
        // validate $TXN_ID and $timestamp
222
        $TXN_ID = absint($TXN_ID);
223
        $timestamp = absint($timestamp);
224
        if ($TXN_ID && $timestamp) {
225
            wp_schedule_single_event(
226
                $timestamp,
227
                'AHEE__EE_Cron_Tasks__update_transaction_with_payment_2',
228
                array($TXN_ID, $PAY_ID)
229
            );
230
        }
231
    }
232
233
234
    /**
235
     * setup_update_for_transaction_with_payment
236
     * this is the callback for the action hook:
237
     * 'AHEE__EE_Cron_Tasks__update_transaction_with_payment'
238
     * which is setup by EE_Cron_Tasks::schedule_update_transaction_with_payment().
239
     * The passed TXN_ID and associated payment gets added to an array, and then
240
     * the EE_Cron_Tasks::update_transaction_with_payment() function is hooked into
241
     * 'shutdown' which will actually handle the processing of any
242
     * transactions requiring updating, because doing so now would be too early
243
     * and the required resources may not be available
244
     *
245
     * @param int $TXN_ID
246
     * @param int $PAY_ID
247
     */
248
    public static function setup_update_for_transaction_with_payment($TXN_ID = 0, $PAY_ID = 0)
249
    {
250
        do_action('AHEE_log', __CLASS__, __FUNCTION__, $TXN_ID, '$TXN_ID');
251
        if (absint($TXN_ID)) {
252
            self::$_update_transactions_with_payment[ $TXN_ID ] = $PAY_ID;
253
            add_action(
254
                'shutdown',
255
                array('EE_Cron_Tasks', 'update_transaction_with_payment'),
256
                5
257
            );
258
        }
259
    }
260
261
262
    /**
263
     * update_transaction_with_payment
264
     * loops through the self::$_abandoned_transactions array
265
     * and attempts to finalize any TXNs that have not been completed
266
     * but have had their sessions expired, most likely due to a user not
267
     * returning from an off-site payment gateway
268
     *
269
     * @throws EE_Error
270
     * @throws DomainException
271
     * @throws InvalidDataTypeException
272
     * @throws InvalidInterfaceException
273
     * @throws InvalidArgumentException
274
     * @throws ReflectionException
275
     * @throws RuntimeException
276
     */
277
    public static function update_transaction_with_payment()
278
    {
279
        do_action('AHEE_log', __CLASS__, __FUNCTION__);
280
        if (// are there any TXNs that need cleaning up ?
281
            empty(self::$_update_transactions_with_payment)
282
            // reschedule the cron if we can't hit the db right now
283
            || EE_Cron_Tasks::reschedule_cron_for_transactions_if_maintenance_mode(
284
                'schedule_update_transaction_with_payment',
285
                self::$_update_transactions_with_payment
286
            )
287
        ) {
288
            return;
289
        }
290
        /** @type EE_Payment_Processor $payment_processor */
291
        $payment_processor = EE_Registry::instance()->load_core('Payment_Processor');
292
        // set revisit flag for payment processor
293
        $payment_processor->set_revisit();
294
        // load EEM_Transaction
295
        EE_Registry::instance()->load_model('Transaction');
296
        foreach (self::$_update_transactions_with_payment as $TXN_ID => $PAY_ID) {
297
            // reschedule the cron if we can't hit the db right now
298
            if (! EE_Maintenance_Mode::instance()->models_can_query()) {
299
                // reset cron job for updating the TXN
300
                EE_Cron_Tasks::schedule_update_transaction_with_payment(
301
                    time() + EE_Cron_Tasks::reschedule_timeout,
302
                    $TXN_ID,
303
                    $PAY_ID
304
                );
305
                continue;
306
            }
307
            $transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
308
            $payment = EEM_Payment::instance()->get_one_by_ID($PAY_ID);
309
            // verify transaction
310
            if ($transaction instanceof EE_Transaction && $payment instanceof EE_Payment) {
311
                // now try to update the TXN with any payments
312
                $payment_processor->update_txn_based_on_payment($transaction, $payment, true, true);
313
            }
314
            unset(self::$_update_transactions_with_payment[ $TXN_ID ]);
315
        }
316
    }
317
318
319
320
    /************  END OF UPDATE TRANSACTION WITH PAYMENT  ************/
321
322
323
    /*****************  EXPIRED TRANSACTION CHECK *****************/
324
325
326
    /**
327
     * array of TXN IDs
328
     *
329
     * @var array
330
     */
331
    protected static $_expired_transactions = array();
332
333
334
    /**
335
     * schedule_expired_transaction_check
336
     * sets a wp_schedule_single_event() for following up on TXNs after their session has expired
337
     *
338
     * @param int $timestamp
339
     * @param int $TXN_ID
340
     */
341
    public static function schedule_expired_transaction_check(
342
        $timestamp,
343
        $TXN_ID
344
    ) {
345
        // validate $TXN_ID and $timestamp
346
        $TXN_ID = absint($TXN_ID);
347
        $timestamp = absint($timestamp);
348
        if ($TXN_ID && $timestamp) {
349
            wp_schedule_single_event(
350
                $timestamp,
351
                'AHEE__EE_Cron_Tasks__expired_transaction_check',
352
                array($TXN_ID)
353
            );
354
        }
355
    }
356
357
358
    /**
359
     * expired_transaction_check
360
     * this is the callback for the action hook:
361
     * 'AHEE__EE_Cron_Tasks__transaction_session_expiration_check'
362
     * which is utilized by wp_schedule_single_event()
363
     * in \EED_Single_Page_Checkout::_initialize_transaction().
364
     * The passed TXN_ID gets added to an array, and then the
365
     * process_expired_transactions() function is hooked into
366
     * 'AHEE__EE_System__core_loaded_and_ready' which will actually handle the
367
     * processing of any failed transactions, because doing so now would be
368
     * too early and the required resources may not be available
369
     *
370
     * @param int $TXN_ID
371
     */
372
    public static function expired_transaction_check($TXN_ID = 0)
373
    {
374
        if (absint($TXN_ID)) {
375
            self::$_expired_transactions[ $TXN_ID ] = $TXN_ID;
376
            add_action(
377
                'shutdown',
378
                array('EE_Cron_Tasks', 'process_expired_transactions'),
379
                5
380
            );
381
        }
382
    }
383
384
385
    /**
386
     * process_expired_transactions
387
     * loops through the self::$_expired_transactions array and processes any failed TXNs
388
     *
389
     * @throws EE_Error
390
     * @throws InvalidDataTypeException
391
     * @throws InvalidInterfaceException
392
     * @throws InvalidArgumentException
393
     * @throws ReflectionException
394
     * @throws DomainException
395
     * @throws RuntimeException
396
     */
397
    public static function process_expired_transactions()
398
    {
399
        if (// are there any TXNs that need cleaning up ?
400
            empty(self::$_expired_transactions)
401
            // reschedule the cron if we can't hit the db right now
402
            || EE_Cron_Tasks::reschedule_cron_for_transactions_if_maintenance_mode(
403
                'schedule_expired_transaction_check',
404
                self::$_expired_transactions
405
            )
406
        ) {
407
            return;
408
        }
409
        /** @type EE_Transaction_Processor $transaction_processor */
410
        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
411
        // set revisit flag for txn processor
412
        $transaction_processor->set_revisit();
413
        // load EEM_Transaction
414
        EE_Registry::instance()->load_model('Transaction');
415
        foreach (self::$_expired_transactions as $TXN_ID) {
416
            $transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
417
            // verify transaction and whether it is failed or not
418
            if ($transaction instanceof EE_Transaction) {
419
                switch ($transaction->status_ID()) {
420
                    // Completed TXNs
421
                    case EEM_Transaction::complete_status_code:
422
                        // Don't update the transaction/registrations if the Primary Registration is Not Approved.
423
                        $primary_registration = $transaction->primary_registration();
424
                        if ($primary_registration instanceof EE_Registration
425
                            && $primary_registration->status_ID() !== EEM_Registration::status_id_not_approved
426
                        ) {
427
                            /** @type EE_Transaction_Processor $transaction_processor */
428
                            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
429
                            $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment(
430
                                $transaction,
431
                                $transaction->last_payment()
432
                            );
433
                            do_action(
434
                                'AHEE__EE_Cron_Tasks__process_expired_transactions__completed_transaction',
435
                                $transaction
436
                            );
437
                        }
438
                        break;
439
                    // Overpaid TXNs
440
                    case EEM_Transaction::overpaid_status_code:
441
                        do_action(
442
                            'AHEE__EE_Cron_Tasks__process_expired_transactions__overpaid_transaction',
443
                            $transaction
444
                        );
445
                        break;
446
                    // Incomplete TXNs
447
                    case EEM_Transaction::incomplete_status_code:
448
                        do_action(
449
                            'AHEE__EE_Cron_Tasks__process_expired_transactions__incomplete_transaction',
450
                            $transaction
451
                        );
452
                        // todo : move business logic into EE_Transaction_Processor for finalizing abandoned transactions
453
                        break;
454
                    // Abandoned TXNs
455
                    case EEM_Transaction::abandoned_status_code:
456
                        // run hook before updating transaction, primarily so
457
                        // EED_Ticket_Sales_Monitor::process_abandoned_transactions() can release reserved tickets
458
                        do_action(
459
                            'AHEE__EE_Cron_Tasks__process_expired_transactions__abandoned_transaction',
460
                            $transaction
461
                        );
462
                        // don't finalize the TXN if it has already been completed
463
                        if ($transaction->all_reg_steps_completed() !== true) {
464
                            /** @type EE_Payment_Processor $payment_processor */
465
                            $payment_processor = EE_Registry::instance()->load_core('Payment_Processor');
466
                            // let's simulate an IPN here which will trigger any notifications that need to go out
467
                            $payment_processor->update_txn_based_on_payment(
468
                                $transaction,
469
                                $transaction->last_payment(),
470
                                true,
471
                                true
472
                            );
473
                        }
474
                        break;
475
                    // Failed TXNs
476
                    case EEM_Transaction::failed_status_code:
477
                        do_action(
478
                            'AHEE__EE_Cron_Tasks__process_expired_transactions__failed_transaction',
479
                            $transaction
480
                        );
481
                        // todo :
482
                        // perform garbage collection here and remove clean_out_junk_transactions()
483
                        // $registrations = $transaction->registrations();
484
                        // if (! empty($registrations)) {
485
                        //     foreach ($registrations as $registration) {
486
                        //         if ($registration instanceof EE_Registration) {
487
                        //             $delete_registration = true;
488
                        //             if ($registration->attendee() instanceof EE_Attendee) {
489
                        //                 $delete_registration = false;
490
                        //             }
491
                        //             if ($delete_registration) {
492
                        //                 $registration->delete_permanently();
493
                        //                 $registration->delete_related_permanently();
494
                        //             }
495
                        //         }
496
                        //     }
497
                        // }
498
                        break;
499
                }
500
            }
501
            unset(self::$_expired_transactions[ $TXN_ID ]);
502
        }
503
    }
504
505
506
507
    /*************  END OF EXPIRED TRANSACTION CHECK  *************/
508
509
510
    /************* START CLEAN UP BOT TRANSACTIONS **********************/
511
512
513
    /**
514
     * callback for 'AHEE__EE_Cron_Tasks__clean_up_junk_transactions'
515
     * which is setup during activation to run on an hourly cron
516
     *
517
     * @throws EE_Error
518
     * @throws InvalidArgumentException
519
     * @throws InvalidDataTypeException
520
     * @throws InvalidInterfaceException
521
     * @throws DomainException
522
     */
523
    public static function clean_out_junk_transactions()
524
    {
525
        if (EE_Maintenance_Mode::instance()->models_can_query()) {
526
            EED_Ticket_Sales_Monitor::reset_reservation_counts();
527
            EEM_Transaction::instance('')->delete_junk_transactions();
528
            EEM_Registration::instance('')->delete_registrations_with_no_transaction();
529
            EEM_Line_Item::instance('')->delete_line_items_with_no_transaction();
530
        }
531
    }
532
533
534
    /**
535
     * Deletes old gateway logs. After about a week we usually don't need them for debugging. But folks can filter that.
536
     *
537
     * @throws EE_Error
538
     * @throws InvalidDataTypeException
539
     * @throws InvalidInterfaceException
540
     * @throws InvalidArgumentException
541
     */
542
    public static function clean_out_old_gateway_logs()
543
    {
544
        if (EE_Maintenance_Mode::instance()->models_can_query()) {
545
            $reg_config = LoaderFactory::getLoader()->load('EE_Registration_Config');
546
            $time_diff_for_comparison = apply_filters(
547
                'FHEE__EE_Cron_Tasks__clean_out_old_gateway_logs__time_diff_for_comparison',
548
                '-' . $reg_config->gateway_log_lifespan
549
            );
550
            EEM_Change_Log::instance()->delete_gateway_logs_older_than(new DateTime($time_diff_for_comparison));
551
        }
552
    }
553
554
555
    /*****************  FINALIZE ABANDONED TRANSACTIONS *****************/
556
557
558
    /**
559
     * @var array
560
     */
561
    protected static $_abandoned_transactions = array();
562
563
564
    /**
565
     * @deprecated
566
     * @param int $timestamp
567
     * @param int $TXN_ID
568
     */
569
    public static function schedule_finalize_abandoned_transactions_check($timestamp, $TXN_ID)
570
    {
571
        EE_Cron_Tasks::schedule_expired_transaction_check($timestamp, $TXN_ID);
572
    }
573
574
575
    /**
576
     * @deprecated
577
     * @param int $TXN_ID
578
     */
579
    public static function check_for_abandoned_transactions($TXN_ID = 0)
580
    {
581
        EE_Cron_Tasks::expired_transaction_check($TXN_ID);
582
    }
583
584
585
    /**
586
     * @deprecated
587
     * @throws EE_Error
588
     * @throws DomainException
589
     * @throws InvalidDataTypeException
590
     * @throws InvalidInterfaceException
591
     * @throws InvalidArgumentException
592
     * @throws ReflectionException
593
     * @throws RuntimeException
594
     */
595
    public static function finalize_abandoned_transactions()
596
    {
597
        do_action('AHEE_log', __CLASS__, __FUNCTION__);
598
        if (// are there any TXNs that need cleaning up ?
599
            empty(self::$_abandoned_transactions)
600
            // reschedule the cron if we can't hit the db right now
601
            || EE_Cron_Tasks::reschedule_cron_for_transactions_if_maintenance_mode(
602
                'schedule_expired_transaction_check',
603
                self::$_abandoned_transactions
604
            )
605
        ) {
606
            return;
607
        }
608
        // combine our arrays of transaction IDs
609
        self::$_expired_transactions = self::$_abandoned_transactions + self::$_expired_transactions;
610
        // and deal with abandoned transactions here now...
611
        EE_Cron_Tasks::process_expired_transactions();
612
    }
613
614
615
    /*************  END OF FINALIZE ABANDONED TRANSACTIONS  *************/
616
}
617