Completed
Branch master (44537d)
by
unknown
14:30 queued 10:03
created
core/libraries/payment_methods/EE_Payment_Method_Manager.lib.php 2 patches
Indentation   +563 added lines, -563 removed lines patch added patch discarded remove patch
@@ -16,567 +16,567 @@
 block discarded – undo
16 16
  */
17 17
 class EE_Payment_Method_Manager implements ResettableInterface
18 18
 {
19
-    /**
20
-     * prefix added to all payment method capabilities names
21
-     */
22
-    const CAPABILITIES_PREFIX = 'ee_payment_method_';
23
-
24
-    private static ?EE_Payment_Method_Manager $_instance = null;
25
-
26
-    /**
27
-     * @var boolean
28
-     */
29
-    protected bool $payment_method_caps_initialized = false;
30
-
31
-    /**
32
-     * @var string[] keys are class names without 'EE_PMT_', values are their filepaths
33
-     */
34
-    protected array $_payment_method_types = [];
35
-
36
-    /**
37
-     * @var EE_PMT_Base[]
38
-     */
39
-    protected array $payment_method_objects = [];
40
-
41
-
42
-    /**
43
-     * EE_Payment_Method_Manager constructor.
44
-     *
45
-     * @throws DomainException
46
-     */
47
-    public function __construct()
48
-    {
49
-        // if in admin lets ensure caps are set.
50
-        if (is_admin()) {
51
-            $this->_register_payment_methods();
52
-            // add PM caps when EE_Capabilities is initialized
53
-            add_action(
54
-                'AHEE__EE_Capabilities__init_caps__before_initialization',
55
-                [$this, 'initializePaymentMethodCaps']
56
-            );
57
-            // plus any time they get reset
58
-            add_filter(
59
-                'FHEE__EE_Capabilities__addCaps__capabilities_to_add',
60
-                [$this, 'addPaymentMethodCapsDuringReset']
61
-            );
62
-        }
63
-    }
64
-
65
-
66
-    /**
67
-     * @singleton method used to instantiate class object
68
-     * @return EE_Payment_Method_Manager instance
69
-     * @throws DomainException
70
-     * @throws EE_Error
71
-     * @throws ReflectionException
72
-     */
73
-    public static function instance(): EE_Payment_Method_Manager
74
-    {
75
-        // check if class object is instantiated, and instantiated properly
76
-        if (! self::$_instance instanceof EE_Payment_Method_Manager) {
77
-            EE_Registry::instance()->load_lib('PMT_Base');
78
-            self::$_instance = new self();
79
-        }
80
-        return self::$_instance;
81
-    }
82
-
83
-
84
-    /**
85
-     * Resets the instance and returns a new one
86
-     *
87
-     * @return EE_Payment_Method_Manager
88
-     * @throws DomainException
89
-     * @throws EE_Error
90
-     * @throws ReflectionException
91
-     */
92
-    public static function reset(): EE_Payment_Method_Manager
93
-    {
94
-        self::$_instance = null;
95
-        return self::instance();
96
-    }
97
-
98
-
99
-    /**
100
-     * If necessary, re-register payment methods
101
-     *
102
-     * @param boolean $force_recheck whether to recheck for payment method types,
103
-     *                               or just re-use the PMTs we found last time we checked during this request (if
104
-     *                               we have not yet checked during this request, then we need to check anyways)
105
-     */
106
-    public function maybe_register_payment_methods(bool $force_recheck = false)
107
-    {
108
-        if (! $this->_payment_method_types || $force_recheck) {
109
-            $this->_register_payment_methods();
110
-        }
111
-    }
112
-
113
-
114
-    /**
115
-     * register_payment_methods
116
-     *
117
-     * @return array
118
-     */
119
-    protected function _register_payment_methods(): array
120
-    {
121
-        // grab list of installed modules
122
-        $pm_to_register = glob(EE_PAYMENT_METHODS . '*', GLOB_ONLYDIR);
123
-        // filter list of modules to register
124
-        $pm_to_register = apply_filters(
125
-            'FHEE__EE_Payment_Method_Manager__register_payment_methods__payment_methods_to_register',
126
-            $pm_to_register
127
-        );
128
-        // remove any duplicates if that should happen for some reason
129
-        $pm_to_register = array_unique($pm_to_register);
130
-        // loop through folders
131
-        foreach ($pm_to_register as $pm_path) {
132
-            $this->register_payment_method($pm_path);
133
-        }
134
-        do_action('FHEE__EE_Payment_Method_Manager__register_payment_methods__registered_payment_methods');
135
-        // filter list of installed modules
136
-        // keep them organized alphabetically by the payment method type's name
137
-        ksort($this->_payment_method_types);
138
-        return apply_filters(
139
-            'FHEE__EE_Payment_Method_Manager__register_payment_methods__installed_payment_methods',
140
-            $this->_payment_method_types
141
-        );
142
-    }
143
-
144
-
145
-    /**
146
-     * register_payment_method- makes core aware of this payment method
147
-     *
148
-     * @param string $payment_method_path - full path up to and including payment method folder
149
-     * @return boolean
150
-     */
151
-    public function register_payment_method(string $payment_method_path = ''): bool
152
-    {
153
-        do_action('AHEE__EE_Payment_Method_Manager__register_payment_method__begin', $payment_method_path);
154
-        $module_ext = '.pm.php';
155
-        // make all separators match
156
-        $payment_method_path = rtrim(str_replace('/\\', '/', $payment_method_path), '/');
157
-        // grab and sanitize module name
158
-        $module_dir = basename($payment_method_path);
159
-        // create class name from module directory name
160
-        $module = str_replace(['_', ' '], [' ', '_'], $module_dir);
161
-        // add class prefix
162
-        $module_class = 'EE_PMT_' . $module;
163
-        // does the module exist ?
164
-        if (! is_readable($payment_method_path . '/' . $module_class . $module_ext)) {
165
-            $msg = sprintf(
166
-                esc_html__(
167
-                    'The requested %s payment method file could not be found or is not readable due to file permissions.',
168
-                    'event_espresso'
169
-                ),
170
-                $module
171
-            );
172
-            EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
173
-            return false;
174
-        }
175
-        // load the module class file
176
-        require_once($payment_method_path . '/' . $module_class . $module_ext);
177
-        // verify that class exists
178
-        if (! class_exists($module_class)) {
179
-            $msg = sprintf(
180
-                esc_html__('The requested %s module class does not exist.', 'event_espresso'),
181
-                $module_class
182
-            );
183
-            EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
184
-            return false;
185
-        }
186
-        // add to array of registered modules
187
-        $this->_payment_method_types[ $module ] = $payment_method_path . '/' . $module_class . $module_ext;
188
-        ksort($this->_payment_method_types);
189
-        return true;
190
-    }
191
-
192
-
193
-    /**
194
-     * Checks if a payment method has been registered, and if so includes it
195
-     *
196
-     * @param string  $payment_method_name like 'PayPal_Pro', (ie class name without the prefix 'EEPM_')
197
-     * @param boolean $force_recheck       whether to force re-checking for new payment method types
198
-     * @return boolean
199
-     */
200
-    public function payment_method_type_exists(string $payment_method_name, bool $force_recheck = false): bool
201
-    {
202
-        if (
203
-            $force_recheck
204
-            || empty($this->_payment_method_types)
205
-            || ! isset($this->_payment_method_types[ $payment_method_name ])
206
-        ) {
207
-            $this->maybe_register_payment_methods($force_recheck);
208
-        }
209
-        if (isset($this->_payment_method_types[ $payment_method_name ])) {
210
-            require_once($this->_payment_method_types[ $payment_method_name ]);
211
-            return true;
212
-        }
213
-        return false;
214
-    }
215
-
216
-
217
-    /**
218
-     * Returns all the class names of the various payment method types
219
-     *
220
-     * @param boolean $with_prefixes TRUE: get payment method type class names; false just their 'names'
221
-     *                               (what you'd find in wp_esp_payment_method.PMD_type)
222
-     * @param boolean $force_recheck whether to force re-checking for new payment method types
223
-     * @return array
224
-     */
225
-    public function payment_method_type_names(bool $with_prefixes = false, bool $force_recheck = false): array
226
-    {
227
-        $this->maybe_register_payment_methods($force_recheck);
228
-        if ($with_prefixes) {
229
-            $classnames      = array_keys($this->_payment_method_types);
230
-            $payment_methods = [];
231
-            foreach ($classnames as $classname) {
232
-                $payment_methods[] = $this->payment_method_class_from_type($classname);
233
-            }
234
-            return $payment_methods;
235
-        }
236
-        return array_keys($this->_payment_method_types);
237
-    }
238
-
239
-
240
-    /**
241
-     * Gets an object of each payment method type, none of which are bound to a
242
-     * payment method instance
243
-     *
244
-     * @param boolean $force_recheck whether to force re-checking for new payment method types
245
-     * @return EE_PMT_Base[]
246
-     */
247
-    public function payment_method_types(bool $force_recheck = false): array
248
-    {
249
-        if ($force_recheck || empty($this->payment_method_objects)) {
250
-            $this->maybe_register_payment_methods($force_recheck);
251
-            foreach ($this->payment_method_type_names(true) as $classname) {
252
-                if (! isset($this->payment_method_objects[ $classname ])) {
253
-                    $this->payment_method_objects[ $classname ] = new $classname();
254
-                }
255
-            }
256
-        }
257
-        return $this->payment_method_objects;
258
-    }
259
-
260
-
261
-    /**
262
-     * Changes the payment method's class name into the payment method type's name
263
-     * (as used on the payment method's table's PMD_type field)
264
-     *
265
-     * @param string $classname
266
-     * @return string
267
-     */
268
-    public function payment_method_type_sans_class_prefix(string $classname): string
269
-    {
270
-        return str_replace('EE_PMT_', '', $classname);
271
-    }
272
-
273
-
274
-    /**
275
-     * Does the opposite of payment-method_type_sans_prefix
276
-     *
277
-     * @param string $type
278
-     * @return string
279
-     */
280
-    public function payment_method_class_from_type(string $type): string
281
-    {
282
-        return 'EE_PMT_' . $type;
283
-    }
284
-
285
-
286
-    /**
287
-     * Activates a payment method of the given type.
288
-     *
289
-     * @param string $payment_method_type the PMT_type; for EE_PMT_Invoice this would be 'Invoice'
290
-     * @return EE_Payment_Method
291
-     * @throws InvalidDataTypeException
292
-     * @throws EE_Error
293
-     * @throws ReflectionException
294
-     */
295
-    public function activate_a_payment_method_of_type(string $payment_method_type): EE_Payment_Method
296
-    {
297
-        $this->maybe_register_payment_methods();
298
-        $payment_method = EEM_Payment_Method::instance()->get_one_of_type($payment_method_type);
299
-        if (! $payment_method instanceof EE_Payment_Method) {
300
-            $pm_type_class = $this->payment_method_class_from_type($payment_method_type);
301
-            if (class_exists($pm_type_class)) {
302
-                /** @var $pm_type_obj EE_PMT_Base */
303
-                $pm_type_obj    = new $pm_type_class();
304
-                $payment_method = EEM_Payment_Method::instance()->get_one_by_slug($pm_type_obj->system_name());
305
-                if (! $payment_method) {
306
-                    $payment_method = $this->create_payment_method_of_type($pm_type_obj);
307
-                }
308
-                $payment_method->set_type($payment_method_type);
309
-                $this->initialize_payment_method($payment_method);
310
-            } else {
311
-                throw new EE_Error(
312
-                    sprintf(
313
-                        esc_html__(
314
-                            'There is no payment method of type %1$s, so it could not be activated',
315
-                            'event_espresso'
316
-                        ),
317
-                        $pm_type_class
318
-                    )
319
-                );
320
-            }
321
-        }
322
-        $payment_method->set_active();
323
-        $payment_method->save();
324
-        /** @type EE_Message_Resource_Manager $message_resource_manager */
325
-        // if this was the invoice message type, make sure users can view their invoices
326
-        if (
327
-            $payment_method->type() === 'Invoice'
328
-            && (
329
-            ! EEH_MSG_Template::is_mt_active('invoice')
330
-            )
331
-        ) {
332
-            $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
333
-            /** @type EE_Message_Resource_Manager $message_resource_manager */
334
-            $message_resource_manager->ensure_message_type_is_active('invoice', 'html');
335
-            new PersistentAdminNotice(
336
-                'invoice_pm_requirements_notice',
337
-                sprintf(
338
-                    esc_html__(
339
-                        'The Invoice payment method has been activated. It requires the %1$sinvoice message%2$s type to be active, so it was automatically activated for you.',
340
-                        'event_espresso'
341
-                    ),
342
-                    '<a href="' . admin_url('admin.php?page=espresso_messages&action=settings') . '">',
343
-                    '</a>'
344
-                ),
345
-                true
346
-            );
347
-        }
348
-        return $payment_method;
349
-    }
350
-
351
-
352
-    /**
353
-     * Creates a payment method of the specified type. Does not save it.
354
-     *
355
-     * @param EE_PMT_Base $pm_type_obj
356
-     * @return EE_Payment_Method
357
-     * @throws EE_Error*@throws ReflectionException
358
-     * @throws ReflectionException
359
-     * @global WP_User    $current_user
360
-     */
361
-    public function create_payment_method_of_type(EE_PMT_Base $pm_type_obj): EE_Payment_Method
362
-    {
363
-        global $current_user;
364
-        return EE_Payment_Method::new_instance(
365
-            [
366
-                'PMD_type'       => $pm_type_obj->system_name(),
367
-                'PMD_name'       => $pm_type_obj->defaultFrontendName(),
368
-                'PMD_admin_name' => $pm_type_obj->pretty_name(),
369
-                'PMD_slug'       => $pm_type_obj->system_name(),// automatically converted to slug
370
-                'PMD_wp_user'    => $current_user->ID,
371
-                'PMD_order'      => EEM_Payment_Method::instance()->count(
372
-                    [['PMD_type' => ['!=', 'Admin_Only']]]
373
-                ) * 10,
374
-            ]
375
-        );
376
-    }
377
-
378
-
379
-    /**
380
-     * Sets the initial payment method properties (including extra meta)
381
-     *
382
-     * @param EE_Payment_Method $payment_method
383
-     * @return EE_Payment_Method
384
-     * @throws EE_Error
385
-     * @throws ReflectionException
386
-     */
387
-    public function initialize_payment_method(EE_Payment_Method $payment_method): EE_Payment_Method
388
-    {
389
-        $pm_type_obj = $payment_method->type_obj();
390
-        $payment_method->set_description($pm_type_obj->default_description());
391
-        if (! $payment_method->button_url()) {
392
-            $payment_method->set_button_url($pm_type_obj->default_button_url());
393
-        }
394
-        // now add setup its default extra meta properties
395
-        $extra_metas = $pm_type_obj->settings_form()->extra_meta_inputs();
396
-        if (! empty($extra_metas)) {
397
-            // verify the payment method has an ID before adding extra meta
398
-            if (! $payment_method->ID()) {
399
-                $payment_method->save();
400
-            }
401
-            foreach ($extra_metas as $meta_name => $input) {
402
-                $payment_method->update_extra_meta($meta_name, $input->raw_value());
403
-            }
404
-        }
405
-        return $payment_method;
406
-    }
407
-
408
-
409
-    /**
410
-     * Makes sure the payment method is related to the specified payment method
411
-     *
412
-     * @param EE_Payment_Method $payment_method
413
-     * @return EE_Payment_Method
414
-     * @deprecated in 4.9.40 because the currency payment method table is being deprecated
415
-     */
416
-    public function set_usable_currencies_on_payment_method(EE_Payment_Method $payment_method): EE_Payment_Method
417
-    {
418
-        EE_Error::doing_it_wrong(
419
-            'EE_Payment_Method_Manager::set_usable_currencies_on_payment_method',
420
-            esc_html__(
421
-                'We no longer define what currencies are usable by payment methods. Its not used nor efficient.',
422
-                'event_espresso'
423
-            ),
424
-            '4.9.40'
425
-        );
426
-        return $payment_method;
427
-    }
428
-
429
-
430
-    /**
431
-     * Deactivates a payment method of the given payment method slug.
432
-     *
433
-     * @param string $payment_method_slug The slug for the payment method to deactivate.
434
-     * @return int count of rows updated.
435
-     * @throws EE_Error
436
-     * @throws ReflectionException
437
-     */
438
-    public function deactivate_payment_method(string $payment_method_slug): int
439
-    {
440
-        EE_Log::instance()->log(
441
-            __FILE__,
442
-            __FUNCTION__,
443
-            sprintf(
444
-                esc_html__(
445
-                    'Payment method with slug %1$s is being deactivated by site admin',
446
-                    'event_espresso'
447
-                ),
448
-                $payment_method_slug
449
-            ),
450
-            'payment_method_change'
451
-        );
452
-        $count_updated = EEM_Payment_Method::instance()->update(
453
-            ['PMD_scope' => []],
454
-            [['PMD_slug' => $payment_method_slug]]
455
-        );
456
-        do_action(
457
-            'AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method',
458
-            $payment_method_slug,
459
-            $count_updated
460
-        );
461
-        return $count_updated;
462
-    }
463
-
464
-
465
-    /**
466
-     * initializes payment method access caps via EE_Capabilities::init_role_caps()
467
-     * upon EE_Payment_Method_Manager construction
468
-     *
469
-     * @throws EE_Error
470
-     * @throws DomainException
471
-     */
472
-    public function initializePaymentMethodCaps()
473
-    {
474
-        // don't do this twice
475
-        if ($this->payment_method_caps_initialized) {
476
-            return;
477
-        }
478
-        EE_Capabilities::instance()->addCaps(
479
-            $this->getPaymentMethodCaps()
480
-        );
481
-        $this->payment_method_caps_initialized = true;
482
-    }
483
-
484
-
485
-    /**
486
-     * array  of dynamic payment method access caps.
487
-     * at the time of writing, october 20 2014, these are the caps added:
488
-     *  ee_payment_method_admin_only
489
-     *  ee_payment_method_aim
490
-     *  ee_payment_method_bank
491
-     *  ee_payment_method_check
492
-     *  ee_payment_method_invoice
493
-     *  ee_payment_method_mijireh
494
-     *  ee_payment_method_paypal_pro
495
-     *  ee_payment_method_paypal_standard
496
-     * Any other payment methods added to core or via addons will also get
497
-     * their related capability automatically added too, so long as they are
498
-     * registered properly using EE_Register_Payment_Method::register()
499
-     *
500
-     * @return array
501
-     * @throws DomainException
502
-     */
503
-    protected function getPaymentMethodCaps(): array
504
-    {
505
-        $caps = [];
506
-        foreach ($this->payment_method_type_names() as $payment_method_name) {
507
-            $caps = $this->addPaymentMethodCap($payment_method_name, $caps);
508
-        }
509
-        return $caps;
510
-    }
511
-
512
-
513
-    /**
514
-     * @param string $payment_method_name
515
-     * @param array  $payment_method_caps
516
-     * @param string $role
517
-     * @return array
518
-     * @throws DomainException
519
-     */
520
-    public function addPaymentMethodCap(
521
-        string $payment_method_name,
522
-        array $payment_method_caps,
523
-        string $role = 'administrator'
524
-    ): array {
525
-        if (empty($payment_method_name)) {
526
-            throw new DomainException(
527
-                esc_html__(
528
-                    'The name of a payment method must be specified to add capabilities.',
529
-                    'event_espresso'
530
-                )
531
-            );
532
-        }
533
-        if (empty($role)) {
534
-            throw new DomainException(
535
-                sprintf(
536
-                    esc_html__(
537
-                        'No role was supplied while trying to add capabilities for the %1$s payment method.',
538
-                        'event_espresso'
539
-                    ),
540
-                    $payment_method_name
541
-                )
542
-            );
543
-        }
544
-        if (! isset($payment_method_caps[ $role ])) {
545
-            $payment_method_caps[ $role ] = [];
546
-        }
547
-        $payment_method_caps[ $role ][] = EE_Payment_Method_Manager::CAPABILITIES_PREFIX
548
-                                          . strtolower($payment_method_name);
549
-        return $payment_method_caps;
550
-    }
551
-
552
-
553
-    /**
554
-     * callback for FHEE__EE_Capabilities__init_role_caps__caps_map filter
555
-     * to add dynamic payment method access caps when capabilities are reset
556
-     * (or if that filter is called and PM caps are not already set)
557
-     *
558
-     * @param array $caps capabilities being filtered
559
-     * @param bool  $reset
560
-     * @return array
561
-     * @throws DomainException
562
-     */
563
-    public function addPaymentMethodCapsDuringReset(array $caps, bool $reset = false): array
564
-    {
565
-        if ($reset || ! $this->payment_method_caps_initialized) {
566
-            $this->payment_method_caps_initialized = true;
567
-            $caps                                  = array_merge_recursive($caps, $this->getPaymentMethodCaps());
568
-        }
569
-        return $caps;
570
-    }
571
-
572
-
573
-    /**
574
-     * @param $caps
575
-     * @return mixed
576
-     * @deprecated 4.9.42
577
-     */
578
-    public function add_payment_method_caps($caps)
579
-    {
580
-        return $caps;
581
-    }
19
+	/**
20
+	 * prefix added to all payment method capabilities names
21
+	 */
22
+	const CAPABILITIES_PREFIX = 'ee_payment_method_';
23
+
24
+	private static ?EE_Payment_Method_Manager $_instance = null;
25
+
26
+	/**
27
+	 * @var boolean
28
+	 */
29
+	protected bool $payment_method_caps_initialized = false;
30
+
31
+	/**
32
+	 * @var string[] keys are class names without 'EE_PMT_', values are their filepaths
33
+	 */
34
+	protected array $_payment_method_types = [];
35
+
36
+	/**
37
+	 * @var EE_PMT_Base[]
38
+	 */
39
+	protected array $payment_method_objects = [];
40
+
41
+
42
+	/**
43
+	 * EE_Payment_Method_Manager constructor.
44
+	 *
45
+	 * @throws DomainException
46
+	 */
47
+	public function __construct()
48
+	{
49
+		// if in admin lets ensure caps are set.
50
+		if (is_admin()) {
51
+			$this->_register_payment_methods();
52
+			// add PM caps when EE_Capabilities is initialized
53
+			add_action(
54
+				'AHEE__EE_Capabilities__init_caps__before_initialization',
55
+				[$this, 'initializePaymentMethodCaps']
56
+			);
57
+			// plus any time they get reset
58
+			add_filter(
59
+				'FHEE__EE_Capabilities__addCaps__capabilities_to_add',
60
+				[$this, 'addPaymentMethodCapsDuringReset']
61
+			);
62
+		}
63
+	}
64
+
65
+
66
+	/**
67
+	 * @singleton method used to instantiate class object
68
+	 * @return EE_Payment_Method_Manager instance
69
+	 * @throws DomainException
70
+	 * @throws EE_Error
71
+	 * @throws ReflectionException
72
+	 */
73
+	public static function instance(): EE_Payment_Method_Manager
74
+	{
75
+		// check if class object is instantiated, and instantiated properly
76
+		if (! self::$_instance instanceof EE_Payment_Method_Manager) {
77
+			EE_Registry::instance()->load_lib('PMT_Base');
78
+			self::$_instance = new self();
79
+		}
80
+		return self::$_instance;
81
+	}
82
+
83
+
84
+	/**
85
+	 * Resets the instance and returns a new one
86
+	 *
87
+	 * @return EE_Payment_Method_Manager
88
+	 * @throws DomainException
89
+	 * @throws EE_Error
90
+	 * @throws ReflectionException
91
+	 */
92
+	public static function reset(): EE_Payment_Method_Manager
93
+	{
94
+		self::$_instance = null;
95
+		return self::instance();
96
+	}
97
+
98
+
99
+	/**
100
+	 * If necessary, re-register payment methods
101
+	 *
102
+	 * @param boolean $force_recheck whether to recheck for payment method types,
103
+	 *                               or just re-use the PMTs we found last time we checked during this request (if
104
+	 *                               we have not yet checked during this request, then we need to check anyways)
105
+	 */
106
+	public function maybe_register_payment_methods(bool $force_recheck = false)
107
+	{
108
+		if (! $this->_payment_method_types || $force_recheck) {
109
+			$this->_register_payment_methods();
110
+		}
111
+	}
112
+
113
+
114
+	/**
115
+	 * register_payment_methods
116
+	 *
117
+	 * @return array
118
+	 */
119
+	protected function _register_payment_methods(): array
120
+	{
121
+		// grab list of installed modules
122
+		$pm_to_register = glob(EE_PAYMENT_METHODS . '*', GLOB_ONLYDIR);
123
+		// filter list of modules to register
124
+		$pm_to_register = apply_filters(
125
+			'FHEE__EE_Payment_Method_Manager__register_payment_methods__payment_methods_to_register',
126
+			$pm_to_register
127
+		);
128
+		// remove any duplicates if that should happen for some reason
129
+		$pm_to_register = array_unique($pm_to_register);
130
+		// loop through folders
131
+		foreach ($pm_to_register as $pm_path) {
132
+			$this->register_payment_method($pm_path);
133
+		}
134
+		do_action('FHEE__EE_Payment_Method_Manager__register_payment_methods__registered_payment_methods');
135
+		// filter list of installed modules
136
+		// keep them organized alphabetically by the payment method type's name
137
+		ksort($this->_payment_method_types);
138
+		return apply_filters(
139
+			'FHEE__EE_Payment_Method_Manager__register_payment_methods__installed_payment_methods',
140
+			$this->_payment_method_types
141
+		);
142
+	}
143
+
144
+
145
+	/**
146
+	 * register_payment_method- makes core aware of this payment method
147
+	 *
148
+	 * @param string $payment_method_path - full path up to and including payment method folder
149
+	 * @return boolean
150
+	 */
151
+	public function register_payment_method(string $payment_method_path = ''): bool
152
+	{
153
+		do_action('AHEE__EE_Payment_Method_Manager__register_payment_method__begin', $payment_method_path);
154
+		$module_ext = '.pm.php';
155
+		// make all separators match
156
+		$payment_method_path = rtrim(str_replace('/\\', '/', $payment_method_path), '/');
157
+		// grab and sanitize module name
158
+		$module_dir = basename($payment_method_path);
159
+		// create class name from module directory name
160
+		$module = str_replace(['_', ' '], [' ', '_'], $module_dir);
161
+		// add class prefix
162
+		$module_class = 'EE_PMT_' . $module;
163
+		// does the module exist ?
164
+		if (! is_readable($payment_method_path . '/' . $module_class . $module_ext)) {
165
+			$msg = sprintf(
166
+				esc_html__(
167
+					'The requested %s payment method file could not be found or is not readable due to file permissions.',
168
+					'event_espresso'
169
+				),
170
+				$module
171
+			);
172
+			EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
173
+			return false;
174
+		}
175
+		// load the module class file
176
+		require_once($payment_method_path . '/' . $module_class . $module_ext);
177
+		// verify that class exists
178
+		if (! class_exists($module_class)) {
179
+			$msg = sprintf(
180
+				esc_html__('The requested %s module class does not exist.', 'event_espresso'),
181
+				$module_class
182
+			);
183
+			EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
184
+			return false;
185
+		}
186
+		// add to array of registered modules
187
+		$this->_payment_method_types[ $module ] = $payment_method_path . '/' . $module_class . $module_ext;
188
+		ksort($this->_payment_method_types);
189
+		return true;
190
+	}
191
+
192
+
193
+	/**
194
+	 * Checks if a payment method has been registered, and if so includes it
195
+	 *
196
+	 * @param string  $payment_method_name like 'PayPal_Pro', (ie class name without the prefix 'EEPM_')
197
+	 * @param boolean $force_recheck       whether to force re-checking for new payment method types
198
+	 * @return boolean
199
+	 */
200
+	public function payment_method_type_exists(string $payment_method_name, bool $force_recheck = false): bool
201
+	{
202
+		if (
203
+			$force_recheck
204
+			|| empty($this->_payment_method_types)
205
+			|| ! isset($this->_payment_method_types[ $payment_method_name ])
206
+		) {
207
+			$this->maybe_register_payment_methods($force_recheck);
208
+		}
209
+		if (isset($this->_payment_method_types[ $payment_method_name ])) {
210
+			require_once($this->_payment_method_types[ $payment_method_name ]);
211
+			return true;
212
+		}
213
+		return false;
214
+	}
215
+
216
+
217
+	/**
218
+	 * Returns all the class names of the various payment method types
219
+	 *
220
+	 * @param boolean $with_prefixes TRUE: get payment method type class names; false just their 'names'
221
+	 *                               (what you'd find in wp_esp_payment_method.PMD_type)
222
+	 * @param boolean $force_recheck whether to force re-checking for new payment method types
223
+	 * @return array
224
+	 */
225
+	public function payment_method_type_names(bool $with_prefixes = false, bool $force_recheck = false): array
226
+	{
227
+		$this->maybe_register_payment_methods($force_recheck);
228
+		if ($with_prefixes) {
229
+			$classnames      = array_keys($this->_payment_method_types);
230
+			$payment_methods = [];
231
+			foreach ($classnames as $classname) {
232
+				$payment_methods[] = $this->payment_method_class_from_type($classname);
233
+			}
234
+			return $payment_methods;
235
+		}
236
+		return array_keys($this->_payment_method_types);
237
+	}
238
+
239
+
240
+	/**
241
+	 * Gets an object of each payment method type, none of which are bound to a
242
+	 * payment method instance
243
+	 *
244
+	 * @param boolean $force_recheck whether to force re-checking for new payment method types
245
+	 * @return EE_PMT_Base[]
246
+	 */
247
+	public function payment_method_types(bool $force_recheck = false): array
248
+	{
249
+		if ($force_recheck || empty($this->payment_method_objects)) {
250
+			$this->maybe_register_payment_methods($force_recheck);
251
+			foreach ($this->payment_method_type_names(true) as $classname) {
252
+				if (! isset($this->payment_method_objects[ $classname ])) {
253
+					$this->payment_method_objects[ $classname ] = new $classname();
254
+				}
255
+			}
256
+		}
257
+		return $this->payment_method_objects;
258
+	}
259
+
260
+
261
+	/**
262
+	 * Changes the payment method's class name into the payment method type's name
263
+	 * (as used on the payment method's table's PMD_type field)
264
+	 *
265
+	 * @param string $classname
266
+	 * @return string
267
+	 */
268
+	public function payment_method_type_sans_class_prefix(string $classname): string
269
+	{
270
+		return str_replace('EE_PMT_', '', $classname);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Does the opposite of payment-method_type_sans_prefix
276
+	 *
277
+	 * @param string $type
278
+	 * @return string
279
+	 */
280
+	public function payment_method_class_from_type(string $type): string
281
+	{
282
+		return 'EE_PMT_' . $type;
283
+	}
284
+
285
+
286
+	/**
287
+	 * Activates a payment method of the given type.
288
+	 *
289
+	 * @param string $payment_method_type the PMT_type; for EE_PMT_Invoice this would be 'Invoice'
290
+	 * @return EE_Payment_Method
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws EE_Error
293
+	 * @throws ReflectionException
294
+	 */
295
+	public function activate_a_payment_method_of_type(string $payment_method_type): EE_Payment_Method
296
+	{
297
+		$this->maybe_register_payment_methods();
298
+		$payment_method = EEM_Payment_Method::instance()->get_one_of_type($payment_method_type);
299
+		if (! $payment_method instanceof EE_Payment_Method) {
300
+			$pm_type_class = $this->payment_method_class_from_type($payment_method_type);
301
+			if (class_exists($pm_type_class)) {
302
+				/** @var $pm_type_obj EE_PMT_Base */
303
+				$pm_type_obj    = new $pm_type_class();
304
+				$payment_method = EEM_Payment_Method::instance()->get_one_by_slug($pm_type_obj->system_name());
305
+				if (! $payment_method) {
306
+					$payment_method = $this->create_payment_method_of_type($pm_type_obj);
307
+				}
308
+				$payment_method->set_type($payment_method_type);
309
+				$this->initialize_payment_method($payment_method);
310
+			} else {
311
+				throw new EE_Error(
312
+					sprintf(
313
+						esc_html__(
314
+							'There is no payment method of type %1$s, so it could not be activated',
315
+							'event_espresso'
316
+						),
317
+						$pm_type_class
318
+					)
319
+				);
320
+			}
321
+		}
322
+		$payment_method->set_active();
323
+		$payment_method->save();
324
+		/** @type EE_Message_Resource_Manager $message_resource_manager */
325
+		// if this was the invoice message type, make sure users can view their invoices
326
+		if (
327
+			$payment_method->type() === 'Invoice'
328
+			&& (
329
+			! EEH_MSG_Template::is_mt_active('invoice')
330
+			)
331
+		) {
332
+			$message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
333
+			/** @type EE_Message_Resource_Manager $message_resource_manager */
334
+			$message_resource_manager->ensure_message_type_is_active('invoice', 'html');
335
+			new PersistentAdminNotice(
336
+				'invoice_pm_requirements_notice',
337
+				sprintf(
338
+					esc_html__(
339
+						'The Invoice payment method has been activated. It requires the %1$sinvoice message%2$s type to be active, so it was automatically activated for you.',
340
+						'event_espresso'
341
+					),
342
+					'<a href="' . admin_url('admin.php?page=espresso_messages&action=settings') . '">',
343
+					'</a>'
344
+				),
345
+				true
346
+			);
347
+		}
348
+		return $payment_method;
349
+	}
350
+
351
+
352
+	/**
353
+	 * Creates a payment method of the specified type. Does not save it.
354
+	 *
355
+	 * @param EE_PMT_Base $pm_type_obj
356
+	 * @return EE_Payment_Method
357
+	 * @throws EE_Error*@throws ReflectionException
358
+	 * @throws ReflectionException
359
+	 * @global WP_User    $current_user
360
+	 */
361
+	public function create_payment_method_of_type(EE_PMT_Base $pm_type_obj): EE_Payment_Method
362
+	{
363
+		global $current_user;
364
+		return EE_Payment_Method::new_instance(
365
+			[
366
+				'PMD_type'       => $pm_type_obj->system_name(),
367
+				'PMD_name'       => $pm_type_obj->defaultFrontendName(),
368
+				'PMD_admin_name' => $pm_type_obj->pretty_name(),
369
+				'PMD_slug'       => $pm_type_obj->system_name(),// automatically converted to slug
370
+				'PMD_wp_user'    => $current_user->ID,
371
+				'PMD_order'      => EEM_Payment_Method::instance()->count(
372
+					[['PMD_type' => ['!=', 'Admin_Only']]]
373
+				) * 10,
374
+			]
375
+		);
376
+	}
377
+
378
+
379
+	/**
380
+	 * Sets the initial payment method properties (including extra meta)
381
+	 *
382
+	 * @param EE_Payment_Method $payment_method
383
+	 * @return EE_Payment_Method
384
+	 * @throws EE_Error
385
+	 * @throws ReflectionException
386
+	 */
387
+	public function initialize_payment_method(EE_Payment_Method $payment_method): EE_Payment_Method
388
+	{
389
+		$pm_type_obj = $payment_method->type_obj();
390
+		$payment_method->set_description($pm_type_obj->default_description());
391
+		if (! $payment_method->button_url()) {
392
+			$payment_method->set_button_url($pm_type_obj->default_button_url());
393
+		}
394
+		// now add setup its default extra meta properties
395
+		$extra_metas = $pm_type_obj->settings_form()->extra_meta_inputs();
396
+		if (! empty($extra_metas)) {
397
+			// verify the payment method has an ID before adding extra meta
398
+			if (! $payment_method->ID()) {
399
+				$payment_method->save();
400
+			}
401
+			foreach ($extra_metas as $meta_name => $input) {
402
+				$payment_method->update_extra_meta($meta_name, $input->raw_value());
403
+			}
404
+		}
405
+		return $payment_method;
406
+	}
407
+
408
+
409
+	/**
410
+	 * Makes sure the payment method is related to the specified payment method
411
+	 *
412
+	 * @param EE_Payment_Method $payment_method
413
+	 * @return EE_Payment_Method
414
+	 * @deprecated in 4.9.40 because the currency payment method table is being deprecated
415
+	 */
416
+	public function set_usable_currencies_on_payment_method(EE_Payment_Method $payment_method): EE_Payment_Method
417
+	{
418
+		EE_Error::doing_it_wrong(
419
+			'EE_Payment_Method_Manager::set_usable_currencies_on_payment_method',
420
+			esc_html__(
421
+				'We no longer define what currencies are usable by payment methods. Its not used nor efficient.',
422
+				'event_espresso'
423
+			),
424
+			'4.9.40'
425
+		);
426
+		return $payment_method;
427
+	}
428
+
429
+
430
+	/**
431
+	 * Deactivates a payment method of the given payment method slug.
432
+	 *
433
+	 * @param string $payment_method_slug The slug for the payment method to deactivate.
434
+	 * @return int count of rows updated.
435
+	 * @throws EE_Error
436
+	 * @throws ReflectionException
437
+	 */
438
+	public function deactivate_payment_method(string $payment_method_slug): int
439
+	{
440
+		EE_Log::instance()->log(
441
+			__FILE__,
442
+			__FUNCTION__,
443
+			sprintf(
444
+				esc_html__(
445
+					'Payment method with slug %1$s is being deactivated by site admin',
446
+					'event_espresso'
447
+				),
448
+				$payment_method_slug
449
+			),
450
+			'payment_method_change'
451
+		);
452
+		$count_updated = EEM_Payment_Method::instance()->update(
453
+			['PMD_scope' => []],
454
+			[['PMD_slug' => $payment_method_slug]]
455
+		);
456
+		do_action(
457
+			'AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method',
458
+			$payment_method_slug,
459
+			$count_updated
460
+		);
461
+		return $count_updated;
462
+	}
463
+
464
+
465
+	/**
466
+	 * initializes payment method access caps via EE_Capabilities::init_role_caps()
467
+	 * upon EE_Payment_Method_Manager construction
468
+	 *
469
+	 * @throws EE_Error
470
+	 * @throws DomainException
471
+	 */
472
+	public function initializePaymentMethodCaps()
473
+	{
474
+		// don't do this twice
475
+		if ($this->payment_method_caps_initialized) {
476
+			return;
477
+		}
478
+		EE_Capabilities::instance()->addCaps(
479
+			$this->getPaymentMethodCaps()
480
+		);
481
+		$this->payment_method_caps_initialized = true;
482
+	}
483
+
484
+
485
+	/**
486
+	 * array  of dynamic payment method access caps.
487
+	 * at the time of writing, october 20 2014, these are the caps added:
488
+	 *  ee_payment_method_admin_only
489
+	 *  ee_payment_method_aim
490
+	 *  ee_payment_method_bank
491
+	 *  ee_payment_method_check
492
+	 *  ee_payment_method_invoice
493
+	 *  ee_payment_method_mijireh
494
+	 *  ee_payment_method_paypal_pro
495
+	 *  ee_payment_method_paypal_standard
496
+	 * Any other payment methods added to core or via addons will also get
497
+	 * their related capability automatically added too, so long as they are
498
+	 * registered properly using EE_Register_Payment_Method::register()
499
+	 *
500
+	 * @return array
501
+	 * @throws DomainException
502
+	 */
503
+	protected function getPaymentMethodCaps(): array
504
+	{
505
+		$caps = [];
506
+		foreach ($this->payment_method_type_names() as $payment_method_name) {
507
+			$caps = $this->addPaymentMethodCap($payment_method_name, $caps);
508
+		}
509
+		return $caps;
510
+	}
511
+
512
+
513
+	/**
514
+	 * @param string $payment_method_name
515
+	 * @param array  $payment_method_caps
516
+	 * @param string $role
517
+	 * @return array
518
+	 * @throws DomainException
519
+	 */
520
+	public function addPaymentMethodCap(
521
+		string $payment_method_name,
522
+		array $payment_method_caps,
523
+		string $role = 'administrator'
524
+	): array {
525
+		if (empty($payment_method_name)) {
526
+			throw new DomainException(
527
+				esc_html__(
528
+					'The name of a payment method must be specified to add capabilities.',
529
+					'event_espresso'
530
+				)
531
+			);
532
+		}
533
+		if (empty($role)) {
534
+			throw new DomainException(
535
+				sprintf(
536
+					esc_html__(
537
+						'No role was supplied while trying to add capabilities for the %1$s payment method.',
538
+						'event_espresso'
539
+					),
540
+					$payment_method_name
541
+				)
542
+			);
543
+		}
544
+		if (! isset($payment_method_caps[ $role ])) {
545
+			$payment_method_caps[ $role ] = [];
546
+		}
547
+		$payment_method_caps[ $role ][] = EE_Payment_Method_Manager::CAPABILITIES_PREFIX
548
+										  . strtolower($payment_method_name);
549
+		return $payment_method_caps;
550
+	}
551
+
552
+
553
+	/**
554
+	 * callback for FHEE__EE_Capabilities__init_role_caps__caps_map filter
555
+	 * to add dynamic payment method access caps when capabilities are reset
556
+	 * (or if that filter is called and PM caps are not already set)
557
+	 *
558
+	 * @param array $caps capabilities being filtered
559
+	 * @param bool  $reset
560
+	 * @return array
561
+	 * @throws DomainException
562
+	 */
563
+	public function addPaymentMethodCapsDuringReset(array $caps, bool $reset = false): array
564
+	{
565
+		if ($reset || ! $this->payment_method_caps_initialized) {
566
+			$this->payment_method_caps_initialized = true;
567
+			$caps                                  = array_merge_recursive($caps, $this->getPaymentMethodCaps());
568
+		}
569
+		return $caps;
570
+	}
571
+
572
+
573
+	/**
574
+	 * @param $caps
575
+	 * @return mixed
576
+	 * @deprecated 4.9.42
577
+	 */
578
+	public function add_payment_method_caps($caps)
579
+	{
580
+		return $caps;
581
+	}
582 582
 }
Please login to merge, or discard this patch.
Spacing   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -73,7 +73,7 @@  discard block
 block discarded – undo
73 73
     public static function instance(): EE_Payment_Method_Manager
74 74
     {
75 75
         // check if class object is instantiated, and instantiated properly
76
-        if (! self::$_instance instanceof EE_Payment_Method_Manager) {
76
+        if ( ! self::$_instance instanceof EE_Payment_Method_Manager) {
77 77
             EE_Registry::instance()->load_lib('PMT_Base');
78 78
             self::$_instance = new self();
79 79
         }
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
      */
106 106
     public function maybe_register_payment_methods(bool $force_recheck = false)
107 107
     {
108
-        if (! $this->_payment_method_types || $force_recheck) {
108
+        if ( ! $this->_payment_method_types || $force_recheck) {
109 109
             $this->_register_payment_methods();
110 110
         }
111 111
     }
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
     protected function _register_payment_methods(): array
120 120
     {
121 121
         // grab list of installed modules
122
-        $pm_to_register = glob(EE_PAYMENT_METHODS . '*', GLOB_ONLYDIR);
122
+        $pm_to_register = glob(EE_PAYMENT_METHODS.'*', GLOB_ONLYDIR);
123 123
         // filter list of modules to register
124 124
         $pm_to_register = apply_filters(
125 125
             'FHEE__EE_Payment_Method_Manager__register_payment_methods__payment_methods_to_register',
@@ -159,9 +159,9 @@  discard block
 block discarded – undo
159 159
         // create class name from module directory name
160 160
         $module = str_replace(['_', ' '], [' ', '_'], $module_dir);
161 161
         // add class prefix
162
-        $module_class = 'EE_PMT_' . $module;
162
+        $module_class = 'EE_PMT_'.$module;
163 163
         // does the module exist ?
164
-        if (! is_readable($payment_method_path . '/' . $module_class . $module_ext)) {
164
+        if ( ! is_readable($payment_method_path.'/'.$module_class.$module_ext)) {
165 165
             $msg = sprintf(
166 166
                 esc_html__(
167 167
                     'The requested %s payment method file could not be found or is not readable due to file permissions.',
@@ -169,22 +169,22 @@  discard block
 block discarded – undo
169 169
                 ),
170 170
                 $module
171 171
             );
172
-            EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
172
+            EE_Error::add_error($msg.'||'.$msg, __FILE__, __FUNCTION__, __LINE__);
173 173
             return false;
174 174
         }
175 175
         // load the module class file
176
-        require_once($payment_method_path . '/' . $module_class . $module_ext);
176
+        require_once($payment_method_path.'/'.$module_class.$module_ext);
177 177
         // verify that class exists
178
-        if (! class_exists($module_class)) {
178
+        if ( ! class_exists($module_class)) {
179 179
             $msg = sprintf(
180 180
                 esc_html__('The requested %s module class does not exist.', 'event_espresso'),
181 181
                 $module_class
182 182
             );
183
-            EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
183
+            EE_Error::add_error($msg.'||'.$msg, __FILE__, __FUNCTION__, __LINE__);
184 184
             return false;
185 185
         }
186 186
         // add to array of registered modules
187
-        $this->_payment_method_types[ $module ] = $payment_method_path . '/' . $module_class . $module_ext;
187
+        $this->_payment_method_types[$module] = $payment_method_path.'/'.$module_class.$module_ext;
188 188
         ksort($this->_payment_method_types);
189 189
         return true;
190 190
     }
@@ -202,12 +202,12 @@  discard block
 block discarded – undo
202 202
         if (
203 203
             $force_recheck
204 204
             || empty($this->_payment_method_types)
205
-            || ! isset($this->_payment_method_types[ $payment_method_name ])
205
+            || ! isset($this->_payment_method_types[$payment_method_name])
206 206
         ) {
207 207
             $this->maybe_register_payment_methods($force_recheck);
208 208
         }
209
-        if (isset($this->_payment_method_types[ $payment_method_name ])) {
210
-            require_once($this->_payment_method_types[ $payment_method_name ]);
209
+        if (isset($this->_payment_method_types[$payment_method_name])) {
210
+            require_once($this->_payment_method_types[$payment_method_name]);
211 211
             return true;
212 212
         }
213 213
         return false;
@@ -249,8 +249,8 @@  discard block
 block discarded – undo
249 249
         if ($force_recheck || empty($this->payment_method_objects)) {
250 250
             $this->maybe_register_payment_methods($force_recheck);
251 251
             foreach ($this->payment_method_type_names(true) as $classname) {
252
-                if (! isset($this->payment_method_objects[ $classname ])) {
253
-                    $this->payment_method_objects[ $classname ] = new $classname();
252
+                if ( ! isset($this->payment_method_objects[$classname])) {
253
+                    $this->payment_method_objects[$classname] = new $classname();
254 254
                 }
255 255
             }
256 256
         }
@@ -279,7 +279,7 @@  discard block
 block discarded – undo
279 279
      */
280 280
     public function payment_method_class_from_type(string $type): string
281 281
     {
282
-        return 'EE_PMT_' . $type;
282
+        return 'EE_PMT_'.$type;
283 283
     }
284 284
 
285 285
 
@@ -296,13 +296,13 @@  discard block
 block discarded – undo
296 296
     {
297 297
         $this->maybe_register_payment_methods();
298 298
         $payment_method = EEM_Payment_Method::instance()->get_one_of_type($payment_method_type);
299
-        if (! $payment_method instanceof EE_Payment_Method) {
299
+        if ( ! $payment_method instanceof EE_Payment_Method) {
300 300
             $pm_type_class = $this->payment_method_class_from_type($payment_method_type);
301 301
             if (class_exists($pm_type_class)) {
302 302
                 /** @var $pm_type_obj EE_PMT_Base */
303 303
                 $pm_type_obj    = new $pm_type_class();
304 304
                 $payment_method = EEM_Payment_Method::instance()->get_one_by_slug($pm_type_obj->system_name());
305
-                if (! $payment_method) {
305
+                if ( ! $payment_method) {
306 306
                     $payment_method = $this->create_payment_method_of_type($pm_type_obj);
307 307
                 }
308 308
                 $payment_method->set_type($payment_method_type);
@@ -339,7 +339,7 @@  discard block
 block discarded – undo
339 339
                         'The Invoice payment method has been activated. It requires the %1$sinvoice message%2$s type to be active, so it was automatically activated for you.',
340 340
                         'event_espresso'
341 341
                     ),
342
-                    '<a href="' . admin_url('admin.php?page=espresso_messages&action=settings') . '">',
342
+                    '<a href="'.admin_url('admin.php?page=espresso_messages&action=settings').'">',
343 343
                     '</a>'
344 344
                 ),
345 345
                 true
@@ -366,7 +366,7 @@  discard block
 block discarded – undo
366 366
                 'PMD_type'       => $pm_type_obj->system_name(),
367 367
                 'PMD_name'       => $pm_type_obj->defaultFrontendName(),
368 368
                 'PMD_admin_name' => $pm_type_obj->pretty_name(),
369
-                'PMD_slug'       => $pm_type_obj->system_name(),// automatically converted to slug
369
+                'PMD_slug'       => $pm_type_obj->system_name(), // automatically converted to slug
370 370
                 'PMD_wp_user'    => $current_user->ID,
371 371
                 'PMD_order'      => EEM_Payment_Method::instance()->count(
372 372
                     [['PMD_type' => ['!=', 'Admin_Only']]]
@@ -388,14 +388,14 @@  discard block
 block discarded – undo
388 388
     {
389 389
         $pm_type_obj = $payment_method->type_obj();
390 390
         $payment_method->set_description($pm_type_obj->default_description());
391
-        if (! $payment_method->button_url()) {
391
+        if ( ! $payment_method->button_url()) {
392 392
             $payment_method->set_button_url($pm_type_obj->default_button_url());
393 393
         }
394 394
         // now add setup its default extra meta properties
395 395
         $extra_metas = $pm_type_obj->settings_form()->extra_meta_inputs();
396
-        if (! empty($extra_metas)) {
396
+        if ( ! empty($extra_metas)) {
397 397
             // verify the payment method has an ID before adding extra meta
398
-            if (! $payment_method->ID()) {
398
+            if ( ! $payment_method->ID()) {
399 399
                 $payment_method->save();
400 400
             }
401 401
             foreach ($extra_metas as $meta_name => $input) {
@@ -541,10 +541,10 @@  discard block
 block discarded – undo
541 541
                 )
542 542
             );
543 543
         }
544
-        if (! isset($payment_method_caps[ $role ])) {
545
-            $payment_method_caps[ $role ] = [];
544
+        if ( ! isset($payment_method_caps[$role])) {
545
+            $payment_method_caps[$role] = [];
546 546
         }
547
-        $payment_method_caps[ $role ][] = EE_Payment_Method_Manager::CAPABILITIES_PREFIX
547
+        $payment_method_caps[$role][] = EE_Payment_Method_Manager::CAPABILITIES_PREFIX
548 548
                                           . strtolower($payment_method_name);
549 549
         return $payment_method_caps;
550 550
     }
Please login to merge, or discard this patch.
core/EE_Data_Migration_Manager.core.php 2 patches
Indentation   +1324 added lines, -1324 removed lines patch added patch discarded remove patch
@@ -28,1328 +28,1328 @@
 block discarded – undo
28 28
  */
29 29
 class EE_Data_Migration_Manager implements ResettableInterface
30 30
 {
31
-    /**
32
-     * @var EE_Registry
33
-     */
34
-    // protected $EE;
35
-    /**
36
-     * name of the WordPress option which stores an array of data about
37
-     */
38
-    const data_migrations_option_name = 'ee_data_migration';
39
-
40
-
41
-    const data_migration_script_option_prefix         = 'ee_data_migration_script_';
42
-
43
-    const data_migration_script_mapping_option_prefix = 'ee_dms_map_';
44
-
45
-    /**
46
-     * name of the WordPress option which stores the database' current version. IE, the code may be at version 4.2.0,
47
-     * but as migrations are performed the database will progress from 3.1.35 to 4.1.0 etc.
48
-     */
49
-    const current_database_state = 'ee_data_migration_current_db_state';
50
-
51
-    /**
52
-     * Special status string returned when we're positive there are no more data migration
53
-     * scripts that can be run.
54
-     */
55
-    const status_no_more_migration_scripts = 'no_more_migration_scripts';
56
-
57
-    /**
58
-     * string indicating the migration should continue
59
-     */
60
-    const status_continue = 'status_continue';
61
-
62
-    /**
63
-     * string indicating the migration has completed and should be ended
64
-     */
65
-    const status_completed = 'status_completed';
66
-
67
-    /**
68
-     * string indicating a fatal error occurred and the data migration should be completely aborted
69
-     */
70
-    const status_fatal_error = 'status_fatal_error';
71
-
72
-    /**
73
-     * the number of 'items' (usually DB rows) to migrate on each 'step' (ajax request sent
74
-     * during migration)
75
-     */
76
-    const step_size = 50;
77
-
78
-    /**
79
-     * option name that stores the queue of ee plugins needing to have
80
-     * their data initialized (or re-initialized) once we are done migrations
81
-     */
82
-    const db_init_queue_option_name = 'ee_db_init_queue';
83
-
84
-    /**
85
-     * Array of information concerning data migrations that have run in the history
86
-     * of this EE installation. Keys should be the name of the version the script upgraded to
87
-     *
88
-     * @var EE_Data_Migration_Script_Base[]
89
-     */
90
-    private $_data_migrations_ran = null;
91
-
92
-    /**
93
-     * The last run script. It's nice to store this somewhere accessible, as its easiest
94
-     * to know which was the last run by which is the newest wp option; but in most of the code
95
-     * we just use the local $_data_migration_ran array, which organized the scripts differently
96
-     *
97
-     * @var EE_Data_Migration_Script_Base
98
-     */
99
-    private $_last_ran_script = null;
100
-
101
-    /**
102
-     * Similarly to _last_ran_script, but this is the last INCOMPLETE migration script.
103
-     *
104
-     * @var EE_Data_Migration_Script_Base
105
-     */
106
-    private $_last_ran_incomplete_script = null;
107
-
108
-    /**
109
-     * array where keys are classnames, and values are filepaths of all the known migration scripts
110
-     *
111
-     * @var array
112
-     */
113
-    private $_data_migration_class_to_filepath_map;
114
-
115
-    /**
116
-     * the following 4 properties are fully set on construction.
117
-     * Note: the first two apply to whether to continue running ALL migration scripts (ie, even though we're finished
118
-     * one, we may want to start the next one); whereas the last two indicate whether to continue running a single
119
-     * data migration script
120
-     *
121
-     * @var array
122
-     */
123
-    public $stati_that_indicate_to_continue_migrations              = [];
124
-
125
-    public $stati_that_indicate_to_stop_migrations                  = [];
126
-
127
-    public $stati_that_indicate_to_continue_single_migration_script = [];
128
-
129
-    public $stati_that_indicate_to_stop_single_migration_script     = [];
130
-
131
-    /**
132
-     * @var TableManager $table_manager
133
-     */
134
-    protected $_table_manager;
135
-
136
-    /**
137
-     * @var TableAnalysis $table_analysis
138
-     */
139
-    protected $_table_analysis;
140
-
141
-    /**
142
-     * @var array $script_migration_versions
143
-     */
144
-    protected $script_migration_versions;
145
-
146
-    /**
147
-     * @var array $dms_folders
148
-     */
149
-    protected $dms_folders;
150
-
151
-    /**
152
-     * @var EE_Data_Migration_Manager $_instance
153
-     * @access    private
154
-     */
155
-    private static $_instance = null;
156
-
157
-
158
-    /**
159
-     * @singleton method used to instantiate class object
160
-     * @access    public
161
-     * @return EE_Data_Migration_Manager instance
162
-     */
163
-    public static function instance()
164
-    {
165
-        // check if class object is instantiated
166
-        if (! self::$_instance instanceof EE_Data_Migration_Manager) {
167
-            self::$_instance = new self();
168
-        }
169
-        return self::$_instance;
170
-    }
171
-
172
-
173
-    /**
174
-     * resets the singleton to its brand-new state (but does NOT delete old references to the old singleton. Meaning,
175
-     * all new usages of the singleton should be made with Classname::instance()) and returns it
176
-     *
177
-     * @return EE_Data_Migration_Manager
178
-     */
179
-    public static function reset()
180
-    {
181
-        self::$_instance = new self();
182
-        return self::instance();
183
-    }
184
-
185
-
186
-    /**
187
-     * @throws EE_Error
188
-     * @throws ReflectionException
189
-     */
190
-    private function __construct()
191
-    {
192
-        $this->stati_that_indicate_to_continue_migrations              = [
193
-            self::status_continue,
194
-            self::status_completed,
195
-        ];
196
-        $this->stati_that_indicate_to_stop_migrations                  = [
197
-            self::status_fatal_error,
198
-            self::status_no_more_migration_scripts,
199
-        ];
200
-        $this->stati_that_indicate_to_continue_single_migration_script = [
201
-            self::status_continue,
202
-        ];
203
-        $this->stati_that_indicate_to_stop_single_migration_script     = [
204
-            self::status_completed,
205
-            self::status_fatal_error,
206
-            // note: status_no_more_migration_scripts doesn't apply
207
-        ];
208
-        $this->dms_folders                                             = [];
209
-        // make sure we've included the base migration script, because we may need the EE_DMS_Unknown_1_0_0 class
210
-        // to be defined, because right now it doesn't get autoloaded on its own
211
-        EE_Registry::instance()->load_core('Data_Migration_Class_Base', [], true);
212
-        EE_Registry::instance()->load_core('Data_Migration_Script_Base', [], true);
213
-        EE_Registry::instance()->load_core('DMS_Unknown_1_0_0', [], true);
214
-        EE_Registry::instance()->load_core('Data_Migration_Script_Stage', [], true);
215
-        EE_Registry::instance()->load_core('Data_Migration_Script_Stage_Table', [], true);
216
-        $this->_table_manager  = EE_Registry::instance()->create('TableManager', [], true);
217
-        $this->_table_analysis = EE_Registry::instance()->create('TableAnalysis', [], true);
218
-    }
219
-
220
-
221
-    /**
222
-     * Deciphers, from an option's name, what plugin and version it relates to (see _save_migrations_ran to see what
223
-     * the option names are like, but generally they're like
224
-     * 'ee_data_migration_script_Core.4.1.0' in 4.2 or 'ee_data_migration_script_4.1.0' before that).
225
-     * The option name shouldn't ever be like 'ee_data_migration_script_Core.4.1.0.reg' because it's derived,
226
-     * indirectly, from the data migration's classname, which should always be like EE_DMS_%s_%d_%d_%d.dms.php
227
-     * (ex: EE_DMS_Core_4_1_0.dms.php)
228
-     *
229
-     * @param string $option_name (see EE_Data_Migration_Manage::_save_migrations_ran() where the option name is set)
230
-     * @return array where the first item is the plugin slug (eg 'Core','Calendar', etc.) and the 2nd is the version of
231
-     *                            that plugin (eg '4.1.0')
232
-     */
233
-    private function _get_plugin_slug_and_version_string_from_dms_option_name($option_name)
234
-    {
235
-        $plugin_slug_and_version_string = str_replace(
236
-            EE_Data_Migration_Manager::data_migration_script_option_prefix,
237
-            "",
238
-            $option_name
239
-        );
240
-        // check if $plugin_slug_and_version_string is like '4.1.0' (4.1-style) or 'Core.4.1.0' (4.2-style)
241
-        $parts = explode(".", $plugin_slug_and_version_string);
242
-
243
-        if (count($parts) == 4) {
244
-            // it's 4.2-style.eg Core.4.1.0
245
-            $plugin_slug    = $parts[0];                                     // eg Core
246
-            $version_string = $parts[1] . "." . $parts[2] . "." . $parts[3]; // eg 4.1.0
247
-        } else {
248
-            // it's 4.1-style: eg 4.1.0
249
-            $plugin_slug    = 'Core';
250
-            $version_string = $plugin_slug_and_version_string;// eg 4.1.0
251
-        }
252
-        return [$plugin_slug, $version_string];
253
-    }
254
-
255
-
256
-    /**
257
-     * Gets the DMS class from the WordPress option, otherwise throws an EE_Error if it's not
258
-     * for a known DMS class.
259
-     *
260
-     * @param string $dms_option_name
261
-     * @param string $dms_option_value (serialized)
262
-     * @return EE_Data_Migration_Script_Base
263
-     * @throws EE_Error
264
-     */
265
-    private function _get_dms_class_from_wp_option($dms_option_name, $dms_option_value)
266
-    {
267
-        $data_migration_data = maybe_unserialize($dms_option_value);
268
-        if (isset($data_migration_data['class']) && class_exists($data_migration_data['class'])) {
269
-            // During multisite migrations, it's possible we already grabbed another instance of this class
270
-            // but for a different blog. Make sure we don't reuse them (as they store info specific
271
-            // to their respective blog, like which database table to migrate).
272
-            $class = LoaderFactory::getLoader()->getNew($data_migration_data['class']);
273
-            if ($class instanceof EE_Data_Migration_Script_Base) {
274
-                $class->instantiate_from_array_of_properties($data_migration_data);
275
-                return $class;
276
-            } else {
277
-                // huh, so it's an object but not a data migration script?? that shouldn't happen
278
-                // just leave it as an array (which will probably just get ignored)
279
-                throw new EE_Error(
280
-                    sprintf(
281
-                        esc_html__(
282
-                            "Trying to retrieve DMS class from wp option. No DMS by the name '%s' exists",
283
-                            'event_espresso'
284
-                        ),
285
-                        $data_migration_data['class']
286
-                    )
287
-                );
288
-            }
289
-        } else {
290
-            // so the data doesn't specify a class. So it must either be a legacy array of info or some array (which we'll probably just ignore), or a class that no longer exists
291
-            throw new EE_Error(
292
-                sprintf(
293
-                    esc_html__("The wp option  with key '%s' does not represent a DMS", 'event_espresso'),
294
-                    $dms_option_name
295
-                )
296
-            );
297
-        }
298
-    }
299
-
300
-
301
-    /**
302
-     * Gets the array describing what data migrations have run.
303
-     * Also has a side effect of recording which was the last run,
304
-     * and which was the last run which hasn't finished yet
305
-     *
306
-     * @return array where each element should be an array of EE_Data_Migration_Script_Base
307
-     *               (but also has a few legacy arrays in there - which should probably be ignored)
308
-     * @throws EE_Error
309
-     */
310
-    public function get_data_migrations_ran()
311
-    {
312
-        if (! $this->_data_migrations_ran) {
313
-            // setup autoloaders for each of the scripts in there
314
-            $this->get_all_data_migration_scripts_available();
315
-            $data_migrations_options =
316
-                $this->get_all_migration_script_options();// get_option(EE_Data_Migration_Manager::data_migrations_option_name,get_option('espresso_data_migrations',array()));
317
-
318
-            $data_migrations_ran = [];
319
-            // convert into data migration script classes where possible
320
-            foreach ($data_migrations_options as $data_migration_option) {
321
-                [$plugin_slug, $version_string] = $this->_get_plugin_slug_and_version_string_from_dms_option_name(
322
-                    $data_migration_option['option_name']
323
-                );
324
-
325
-                try {
326
-                    $class                                                  = $this->_get_dms_class_from_wp_option(
327
-                        $data_migration_option['option_name'],
328
-                        $data_migration_option['option_value']
329
-                    );
330
-                    $data_migrations_ran[ $plugin_slug ][ $version_string ] = $class;
331
-                    // ok so far THIS is the 'last-run-script'... unless we find another on next iteration
332
-                    $this->_last_ran_script = $class;
333
-                    if (! $class->is_completed()) {
334
-                        // sometimes we also like to know which was the last incomplete script (or if there are any at all)
335
-                        $this->_last_ran_incomplete_script = $class;
336
-                    }
337
-                } catch (EE_Error $e) {
338
-                    // ok so it's not a DMS. We'll just keep it, although other code will need to expect non-DMSs
339
-                    $data_migrations_ran[ $plugin_slug ][ $version_string ] = maybe_unserialize(
340
-                        $data_migration_option['option_value']
341
-                    );
342
-                }
343
-            }
344
-            // so here the array of $data_migrations_ran is actually a mix of classes and a few legacy arrays
345
-            $this->_data_migrations_ran = $data_migrations_ran;
346
-            if (! $this->_data_migrations_ran || ! is_array($this->_data_migrations_ran)) {
347
-                $this->_data_migrations_ran = [];
348
-            }
349
-        }
350
-        return $this->_data_migrations_ran;
351
-    }
352
-
353
-
354
-    /**
355
-     * @param string $script_name eg 'DMS_Core_4_1_0'
356
-     * @param string $old_table   eg 'wp_events_detail'
357
-     * @param string $old_pk      eg 'wp_esp_posts'
358
-     * @param        $new_table
359
-     * @return mixed string or int
360
-     * @throws EE_Error
361
-     * @throws ReflectionException
362
-     */
363
-    public function get_mapping_new_pk($script_name, $old_table, $old_pk, $new_table)
364
-    {
365
-        $script = EE_Registry::instance()->load_dms($script_name);
366
-        return $script->get_mapping_new_pk($old_table, $old_pk, $new_table);
367
-    }
368
-
369
-
370
-    /**
371
-     * Gets all the options containing migration scripts that have been run. Ordering is important: it's assumed that
372
-     * the last option returned in this array is the most-recently run DMS option
373
-     *
374
-     * @return array
375
-     */
376
-    public function get_all_migration_script_options()
377
-    {
378
-        global $wpdb;
379
-        return $wpdb->get_results(
380
-            "SELECT * FROM {$wpdb->options} WHERE option_name like '"
381
-            . EE_Data_Migration_Manager::data_migration_script_option_prefix
382
-            . "%' ORDER BY option_id ASC",
383
-            ARRAY_A
384
-        );
385
-    }
386
-
387
-
388
-    /**
389
-     * Gets the array of folders which contain data migration scripts. Also adds them to be auto-loaded
390
-     *
391
-     * @return array where each value is the full folder path of a folder containing data migration scripts, WITH
392
-     *               slashes at the end of the folder name.
393
-     */
394
-    public function get_data_migration_script_folders()
395
-    {
396
-        if (empty($this->dms_folders)) {
397
-            $this->dms_folders = (array) apply_filters(
398
-                'FHEE__EE_Data_Migration_Manager__get_data_migration_script_folders',
399
-                ['Core' => EE_CORE . 'data_migration_scripts']
400
-            );
401
-        }
402
-        return $this->dms_folders;
403
-    }
404
-
405
-
406
-    /**
407
-     * Gets the version the migration script upgrades to
408
-     *
409
-     * @param string $migration_script_name eg 'EE_DMS_Core_4_1_0'
410
-     * @return array {
411
-     * @type string  $slug                  like 'Core','Calendar',etc
412
-     * @type string  $version               like 4.3.0
413
-     *                                      }
414
-     * @throws EE_Error
415
-     */
416
-    public function script_migrates_to_version($migration_script_name, $eeAddonClass = '')
417
-    {
418
-        if (isset($this->script_migration_versions[ $migration_script_name ])) {
419
-            return $this->script_migration_versions[ $migration_script_name ];
420
-        }
421
-        $dms_info                                                  = $this->parse_dms_classname($migration_script_name);
422
-        $this->script_migration_versions[ $migration_script_name ] = [
423
-            'slug'    => $eeAddonClass !== ''
424
-                ? $eeAddonClass
425
-                : $dms_info['slug'],
426
-            'version' => $dms_info['major_version']
427
-                         . "."
428
-                         . $dms_info['minor_version']
429
-                         . "."
430
-                         . $dms_info['micro_version'],
431
-        ];
432
-        return $this->script_migration_versions[ $migration_script_name ];
433
-    }
434
-
435
-
436
-    /**
437
-     * Gets the juicy details out of a dms filename like 'EE_DMS_Core_4_1_0'
438
-     *
439
-     * @param string $classname
440
-     * @return array with keys 'slug','major_version','minor_version', and 'micro_version' (the last 3 are integers)
441
-     * @throws EE_Error
442
-     */
443
-    public function parse_dms_classname($classname)
444
-    {
445
-        $matches = [];
446
-        preg_match('~EE_DMS_(.*)_([0-9]*)_([0-9]*)_([0-9]*)~', $classname, $matches);
447
-        if (! $matches || ! (isset($matches[1]) && isset($matches[2]) && isset($matches[3]))) {
448
-            throw new EE_Error(
449
-                sprintf(
450
-                    esc_html__(
451
-                        "%s is not a valid Data Migration Script. The classname should be like EE_DMS_w_x_y_z, where w is either 'Core' or the slug of an addon and x, y and z are numbers, ",
452
-                        "event_espresso"
453
-                    ),
454
-                    $classname
455
-                )
456
-            );
457
-        }
458
-        return [
459
-            'slug'          => $matches[1],
460
-            'major_version' => intval($matches[2]),
461
-            'minor_version' => intval($matches[3]),
462
-            'micro_version' => intval($matches[4]),
463
-        ];
464
-    }
465
-
466
-
467
-    /**
468
-     * Ensures that the option indicating the current DB version is set. This should only be
469
-     * a concern when activating EE for the first time, THEORETICALLY.
470
-     * If we detect that we're activating EE4 over top of EE3.1, then we set the current db state to 3.1.x, otherwise
471
-     * to 4.1.x.
472
-     *
473
-     * @return string of current db state
474
-     */
475
-    public function ensure_current_database_state_is_set()
476
-    {
477
-        $espresso_db_core_updates = get_option('espresso_db_update', []);
478
-        $db_state                 = get_option(EE_Data_Migration_Manager::current_database_state);
479
-        if (! $db_state) {
480
-            // mark the DB as being in the state as the last version in there.
481
-            // this is done to trigger maintenance mode and do data migration scripts
482
-            // if the admin installed this version of EE over 3.1.x or 4.0.x
483
-            // otherwise, the normal maintenance mode code is fine
484
-            $previous_versions_installed = array_keys($espresso_db_core_updates);
485
-            $previous_version_installed  = end($previous_versions_installed);
486
-            if (version_compare('4.1.0', $previous_version_installed)) {
487
-                // last installed version was less than 4.1, so we want the data migrations to happen.
488
-                // SO, we're going to say the DB is at that state
489
-                $db_state = ['Core' => $previous_version_installed];
490
-            } else {
491
-                $db_state = ['Core' => EVENT_ESPRESSO_VERSION];
492
-            }
493
-            update_option(EE_Data_Migration_Manager::current_database_state, $db_state);
494
-        }
495
-        // in 4.1, $db_state would have only been a simple string like '4.1.0',
496
-        // but in 4.2+ it should be an array with at least key 'Core' and the value of that plugin's
497
-        // db, and possibly other keys for other addons like 'Calendar','Permissions',etc
498
-        if (! is_array($db_state)) {
499
-            $db_state = ['Core' => $db_state];
500
-            update_option(EE_Data_Migration_Manager::current_database_state, $db_state);
501
-        }
502
-        return $db_state;
503
-    }
504
-
505
-
506
-    /**
507
-     * Checks if there are any data migration scripts that ought to be run.
508
-     * If found, returns the instantiated classes.
509
-     * If none are found (ie, they've all already been run, or they don't apply), returns an empty array
510
-     *
511
-     * @return EE_Data_Migration_Script_Base[]
512
-     * @throws EE_Error
513
-     */
514
-    public function check_for_applicable_data_migration_scripts()
515
-    {
516
-        // get the option describing what options have already run
517
-        $scripts_ran = $this->get_data_migrations_ran();
518
-        // $scripts_ran = array('4.1.0.core'=>array('monkey'=>null));
519
-        $script_class_and_filepaths_available = $this->get_all_data_migration_scripts_available();
520
-
521
-
522
-        $current_database_state = $this->ensure_current_database_state_is_set();
523
-        // determine which have already been run
524
-        $script_classes_that_should_run_per_iteration = [];
525
-        $iteration                                    = 0;
526
-        $next_database_state_to_consider              = $current_database_state;
527
-        $theoretical_database_state                   = null;
528
-        do {
529
-            // the next state after the currently-considered one
530
-            // will start off looking the same as the current, but we may make additions...
531
-            $theoretical_database_state = $next_database_state_to_consider;
532
-            // the next db state to consider is
533
-            // "what would the DB be like had we run all the scripts we found that applied last time?"
534
-            foreach ($script_class_and_filepaths_available as $classname => $filepath) {
535
-                $migrates_to_version         = $this->script_migrates_to_version($classname);
536
-                $script_converts_plugin_slug = $migrates_to_version['slug'];
537
-                $script_converts_to_version  = $migrates_to_version['version'];
538
-                // check if this version script is DONE or not; or if it's never been run
539
-                if (
540
-                    ! $scripts_ran
541
-                    || ! isset($scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ])
542
-                ) {
543
-                    // we haven't run this conversion script before
544
-                    // now check if it applies...
545
-                    // note that we've added an autoloader for it on get_all_data_migration_scripts_available
546
-                    // Also, make sure we get a new one. It's possible this is being ran during a multisite migration,
547
-                    // in which case we don't want to reuse a DMS script from a different blog!
548
-                    $script = LoaderFactory::getLoader()->getNew($classname);
549
-                    /* @var $script EE_Data_Migration_Script_Base */
550
-                    $can_migrate = $script->can_migrate_from_version($theoretical_database_state);
551
-                    if ($can_migrate) {
552
-                        $script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
553
-                        $migrates_to_version                                                                 =
554
-                            $script->migrates_to_version();
555
-                        $next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
556
-                            $migrates_to_version['version'];
557
-                        unset($script_class_and_filepaths_available[ $classname ]);
558
-                    }
559
-                } elseif (
560
-                    $scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ]
561
-                    instanceof
562
-                    EE_Data_Migration_Script_Base
563
-                ) {
564
-                    // this script has been run, or at least started
565
-                    $script = $scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ];
566
-                    if ($script->get_status() !== self::status_completed) {
567
-                        // this script is already underway... keep going with it
568
-                        $script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
569
-                        $migrates_to_version                                                                 =
570
-                            $script->migrates_to_version();
571
-                        $next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
572
-                            $migrates_to_version['version'];
573
-                        unset($script_class_and_filepaths_available[ $classname ]);
574
-                    }
575
-                    // else it must have a status that indicates it has finished,
576
-                    // so we don't want to try and run it again
577
-                }
578
-                // else it exists, but it's not  a proper data migration script maybe the script got renamed?
579
-                // or was simply removed from EE? either way, it's certainly not runnable!
580
-            }
581
-            $iteration++;
582
-        } while ($next_database_state_to_consider !== $theoretical_database_state && $iteration < 6);
583
-        // ok we have all the scripts that should run, now let's make them into flat array
584
-        $scripts_that_should_run = [];
585
-        foreach ($script_classes_that_should_run_per_iteration as $scripts_at_priority) {
586
-            ksort($scripts_at_priority);
587
-            foreach ($scripts_at_priority as $scripts) {
588
-                foreach ($scripts as $script) {
589
-                    $scripts_that_should_run[ get_class($script) ] = $script;
590
-                }
591
-            }
592
-        }
593
-
594
-        do_action(
595
-            'AHEE__EE_Data_Migration_Manager__check_for_applicable_data_migration_scripts__scripts_that_should_run',
596
-            $scripts_that_should_run
597
-        );
598
-        return $scripts_that_should_run;
599
-    }
600
-
601
-
602
-    /**
603
-     * Gets the script which is currently being run, if there is one. If $include_completed_scripts is set to TRUE
604
-     * it will return the last run script even if it's complete.
605
-     * This means: if you want to find the currently-executing script, leave it as FALSE.
606
-     * If you really just want to find the script which ran most recently, regardless of status, leave it as TRUE.
607
-     *
608
-     * @param bool $include_completed_scripts
609
-     * @return EE_Data_Migration_Script_Base
610
-     * @throws EE_Error
611
-     */
612
-    public function get_last_ran_script($include_completed_scripts = false)
613
-    {
614
-        // make sure we've set up the class properties _last_ran_script and _last_ran_incomplete_script
615
-        if (! $this->_data_migrations_ran) {
616
-            $this->get_data_migrations_ran();
617
-        }
618
-        if ($include_completed_scripts) {
619
-            return $this->_last_ran_script;
620
-        } else {
621
-            return $this->_last_ran_incomplete_script;
622
-        }
623
-    }
624
-
625
-
626
-    /**
627
-     * Runs the data migration scripts (well, each request to this method calls one of the
628
-     * data migration scripts' migration_step() functions).
629
-     *
630
-     * @param int   $step_size
631
-     * @return array {
632
-     *                                  // where the first item is one EE_Data_Migration_Script_Base's stati,
633
-     *                                  //and the second item is a string describing what was done
634
-     * @type int    $records_to_migrate from the current migration script
635
-     * @type int    $records_migrated
636
-     * @type string $status             one of EE_Data_Migration_Manager::status_*
637
-     * @type string $script             verbose name of the current DMS
638
-     * @type string $message            string describing what was done during this step
639
-     *                                  }
640
-     * @throws EE_Error
641
-     */
642
-    public function migration_step($step_size = 0)
643
-    {
644
-        // bandaid fix for issue https://events.codebasehq.com/projects/event-espresso/tickets/7535
645
-        if (class_exists('EE_CPT_Strategy')) {
646
-            remove_action('pre_get_posts', [EE_CPT_Strategy::instance(), 'pre_get_posts'], 5);
647
-        }
648
-
649
-        try {
650
-            $currently_executing_script = $this->get_last_ran_script();
651
-            if (! $currently_executing_script) {
652
-                // Find the next script that needs to execute
653
-                $scripts = $this->check_for_applicable_data_migration_scripts();
654
-                if (! $scripts) {
655
-                    // huh, no more scripts to run... apparently we're done!
656
-                    // but don't forget to make sure initial data is there
657
-                    // we should be good to allow them to exit maintenance mode now
658
-                    EE_Maintenance_Mode::instance()->set_maintenance_level(
659
-                        EE_Maintenance_Mode::level_0_not_in_maintenance
660
-                    );
661
-                    // saving migrations run should actually be unnecessary,
662
-                    // but leaving in place just in case... remember this migration was finished
663
-                    // (even if we time out while initializing db for core and plugins)
664
-                    $this->_save_migrations_ran();
665
-                    // make sure DB was updated AFTER we've recorded the migration was done
666
-                    $this->initialize_db_for_enqueued_ee_plugins();
667
-                    return [
668
-                        'records_to_migrate' => 1,
669
-                        'records_migrated'   => 1,
670
-                        'status'             => self::status_no_more_migration_scripts,
671
-                        'script'             => esc_html__("Data Migration Completed Successfully", "event_espresso"),
672
-                        'message'            => esc_html__("All done!", "event_espresso"),
673
-                    ];
674
-                }
675
-                $currently_executing_script = array_shift($scripts);
676
-                // and add to the array/wp option showing the scripts run
677
-
678
-                $migrates_to                                            =
679
-                    $this->script_migrates_to_version(get_class($currently_executing_script));
680
-                $plugin_slug                                            = $migrates_to['slug'];
681
-                $version                                                = $migrates_to['version'];
682
-                $this->_data_migrations_ran[ $plugin_slug ][ $version ] = $currently_executing_script;
683
-            }
684
-            $current_script_name = get_class($currently_executing_script);
685
-        } catch (Exception $e) {
686
-            // an exception occurred while trying to get migration scripts
687
-
688
-            $message = sprintf(
689
-                esc_html__("Error Message: %sStack Trace:%s", "event_espresso"),
690
-                $e->getMessage() . '<br>',
691
-                $e->getTraceAsString()
692
-            );
693
-            // record it on the array of data migration scripts run. This will be overwritten next time we try and try to run data migrations
694
-            // but that's ok-- it's just an FYI to support that we couldn't even run any data migrations
695
-            $this->add_error_to_migrations_ran(
696
-                sprintf(esc_html__("Could not run data migrations because: %s", "event_espresso"), $message)
697
-            );
698
-            return [
699
-                'records_to_migrate' => 1,
700
-                'records_migrated'   => 0,
701
-                'status'             => self::status_fatal_error,
702
-                'script'             => esc_html__("Error loading data migration scripts", "event_espresso"),
703
-                'message'            => $message,
704
-            ];
705
-        }
706
-        // can we wrap it up and verify default data?
707
-        $init_dbs = false;
708
-        // ok so we definitely have a data migration script
709
-        try {
710
-            $init_dbs = false;
711
-            // how big of a bite do we want to take? Allow users to easily override via their wp-config
712
-            if (absint($step_size) < 1) {
713
-                $step_size = defined('EE_MIGRATION_STEP_SIZE') && absint(EE_MIGRATION_STEP_SIZE)
714
-                    ? EE_MIGRATION_STEP_SIZE
715
-                    : EE_Data_Migration_Manager::step_size;
716
-            }
717
-            // do what we came to do!
718
-            $currently_executing_script->migration_step($step_size);
719
-            switch ($currently_executing_script->get_status()) {
720
-                case EE_Data_Migration_Manager::status_continue:
721
-                    $response_array = [
722
-                        'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
723
-                        'records_migrated'   => $currently_executing_script->count_records_migrated(),
724
-                        'status'             => EE_Data_Migration_Manager::status_continue,
725
-                        'message'            => $currently_executing_script->get_feedback_message(),
726
-                        'script'             => $currently_executing_script->pretty_name(),
727
-                    ];
728
-                    break;
729
-                case EE_Data_Migration_Manager::status_completed:
730
-                    // ok so THAT script has completed
731
-                    $this->update_current_database_state_to($this->script_migrates_to_version($current_script_name));
732
-                    $response_array = [
733
-                        'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
734
-                        'records_migrated'   => $currently_executing_script->count_records_migrated(),
735
-                        'status'             => EE_Data_Migration_Manager::status_completed,
736
-                        'message'            => $currently_executing_script->get_feedback_message(),
737
-                        'script'             => sprintf(
738
-                            esc_html__("%s Completed", 'event_espresso'),
739
-                            $currently_executing_script->pretty_name()
740
-                        ),
741
-                    ];
742
-                    // check if there are any more after this one.
743
-                    $scripts_remaining = $this->check_for_applicable_data_migration_scripts();
744
-                    if (! $scripts_remaining) {
745
-                        // we should be good to allow them to exit maintenance mode now
746
-                        EE_Maintenance_Mode::instance()->set_maintenance_level(
747
-                            EE_Maintenance_Mode::level_0_not_in_maintenance
748
-                        );
749
-                        // huh, no more scripts to run... apparently we're done!
750
-                        // but don't forget to make sure initial data is there
751
-                        $init_dbs                 = true;
752
-                        $response_array['status'] = self::status_no_more_migration_scripts;
753
-                    }
754
-                    break;
755
-                default:
756
-                    $response_array = [
757
-                        'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
758
-                        'records_migrated'   => $currently_executing_script->count_records_migrated(),
759
-                        'status'             => $currently_executing_script->get_status(),
760
-                        'message'            => sprintf(
761
-                            esc_html__("Minor errors occurred during %s: %s", "event_espresso"),
762
-                            $currently_executing_script->pretty_name(),
763
-                            implode(", ", $currently_executing_script->get_errors())
764
-                        ),
765
-                        'script'             => $currently_executing_script->pretty_name(),
766
-                    ];
767
-                    break;
768
-            }
769
-        } catch (Exception $e) {
770
-            // ok so some exception was thrown which killed the data migration script
771
-            // double-check we have a real script
772
-            if ($currently_executing_script instanceof EE_Data_Migration_Script_Base) {
773
-                $script_name = $currently_executing_script->pretty_name();
774
-                $currently_executing_script->set_broken();
775
-                $currently_executing_script->add_error($e->getMessage());
776
-            } else {
777
-                $script_name = esc_html__("Error getting Migration Script", "event_espresso");
778
-            }
779
-            $response_array = [
780
-                'records_to_migrate' => 1,
781
-                'records_migrated'   => 0,
782
-                'status'             => self::status_fatal_error,
783
-                'message'            => sprintf(
784
-                    esc_html__("A fatal error occurred during the migration: %s", "event_espresso"),
785
-                    $e->getMessage()
786
-                ),
787
-                'script'             => $script_name,
788
-            ];
789
-        }
790
-        $successful_save = $this->_save_migrations_ran();
791
-        if ($successful_save !== true) {
792
-            // ok so the current wp option didn't save. that's tricky, because we'd like to update it
793
-            // and mark it as having a fatal error, but remember- WE CAN'T SAVE THIS WP OPTION!
794
-            // however, if we throw an exception, and return that, then the next request
795
-            // won't have as much info in it, and it may be able to save
796
-            throw new EE_Error(
797
-                sprintf(
798
-                    esc_html__(
799
-                        "The error '%s' occurred updating the status of the migration. This is a FATAL ERROR, but the error is preventing the system from remembering that. Please contact event espresso support.",
800
-                        "event_espresso"
801
-                    ),
802
-                    $successful_save
803
-                )
804
-            );
805
-        }
806
-        // if we're all done, initialize EE plugins' default data etc.
807
-        if ($init_dbs) {
808
-            $this->initialize_db_for_enqueued_ee_plugins();
809
-        }
810
-        return $response_array;
811
-    }
812
-
813
-
814
-    /**
815
-     * Echo out JSON response to migration script AJAX requests. Takes precautions
816
-     * to buffer output so that we don't throw junk into our json.
817
-     *
818
-     * @return array with keys:
819
-     * 'records_to_migrate' which counts ALL the records for the current migration, and should remain constant. (ie,
820
-     * it's NOT the count of hwo many remain)
821
-     * 'records_migrated' which also counts ALL the records which have been migrated (ie, percent_complete =
822
-     * records_migrated/records_to_migrate)
823
-     * 'status'=>a string, one of EE_Data_migration_Manager::status_*
824
-     * 'message'=>a string, containing any message you want to show to the user. We may decide to split this up into
825
-     * errors, notifications, and successes
826
-     * 'script'=>a pretty name of the script currently running
827
-     */
828
-    public function response_to_migration_ajax_request()
829
-    {
830
-        ob_start();
831
-        try {
832
-            $response = $this->migration_step();
833
-        } catch (Exception $e) {
834
-            $response = [
835
-                'records_to_migrate' => 0,
836
-                'records_migrated'   => 0,
837
-                'status'             => EE_Data_Migration_Manager::status_fatal_error,
838
-                'message'            => sprintf(
839
-                    esc_html__("Unknown fatal error occurred: %s", "event_espresso"),
840
-                    $e->getMessage()
841
-                ),
842
-                'script'             => 'Unknown',
843
-            ];
844
-            $this->add_error_to_migrations_ran($e->getMessage() . "; Stack trace:" . $e->getTraceAsString());
845
-        }
846
-        $warnings_etc = @ob_get_contents();
847
-        ob_end_clean();
848
-        $response['message'] .= $warnings_etc;
849
-        return $response;
850
-    }
851
-
852
-
853
-    /**
854
-     * Updates the WordPress option that keeps track of which EE version the database
855
-     * is at (ie, the code may be at 4.1.0, but the database is still at 3.1.35)
856
-     *
857
-     * @param array $slug_and_version {
858
-     * @type string $slug             like 'Core' or 'Calendar',
859
-     * @type string $version          like '4.1.0'
860
-     *                                }
861
-     * @return void
862
-     */
863
-    public function update_current_database_state_to($slug_and_version = null)
864
-    {
865
-        if (! $slug_and_version) {
866
-            // no version was provided, assume it should be at the current code version
867
-            $slug_and_version = ['slug' => 'Core', 'version' => espresso_version()];
868
-        }
869
-        $current_database_state                              = get_option(self::current_database_state, []);
870
-        $current_database_state[ $slug_and_version['slug'] ] = $slug_and_version['version'];
871
-        update_option(self::current_database_state, $current_database_state);
872
-    }
873
-
874
-
875
-    /**
876
-     * Determines if the database is currently at a state matching what's indicated in $slug and $version.
877
-     *
878
-     * @param array $slug_and_version {
879
-     * @type string $slug             like 'Core' or 'Calendar',
880
-     * @type string $version          like '4.1.0'
881
-     *                                }
882
-     * @return boolean
883
-     */
884
-    public function database_needs_updating_to($slug_and_version)
885
-    {
886
-        $slug                   = $slug_and_version['slug'];
887
-        $version                = $slug_and_version['version'];
888
-        $current_database_state = get_option(self::current_database_state, []);
889
-        if (! isset($current_database_state[ $slug ])) {
890
-            return true;
891
-        }
892
-        // just compare the first 3 parts of version string, eg "4.7.1", not "4.7.1.dev.032" because DBs shouldn't change on nano version changes
893
-        $version_parts_current_db_state     = array_slice(explode('.', $current_database_state[ $slug ]), 0, 3);
894
-        $version_parts_of_provided_db_state = array_slice(explode('.', $version), 0, 3);
895
-        $needs_updating                     = false;
896
-        foreach ($version_parts_current_db_state as $offset => $version_part_in_current_db_state) {
897
-            if ($version_part_in_current_db_state < $version_parts_of_provided_db_state[ $offset ]) {
898
-                $needs_updating = true;
899
-                break;
900
-            }
901
-        }
902
-        return $needs_updating;
903
-    }
904
-
905
-
906
-    /**
907
-     * Gets all the data migration scripts available in the core folder and folders
908
-     * in addons. Has the side effect of adding them for autoloading
909
-     *
910
-     * @return array keys are expected classnames, values are their filepaths
911
-     * @throws InvalidInterfaceException
912
-     * @throws InvalidDataTypeException
913
-     * @throws EE_Error
914
-     * @throws InvalidArgumentException
915
-     */
916
-    public function get_all_data_migration_scripts_available()
917
-    {
918
-        if (! $this->_data_migration_class_to_filepath_map) {
919
-            $this->_data_migration_class_to_filepath_map = [];
920
-            foreach ($this->get_data_migration_script_folders() as $eeAddonClass => $folder_path) {
921
-                // strip any placeholders added to classname to make it a unique array key
922
-                $eeAddonClass = trim($eeAddonClass, '*');
923
-                $eeAddonClass = $eeAddonClass === 'Core' || class_exists($eeAddonClass)
924
-                    ? $eeAddonClass
925
-                    : '';
926
-                $folder_path  = EEH_File::end_with_directory_separator($folder_path);
927
-                $files        = glob($folder_path . '*.dms.php');
928
-                if (empty($files)) {
929
-                    continue;
930
-                }
931
-                natsort($files);
932
-                foreach ($files as $file) {
933
-                    $pos_of_last_slash = strrpos($file, '/');
934
-                    $classname         = str_replace('.dms.php', '', substr($file, $pos_of_last_slash + 1));
935
-                    $migrates_to       = $this->script_migrates_to_version($classname, $eeAddonClass);
936
-                    $slug              = $migrates_to['slug'];
937
-                    // check that the slug as contained in the DMS is associated with
938
-                    // the slug of an addon or core
939
-                    if ($slug !== 'Core' && EE_Registry::instance()->get_addon_by_name($slug) === null) {
940
-                        EE_Error::doing_it_wrong(
941
-                            __FUNCTION__,
942
-                            sprintf(
943
-                                esc_html__(
944
-                                    'The data migration script "%s" migrates the "%s" data, but there is no EE addon with that name. There is only: %s. ',
945
-                                    'event_espresso'
946
-                                ),
947
-                                $classname,
948
-                                $slug,
949
-                                implode(', ', array_keys(EE_Registry::instance()->get_addons_by_name()))
950
-                            ),
951
-                            '4.3.0.alpha.019'
952
-                        );
953
-                    }
954
-                    $this->_data_migration_class_to_filepath_map[ $classname ] = $file;
955
-                }
956
-            }
957
-            EEH_Autoloader::register_autoloader($this->_data_migration_class_to_filepath_map);
958
-        }
959
-        return $this->_data_migration_class_to_filepath_map;
960
-    }
961
-
962
-
963
-    /**
964
-     * Once we have an addon that works with EE4.1, we will actually want to fetch the PUE slugs
965
-     * from each addon, and check if they need updating,
966
-     *
967
-     * @return boolean
968
-     */
969
-    public function addons_need_updating()
970
-    {
971
-        return false;
972
-    }
973
-
974
-
975
-    /**
976
-     * Adds this error string to the data_migrations_ran array, but we don't necessarily know
977
-     * where to put it, so we just throw it in there... better than nothing...
978
-     *
979
-     * @param string $error_message
980
-     */
981
-    public function add_error_to_migrations_ran($error_message)
982
-    {
983
-        // get last-run migration script
984
-        global $wpdb;
985
-        $last_migration_script_option = $wpdb->get_row(
986
-            "SELECT * FROM $wpdb->options WHERE option_name like '"
987
-            . EE_Data_Migration_Manager::data_migration_script_option_prefix
988
-            . "%' ORDER BY option_id DESC LIMIT 1",
989
-            ARRAY_A
990
-        );
991
-
992
-        $last_ran_migration_script_properties = isset($last_migration_script_option['option_value'])
993
-            ? maybe_unserialize($last_migration_script_option['option_value'])
994
-            : null;
995
-        // now, tread lightly because we're here because a FATAL non-catchable error
996
-        // was thrown last time when we were trying to run a data migration script
997
-        // so the fatal error could have happened while getting the migration script
998
-        // or doing running it...
999
-        $versions_migrated_to = isset($last_migration_script_option['option_name'])
1000
-            ? str_replace(
1001
-                EE_Data_Migration_Manager::data_migration_script_option_prefix,
1002
-                "",
1003
-                $last_migration_script_option['option_name']
1004
-            )
1005
-            : null;
1006
-
1007
-        // check if it THINKS it's a data migration script and especially if it's one that HASN'T finished yet
1008
-        // because if it has finished, then it obviously couldn't be the cause of this error, right? (because it's all done)
1009
-        if (
1010
-            isset($last_ran_migration_script_properties['class'])
1011
-            && isset($last_ran_migration_script_properties['_status'])
1012
-            && $last_ran_migration_script_properties['_status'] != self::status_completed
1013
-        ) {
1014
-            // ok then just add this error to its list of errors
1015
-            $last_ran_migration_script_properties['_errors'][] = $error_message;
1016
-            $last_ran_migration_script_properties['_status']   = self::status_fatal_error;
1017
-        } else {
1018
-            // so we don't even know which script was last running
1019
-            // use the data migration error stub, which is designed specifically for this type of thing
1020
-            $general_migration_error = new EE_DMS_Unknown_1_0_0();
1021
-            $general_migration_error->add_error($error_message);
1022
-            $general_migration_error->set_broken();
1023
-            $last_ran_migration_script_properties = $general_migration_error->properties_as_array();
1024
-            $versions_migrated_to                 = 'Unknown.1.0.0';
1025
-            // now just to make sure appears as last (in case the were previously a fatal error like this)
1026
-            // delete the old one
1027
-            delete_option(self::data_migration_script_option_prefix . $versions_migrated_to);
1028
-        }
1029
-        update_option(
1030
-            self::data_migration_script_option_prefix . $versions_migrated_to,
1031
-            $last_ran_migration_script_properties
1032
-        );
1033
-    }
1034
-
1035
-
1036
-    /**
1037
-     * saves what data migrations have run to the database
1038
-     *
1039
-     * @return bool|string TRUE if successfully saved migrations ran, string if an error occurred
1040
-     * @throws EE_Error
1041
-     */
1042
-    protected function _save_migrations_ran()
1043
-    {
1044
-        if ($this->_data_migrations_ran == null) {
1045
-            $this->get_data_migrations_ran();
1046
-        }
1047
-        // now, we don't want to save actual classes to the DB because that's messy
1048
-        $successful_updates = true;
1049
-        foreach ($this->_data_migrations_ran as $plugin_slug => $migrations_ran_for_plugin) {
1050
-            foreach ($migrations_ran_for_plugin as $version_string => $array_or_migration_obj) {
1051
-                $plugin_slug_for_use_in_option_name = $plugin_slug . ".";
1052
-                $option_name                        =
1053
-                    self::data_migration_script_option_prefix . $plugin_slug_for_use_in_option_name . $version_string;
1054
-                $old_option_value                   = get_option($option_name);
1055
-                if ($array_or_migration_obj instanceof EE_Data_Migration_Script_Base) {
1056
-                    $script_array_for_saving = $array_or_migration_obj->properties_as_array();
1057
-                    if ($old_option_value != $script_array_for_saving) {
1058
-                        $successful_updates = update_option($option_name, $script_array_for_saving);
1059
-                    }
1060
-                } else {// we don't know what this array-thing is. So just save it as-is
1061
-                    if ($old_option_value != $array_or_migration_obj) {
1062
-                        $successful_updates = update_option($option_name, $array_or_migration_obj);
1063
-                    }
1064
-                }
1065
-                if (! $successful_updates) {
1066
-                    global $wpdb;
1067
-                    return $wpdb->last_error;
1068
-                }
1069
-            }
1070
-        }
1071
-        return true;
1072
-        // $updated = update_option(self::data_migrations_option_name, $array_of_migrations);
1073
-        // if ($updated !== true) {
1074
-        //     global $wpdb;
1075
-        //     return $wpdb->last_error;
1076
-        // } else {
1077
-        //     return true;
1078
-        // }
1079
-        // wp_mail(
1080
-        //     "[email protected]",
1081
-        //     time() . " price debug info",
1082
-        //     "updated: $updated, last error: $last_error, byte length of option: " . strlen(
1083
-        //         serialize($array_of_migrations)
1084
-        //     )
1085
-        // );
1086
-    }
1087
-
1088
-
1089
-    /**
1090
-     * Takes an array of data migration script properties and re-creates the class from
1091
-     * them. The argument $properties_array is assumed to have been made by
1092
-     * EE_Data_Migration_Script_Base::properties_as_array()
1093
-     *
1094
-     * @param array $properties_array
1095
-     * @return EE_Data_Migration_Script_Base
1096
-     * @throws EE_Error
1097
-     */
1098
-    public function _instantiate_script_from_properties_array($properties_array)
1099
-    {
1100
-        if (! isset($properties_array['class'])) {
1101
-            throw new EE_Error(
1102
-                sprintf(
1103
-                    esc_html__("Properties array  has no 'class' properties. Here's what it has: %s", "event_espresso"),
1104
-                    implode(",", $properties_array)
1105
-                )
1106
-            );
1107
-        }
1108
-        $class_name = $properties_array['class'];
1109
-        if (! class_exists($class_name)) {
1110
-            throw new EE_Error(sprintf(
1111
-                esc_html__("There is no migration script named %s", "event_espresso"),
1112
-                $class_name
1113
-            ));
1114
-        }
1115
-        $class = new $class_name();
1116
-        if (! $class instanceof EE_Data_Migration_Script_Base) {
1117
-            throw new EE_Error(
1118
-                sprintf(
1119
-                    esc_html__(
1120
-                        "Class '%s' is supposed to be a migration script. Its not, its a '%s'",
1121
-                        "event_espresso"
1122
-                    ),
1123
-                    $class_name,
1124
-                    get_class($class)
1125
-                )
1126
-            );
1127
-        }
1128
-        $class->instantiate_from_array_of_properties($properties_array);
1129
-        return $class;
1130
-    }
1131
-
1132
-
1133
-    /**
1134
-     * Gets the classname for the most up-to-date DMS (ie, the one that will finally
1135
-     * leave the DB in a state usable by the current plugin code).
1136
-     *
1137
-     * @param string $plugin_slug the slug for the ee plugin we are searching for. Default is 'Core'
1138
-     * @return string
1139
-     * @throws EE_Error
1140
-     */
1141
-    public function get_most_up_to_date_dms($plugin_slug = 'Core')
1142
-    {
1143
-        $class_to_filepath_map         = $this->get_all_data_migration_scripts_available();
1144
-        $most_up_to_date_dms_classname = null;
1145
-        foreach ($class_to_filepath_map as $classname => $filepath) {
1146
-            if ($most_up_to_date_dms_classname === null) {
1147
-                $migrates_to      = $this->script_migrates_to_version($classname);
1148
-                $this_plugin_slug = $migrates_to['slug'];
1149
-                if ($this_plugin_slug == $plugin_slug) {
1150
-                    // if it's for core, it wins
1151
-                    $most_up_to_date_dms_classname = $classname;
1152
-                }
1153
-                // if it wasn't for core, we must keep searching for one that is!
1154
-                continue;
1155
-            }
1156
-            $champion_migrates_to  = $this->script_migrates_to_version($most_up_to_date_dms_classname);
1157
-            $contender_migrates_to = $this->script_migrates_to_version($classname);
1158
-            if (
1159
-                $contender_migrates_to['slug'] == $plugin_slug
1160
-                && version_compare(
1161
-                    $champion_migrates_to['version'],
1162
-                    $contender_migrates_to['version'],
1163
-                    '<'
1164
-                )
1165
-            ) {
1166
-                // so the contenders version is higher, and it's for Core
1167
-                $most_up_to_date_dms_classname = $classname;
1168
-            }
1169
-        }
1170
-        return $most_up_to_date_dms_classname;
1171
-    }
1172
-
1173
-
1174
-    /**
1175
-     * Gets the migration script specified but ONLY if it has already run.
1176
-     * Eg, if you wanted to see if 'EE_DMS_Core_4_1_0' has run, you would run the following code:
1177
-     * <code> $core_4_1_0_dms_ran = EE_Data_Migration_Manager::instance()->get_migration_ran( '4.1.0', 'Core' ) !==
1178
-     * NULL;</code> This is especially useful in addons' data migration scripts, this way they can tell if a core (or
1179
-     * other addon) DMS has run, in case the current DMS depends on it.
1180
-     *
1181
-     * @param string $version     the version the DMS searched for migrates to. Usually just the content before the 3rd
1182
-     *                            period. Eg '4.1.0'
1183
-     * @param string $plugin_slug like 'Core', 'Mailchimp', 'Calendar', etc
1184
-     * @return EE_Data_Migration_Script_Base
1185
-     * @throws EE_Error
1186
-     */
1187
-    public function get_migration_ran($version, $plugin_slug = 'Core')
1188
-    {
1189
-        $migrations_ran = $this->get_data_migrations_ran();
1190
-        if (isset($migrations_ran[ $plugin_slug ]) && isset($migrations_ran[ $plugin_slug ][ $version ])) {
1191
-            return $migrations_ran[ $plugin_slug ][ $version ];
1192
-        } else {
1193
-            return null;
1194
-        }
1195
-    }
1196
-
1197
-
1198
-    /**
1199
-     * Resets the borked data migration scripts, so they're no longer borked, and we can again attempt to migrate
1200
-     *
1201
-     * @return bool
1202
-     * @throws EE_Error
1203
-     */
1204
-    public function reattempt()
1205
-    {
1206
-        // find if the last-run script was borked
1207
-        // set it as being non-borked (we shouldn't ever get DMSs that we don't recognize)
1208
-        // add an 'error' saying that we attempted to reset
1209
-        // does it have a stage that was borked too? if so make it no longer borked
1210
-        // add an 'error' saying we attempted to reset
1211
-        $last_ran_script = $this->get_last_ran_script();
1212
-        if ($last_ran_script instanceof EE_DMS_Unknown_1_0_0) {
1213
-            // if it was an error DMS, just mark it as complete (if another error occurs it will overwrite it)
1214
-            $last_ran_script->set_completed();
1215
-        } elseif ($last_ran_script instanceof EE_Data_Migration_Script_Base) {
1216
-            $last_ran_script->reattempt();
1217
-        } else {
1218
-            throw new EE_Error(
1219
-                sprintf(
1220
-                    esc_html__(
1221
-                        'Unable to reattempt the last ran migration script because it was not a valid migration script. || It was %s',
1222
-                        'event_espresso'
1223
-                    ),
1224
-                    print_r($last_ran_script, true)
1225
-                )
1226
-            );
1227
-        }
1228
-        return $this->_save_migrations_ran();
1229
-    }
1230
-
1231
-
1232
-    /**
1233
-     * Gets whether this particular migration has run or not
1234
-     *
1235
-     * @param string $version     the version the DMS searched for migrates to. Usually just the content before the 3rd
1236
-     *                            period. Eg '4.1.0'
1237
-     * @param string $plugin_slug like 'Core', 'Mailchimp', 'Calendar', etc
1238
-     * @return boolean
1239
-     * @throws EE_Error
1240
-     */
1241
-    public function migration_has_ran($version, $plugin_slug = 'Core')
1242
-    {
1243
-        return $this->get_migration_ran($version, $plugin_slug) !== null;
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * Enqueues this ee plugin to have its data initialized
1249
-     *
1250
-     * @param string $plugin_slug either 'Core' or EE_Addon::name()'s return value
1251
-     */
1252
-    public function enqueue_db_initialization_for($plugin_slug)
1253
-    {
1254
-        $queue = $this->get_db_initialization_queue();
1255
-        if (! in_array($plugin_slug, $queue)) {
1256
-            $queue[] = $plugin_slug;
1257
-        }
1258
-        update_option(self::db_init_queue_option_name, $queue);
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Calls EE_Addon::initialize_db_if_no_migrations_required() on each addon
1264
-     * specified in EE_Data_Migration_Manager::get_db_init_queue(), and if 'Core' is
1265
-     * in the queue, calls EE_System::initialize_db_if_no_migrations_required().
1266
-     *
1267
-     * @throws EE_Error
1268
-     */
1269
-    public function initialize_db_for_enqueued_ee_plugins()
1270
-    {
1271
-        $queue = $this->get_db_initialization_queue();
1272
-        foreach ($queue as $plugin_slug) {
1273
-            $most_up_to_date_dms = $this->get_most_up_to_date_dms($plugin_slug);
1274
-            if (! $most_up_to_date_dms) {
1275
-                // if there is NO DMS for this plugin, obviously there's no schema to verify anyways
1276
-                $verify_db = false;
1277
-            } else {
1278
-                $most_up_to_date_dms_migrates_to = $this->script_migrates_to_version($most_up_to_date_dms);
1279
-                $verify_db                       = $this->database_needs_updating_to($most_up_to_date_dms_migrates_to);
1280
-            }
1281
-            if ($plugin_slug == 'Core') {
1282
-                EE_System::instance()->initialize_db_if_no_migrations_required(
1283
-                    false,
1284
-                    $verify_db
1285
-                );
1286
-            } else {
1287
-                // just loop through the addons to make sure their database is set up
1288
-                foreach (EE_Registry::instance()->addons as $addon) {
1289
-                    if ($addon->name() == $plugin_slug) {
1290
-                        $addon->initialize_db_if_no_migrations_required($verify_db);
1291
-                        break;
1292
-                    }
1293
-                }
1294
-            }
1295
-        }
1296
-        // because we just initialized the DBs for the enqueued ee plugins
1297
-        // we don't need to keep remembering which ones needed to be initialized
1298
-        delete_option(self::db_init_queue_option_name);
1299
-    }
1300
-
1301
-
1302
-    /**
1303
-     * Gets a numerically-indexed array of plugin slugs that need to have their databases
1304
-     * (re-)initialized after migrations are complete. ie, each element should be either
1305
-     * 'Core', or the return value of EE_Addon::name() for an addon
1306
-     *
1307
-     * @return array
1308
-     */
1309
-    public function get_db_initialization_queue()
1310
-    {
1311
-        return get_option(self::db_init_queue_option_name, []);
1312
-    }
1313
-
1314
-
1315
-    /**
1316
-     * Gets the injected table analyzer, or throws an exception
1317
-     *
1318
-     * @return TableAnalysis
1319
-     * @throws EE_Error
1320
-     */
1321
-    protected function _get_table_analysis()
1322
-    {
1323
-        if ($this->_table_analysis instanceof TableAnalysis) {
1324
-            return $this->_table_analysis;
1325
-        } else {
1326
-            throw new EE_Error(
1327
-                sprintf(
1328
-                    esc_html__('Table analysis class on class %1$s is not set properly.', 'event_espresso'),
1329
-                    get_class($this)
1330
-                )
1331
-            );
1332
-        }
1333
-    }
1334
-
1335
-
1336
-    /**
1337
-     * Gets the injected table manager, or throws an exception
1338
-     *
1339
-     * @return TableManager
1340
-     * @throws EE_Error
1341
-     */
1342
-    protected function _get_table_manager()
1343
-    {
1344
-        if ($this->_table_manager instanceof TableManager) {
1345
-            return $this->_table_manager;
1346
-        } else {
1347
-            throw new EE_Error(
1348
-                sprintf(
1349
-                    esc_html__('Table manager class on class %1$s is not set properly.', 'event_espresso'),
1350
-                    get_class($this)
1351
-                )
1352
-            );
1353
-        }
1354
-    }
31
+	/**
32
+	 * @var EE_Registry
33
+	 */
34
+	// protected $EE;
35
+	/**
36
+	 * name of the WordPress option which stores an array of data about
37
+	 */
38
+	const data_migrations_option_name = 'ee_data_migration';
39
+
40
+
41
+	const data_migration_script_option_prefix         = 'ee_data_migration_script_';
42
+
43
+	const data_migration_script_mapping_option_prefix = 'ee_dms_map_';
44
+
45
+	/**
46
+	 * name of the WordPress option which stores the database' current version. IE, the code may be at version 4.2.0,
47
+	 * but as migrations are performed the database will progress from 3.1.35 to 4.1.0 etc.
48
+	 */
49
+	const current_database_state = 'ee_data_migration_current_db_state';
50
+
51
+	/**
52
+	 * Special status string returned when we're positive there are no more data migration
53
+	 * scripts that can be run.
54
+	 */
55
+	const status_no_more_migration_scripts = 'no_more_migration_scripts';
56
+
57
+	/**
58
+	 * string indicating the migration should continue
59
+	 */
60
+	const status_continue = 'status_continue';
61
+
62
+	/**
63
+	 * string indicating the migration has completed and should be ended
64
+	 */
65
+	const status_completed = 'status_completed';
66
+
67
+	/**
68
+	 * string indicating a fatal error occurred and the data migration should be completely aborted
69
+	 */
70
+	const status_fatal_error = 'status_fatal_error';
71
+
72
+	/**
73
+	 * the number of 'items' (usually DB rows) to migrate on each 'step' (ajax request sent
74
+	 * during migration)
75
+	 */
76
+	const step_size = 50;
77
+
78
+	/**
79
+	 * option name that stores the queue of ee plugins needing to have
80
+	 * their data initialized (or re-initialized) once we are done migrations
81
+	 */
82
+	const db_init_queue_option_name = 'ee_db_init_queue';
83
+
84
+	/**
85
+	 * Array of information concerning data migrations that have run in the history
86
+	 * of this EE installation. Keys should be the name of the version the script upgraded to
87
+	 *
88
+	 * @var EE_Data_Migration_Script_Base[]
89
+	 */
90
+	private $_data_migrations_ran = null;
91
+
92
+	/**
93
+	 * The last run script. It's nice to store this somewhere accessible, as its easiest
94
+	 * to know which was the last run by which is the newest wp option; but in most of the code
95
+	 * we just use the local $_data_migration_ran array, which organized the scripts differently
96
+	 *
97
+	 * @var EE_Data_Migration_Script_Base
98
+	 */
99
+	private $_last_ran_script = null;
100
+
101
+	/**
102
+	 * Similarly to _last_ran_script, but this is the last INCOMPLETE migration script.
103
+	 *
104
+	 * @var EE_Data_Migration_Script_Base
105
+	 */
106
+	private $_last_ran_incomplete_script = null;
107
+
108
+	/**
109
+	 * array where keys are classnames, and values are filepaths of all the known migration scripts
110
+	 *
111
+	 * @var array
112
+	 */
113
+	private $_data_migration_class_to_filepath_map;
114
+
115
+	/**
116
+	 * the following 4 properties are fully set on construction.
117
+	 * Note: the first two apply to whether to continue running ALL migration scripts (ie, even though we're finished
118
+	 * one, we may want to start the next one); whereas the last two indicate whether to continue running a single
119
+	 * data migration script
120
+	 *
121
+	 * @var array
122
+	 */
123
+	public $stati_that_indicate_to_continue_migrations              = [];
124
+
125
+	public $stati_that_indicate_to_stop_migrations                  = [];
126
+
127
+	public $stati_that_indicate_to_continue_single_migration_script = [];
128
+
129
+	public $stati_that_indicate_to_stop_single_migration_script     = [];
130
+
131
+	/**
132
+	 * @var TableManager $table_manager
133
+	 */
134
+	protected $_table_manager;
135
+
136
+	/**
137
+	 * @var TableAnalysis $table_analysis
138
+	 */
139
+	protected $_table_analysis;
140
+
141
+	/**
142
+	 * @var array $script_migration_versions
143
+	 */
144
+	protected $script_migration_versions;
145
+
146
+	/**
147
+	 * @var array $dms_folders
148
+	 */
149
+	protected $dms_folders;
150
+
151
+	/**
152
+	 * @var EE_Data_Migration_Manager $_instance
153
+	 * @access    private
154
+	 */
155
+	private static $_instance = null;
156
+
157
+
158
+	/**
159
+	 * @singleton method used to instantiate class object
160
+	 * @access    public
161
+	 * @return EE_Data_Migration_Manager instance
162
+	 */
163
+	public static function instance()
164
+	{
165
+		// check if class object is instantiated
166
+		if (! self::$_instance instanceof EE_Data_Migration_Manager) {
167
+			self::$_instance = new self();
168
+		}
169
+		return self::$_instance;
170
+	}
171
+
172
+
173
+	/**
174
+	 * resets the singleton to its brand-new state (but does NOT delete old references to the old singleton. Meaning,
175
+	 * all new usages of the singleton should be made with Classname::instance()) and returns it
176
+	 *
177
+	 * @return EE_Data_Migration_Manager
178
+	 */
179
+	public static function reset()
180
+	{
181
+		self::$_instance = new self();
182
+		return self::instance();
183
+	}
184
+
185
+
186
+	/**
187
+	 * @throws EE_Error
188
+	 * @throws ReflectionException
189
+	 */
190
+	private function __construct()
191
+	{
192
+		$this->stati_that_indicate_to_continue_migrations              = [
193
+			self::status_continue,
194
+			self::status_completed,
195
+		];
196
+		$this->stati_that_indicate_to_stop_migrations                  = [
197
+			self::status_fatal_error,
198
+			self::status_no_more_migration_scripts,
199
+		];
200
+		$this->stati_that_indicate_to_continue_single_migration_script = [
201
+			self::status_continue,
202
+		];
203
+		$this->stati_that_indicate_to_stop_single_migration_script     = [
204
+			self::status_completed,
205
+			self::status_fatal_error,
206
+			// note: status_no_more_migration_scripts doesn't apply
207
+		];
208
+		$this->dms_folders                                             = [];
209
+		// make sure we've included the base migration script, because we may need the EE_DMS_Unknown_1_0_0 class
210
+		// to be defined, because right now it doesn't get autoloaded on its own
211
+		EE_Registry::instance()->load_core('Data_Migration_Class_Base', [], true);
212
+		EE_Registry::instance()->load_core('Data_Migration_Script_Base', [], true);
213
+		EE_Registry::instance()->load_core('DMS_Unknown_1_0_0', [], true);
214
+		EE_Registry::instance()->load_core('Data_Migration_Script_Stage', [], true);
215
+		EE_Registry::instance()->load_core('Data_Migration_Script_Stage_Table', [], true);
216
+		$this->_table_manager  = EE_Registry::instance()->create('TableManager', [], true);
217
+		$this->_table_analysis = EE_Registry::instance()->create('TableAnalysis', [], true);
218
+	}
219
+
220
+
221
+	/**
222
+	 * Deciphers, from an option's name, what plugin and version it relates to (see _save_migrations_ran to see what
223
+	 * the option names are like, but generally they're like
224
+	 * 'ee_data_migration_script_Core.4.1.0' in 4.2 or 'ee_data_migration_script_4.1.0' before that).
225
+	 * The option name shouldn't ever be like 'ee_data_migration_script_Core.4.1.0.reg' because it's derived,
226
+	 * indirectly, from the data migration's classname, which should always be like EE_DMS_%s_%d_%d_%d.dms.php
227
+	 * (ex: EE_DMS_Core_4_1_0.dms.php)
228
+	 *
229
+	 * @param string $option_name (see EE_Data_Migration_Manage::_save_migrations_ran() where the option name is set)
230
+	 * @return array where the first item is the plugin slug (eg 'Core','Calendar', etc.) and the 2nd is the version of
231
+	 *                            that plugin (eg '4.1.0')
232
+	 */
233
+	private function _get_plugin_slug_and_version_string_from_dms_option_name($option_name)
234
+	{
235
+		$plugin_slug_and_version_string = str_replace(
236
+			EE_Data_Migration_Manager::data_migration_script_option_prefix,
237
+			"",
238
+			$option_name
239
+		);
240
+		// check if $plugin_slug_and_version_string is like '4.1.0' (4.1-style) or 'Core.4.1.0' (4.2-style)
241
+		$parts = explode(".", $plugin_slug_and_version_string);
242
+
243
+		if (count($parts) == 4) {
244
+			// it's 4.2-style.eg Core.4.1.0
245
+			$plugin_slug    = $parts[0];                                     // eg Core
246
+			$version_string = $parts[1] . "." . $parts[2] . "." . $parts[3]; // eg 4.1.0
247
+		} else {
248
+			// it's 4.1-style: eg 4.1.0
249
+			$plugin_slug    = 'Core';
250
+			$version_string = $plugin_slug_and_version_string;// eg 4.1.0
251
+		}
252
+		return [$plugin_slug, $version_string];
253
+	}
254
+
255
+
256
+	/**
257
+	 * Gets the DMS class from the WordPress option, otherwise throws an EE_Error if it's not
258
+	 * for a known DMS class.
259
+	 *
260
+	 * @param string $dms_option_name
261
+	 * @param string $dms_option_value (serialized)
262
+	 * @return EE_Data_Migration_Script_Base
263
+	 * @throws EE_Error
264
+	 */
265
+	private function _get_dms_class_from_wp_option($dms_option_name, $dms_option_value)
266
+	{
267
+		$data_migration_data = maybe_unserialize($dms_option_value);
268
+		if (isset($data_migration_data['class']) && class_exists($data_migration_data['class'])) {
269
+			// During multisite migrations, it's possible we already grabbed another instance of this class
270
+			// but for a different blog. Make sure we don't reuse them (as they store info specific
271
+			// to their respective blog, like which database table to migrate).
272
+			$class = LoaderFactory::getLoader()->getNew($data_migration_data['class']);
273
+			if ($class instanceof EE_Data_Migration_Script_Base) {
274
+				$class->instantiate_from_array_of_properties($data_migration_data);
275
+				return $class;
276
+			} else {
277
+				// huh, so it's an object but not a data migration script?? that shouldn't happen
278
+				// just leave it as an array (which will probably just get ignored)
279
+				throw new EE_Error(
280
+					sprintf(
281
+						esc_html__(
282
+							"Trying to retrieve DMS class from wp option. No DMS by the name '%s' exists",
283
+							'event_espresso'
284
+						),
285
+						$data_migration_data['class']
286
+					)
287
+				);
288
+			}
289
+		} else {
290
+			// so the data doesn't specify a class. So it must either be a legacy array of info or some array (which we'll probably just ignore), or a class that no longer exists
291
+			throw new EE_Error(
292
+				sprintf(
293
+					esc_html__("The wp option  with key '%s' does not represent a DMS", 'event_espresso'),
294
+					$dms_option_name
295
+				)
296
+			);
297
+		}
298
+	}
299
+
300
+
301
+	/**
302
+	 * Gets the array describing what data migrations have run.
303
+	 * Also has a side effect of recording which was the last run,
304
+	 * and which was the last run which hasn't finished yet
305
+	 *
306
+	 * @return array where each element should be an array of EE_Data_Migration_Script_Base
307
+	 *               (but also has a few legacy arrays in there - which should probably be ignored)
308
+	 * @throws EE_Error
309
+	 */
310
+	public function get_data_migrations_ran()
311
+	{
312
+		if (! $this->_data_migrations_ran) {
313
+			// setup autoloaders for each of the scripts in there
314
+			$this->get_all_data_migration_scripts_available();
315
+			$data_migrations_options =
316
+				$this->get_all_migration_script_options();// get_option(EE_Data_Migration_Manager::data_migrations_option_name,get_option('espresso_data_migrations',array()));
317
+
318
+			$data_migrations_ran = [];
319
+			// convert into data migration script classes where possible
320
+			foreach ($data_migrations_options as $data_migration_option) {
321
+				[$plugin_slug, $version_string] = $this->_get_plugin_slug_and_version_string_from_dms_option_name(
322
+					$data_migration_option['option_name']
323
+				);
324
+
325
+				try {
326
+					$class                                                  = $this->_get_dms_class_from_wp_option(
327
+						$data_migration_option['option_name'],
328
+						$data_migration_option['option_value']
329
+					);
330
+					$data_migrations_ran[ $plugin_slug ][ $version_string ] = $class;
331
+					// ok so far THIS is the 'last-run-script'... unless we find another on next iteration
332
+					$this->_last_ran_script = $class;
333
+					if (! $class->is_completed()) {
334
+						// sometimes we also like to know which was the last incomplete script (or if there are any at all)
335
+						$this->_last_ran_incomplete_script = $class;
336
+					}
337
+				} catch (EE_Error $e) {
338
+					// ok so it's not a DMS. We'll just keep it, although other code will need to expect non-DMSs
339
+					$data_migrations_ran[ $plugin_slug ][ $version_string ] = maybe_unserialize(
340
+						$data_migration_option['option_value']
341
+					);
342
+				}
343
+			}
344
+			// so here the array of $data_migrations_ran is actually a mix of classes and a few legacy arrays
345
+			$this->_data_migrations_ran = $data_migrations_ran;
346
+			if (! $this->_data_migrations_ran || ! is_array($this->_data_migrations_ran)) {
347
+				$this->_data_migrations_ran = [];
348
+			}
349
+		}
350
+		return $this->_data_migrations_ran;
351
+	}
352
+
353
+
354
+	/**
355
+	 * @param string $script_name eg 'DMS_Core_4_1_0'
356
+	 * @param string $old_table   eg 'wp_events_detail'
357
+	 * @param string $old_pk      eg 'wp_esp_posts'
358
+	 * @param        $new_table
359
+	 * @return mixed string or int
360
+	 * @throws EE_Error
361
+	 * @throws ReflectionException
362
+	 */
363
+	public function get_mapping_new_pk($script_name, $old_table, $old_pk, $new_table)
364
+	{
365
+		$script = EE_Registry::instance()->load_dms($script_name);
366
+		return $script->get_mapping_new_pk($old_table, $old_pk, $new_table);
367
+	}
368
+
369
+
370
+	/**
371
+	 * Gets all the options containing migration scripts that have been run. Ordering is important: it's assumed that
372
+	 * the last option returned in this array is the most-recently run DMS option
373
+	 *
374
+	 * @return array
375
+	 */
376
+	public function get_all_migration_script_options()
377
+	{
378
+		global $wpdb;
379
+		return $wpdb->get_results(
380
+			"SELECT * FROM {$wpdb->options} WHERE option_name like '"
381
+			. EE_Data_Migration_Manager::data_migration_script_option_prefix
382
+			. "%' ORDER BY option_id ASC",
383
+			ARRAY_A
384
+		);
385
+	}
386
+
387
+
388
+	/**
389
+	 * Gets the array of folders which contain data migration scripts. Also adds them to be auto-loaded
390
+	 *
391
+	 * @return array where each value is the full folder path of a folder containing data migration scripts, WITH
392
+	 *               slashes at the end of the folder name.
393
+	 */
394
+	public function get_data_migration_script_folders()
395
+	{
396
+		if (empty($this->dms_folders)) {
397
+			$this->dms_folders = (array) apply_filters(
398
+				'FHEE__EE_Data_Migration_Manager__get_data_migration_script_folders',
399
+				['Core' => EE_CORE . 'data_migration_scripts']
400
+			);
401
+		}
402
+		return $this->dms_folders;
403
+	}
404
+
405
+
406
+	/**
407
+	 * Gets the version the migration script upgrades to
408
+	 *
409
+	 * @param string $migration_script_name eg 'EE_DMS_Core_4_1_0'
410
+	 * @return array {
411
+	 * @type string  $slug                  like 'Core','Calendar',etc
412
+	 * @type string  $version               like 4.3.0
413
+	 *                                      }
414
+	 * @throws EE_Error
415
+	 */
416
+	public function script_migrates_to_version($migration_script_name, $eeAddonClass = '')
417
+	{
418
+		if (isset($this->script_migration_versions[ $migration_script_name ])) {
419
+			return $this->script_migration_versions[ $migration_script_name ];
420
+		}
421
+		$dms_info                                                  = $this->parse_dms_classname($migration_script_name);
422
+		$this->script_migration_versions[ $migration_script_name ] = [
423
+			'slug'    => $eeAddonClass !== ''
424
+				? $eeAddonClass
425
+				: $dms_info['slug'],
426
+			'version' => $dms_info['major_version']
427
+						 . "."
428
+						 . $dms_info['minor_version']
429
+						 . "."
430
+						 . $dms_info['micro_version'],
431
+		];
432
+		return $this->script_migration_versions[ $migration_script_name ];
433
+	}
434
+
435
+
436
+	/**
437
+	 * Gets the juicy details out of a dms filename like 'EE_DMS_Core_4_1_0'
438
+	 *
439
+	 * @param string $classname
440
+	 * @return array with keys 'slug','major_version','minor_version', and 'micro_version' (the last 3 are integers)
441
+	 * @throws EE_Error
442
+	 */
443
+	public function parse_dms_classname($classname)
444
+	{
445
+		$matches = [];
446
+		preg_match('~EE_DMS_(.*)_([0-9]*)_([0-9]*)_([0-9]*)~', $classname, $matches);
447
+		if (! $matches || ! (isset($matches[1]) && isset($matches[2]) && isset($matches[3]))) {
448
+			throw new EE_Error(
449
+				sprintf(
450
+					esc_html__(
451
+						"%s is not a valid Data Migration Script. The classname should be like EE_DMS_w_x_y_z, where w is either 'Core' or the slug of an addon and x, y and z are numbers, ",
452
+						"event_espresso"
453
+					),
454
+					$classname
455
+				)
456
+			);
457
+		}
458
+		return [
459
+			'slug'          => $matches[1],
460
+			'major_version' => intval($matches[2]),
461
+			'minor_version' => intval($matches[3]),
462
+			'micro_version' => intval($matches[4]),
463
+		];
464
+	}
465
+
466
+
467
+	/**
468
+	 * Ensures that the option indicating the current DB version is set. This should only be
469
+	 * a concern when activating EE for the first time, THEORETICALLY.
470
+	 * If we detect that we're activating EE4 over top of EE3.1, then we set the current db state to 3.1.x, otherwise
471
+	 * to 4.1.x.
472
+	 *
473
+	 * @return string of current db state
474
+	 */
475
+	public function ensure_current_database_state_is_set()
476
+	{
477
+		$espresso_db_core_updates = get_option('espresso_db_update', []);
478
+		$db_state                 = get_option(EE_Data_Migration_Manager::current_database_state);
479
+		if (! $db_state) {
480
+			// mark the DB as being in the state as the last version in there.
481
+			// this is done to trigger maintenance mode and do data migration scripts
482
+			// if the admin installed this version of EE over 3.1.x or 4.0.x
483
+			// otherwise, the normal maintenance mode code is fine
484
+			$previous_versions_installed = array_keys($espresso_db_core_updates);
485
+			$previous_version_installed  = end($previous_versions_installed);
486
+			if (version_compare('4.1.0', $previous_version_installed)) {
487
+				// last installed version was less than 4.1, so we want the data migrations to happen.
488
+				// SO, we're going to say the DB is at that state
489
+				$db_state = ['Core' => $previous_version_installed];
490
+			} else {
491
+				$db_state = ['Core' => EVENT_ESPRESSO_VERSION];
492
+			}
493
+			update_option(EE_Data_Migration_Manager::current_database_state, $db_state);
494
+		}
495
+		// in 4.1, $db_state would have only been a simple string like '4.1.0',
496
+		// but in 4.2+ it should be an array with at least key 'Core' and the value of that plugin's
497
+		// db, and possibly other keys for other addons like 'Calendar','Permissions',etc
498
+		if (! is_array($db_state)) {
499
+			$db_state = ['Core' => $db_state];
500
+			update_option(EE_Data_Migration_Manager::current_database_state, $db_state);
501
+		}
502
+		return $db_state;
503
+	}
504
+
505
+
506
+	/**
507
+	 * Checks if there are any data migration scripts that ought to be run.
508
+	 * If found, returns the instantiated classes.
509
+	 * If none are found (ie, they've all already been run, or they don't apply), returns an empty array
510
+	 *
511
+	 * @return EE_Data_Migration_Script_Base[]
512
+	 * @throws EE_Error
513
+	 */
514
+	public function check_for_applicable_data_migration_scripts()
515
+	{
516
+		// get the option describing what options have already run
517
+		$scripts_ran = $this->get_data_migrations_ran();
518
+		// $scripts_ran = array('4.1.0.core'=>array('monkey'=>null));
519
+		$script_class_and_filepaths_available = $this->get_all_data_migration_scripts_available();
520
+
521
+
522
+		$current_database_state = $this->ensure_current_database_state_is_set();
523
+		// determine which have already been run
524
+		$script_classes_that_should_run_per_iteration = [];
525
+		$iteration                                    = 0;
526
+		$next_database_state_to_consider              = $current_database_state;
527
+		$theoretical_database_state                   = null;
528
+		do {
529
+			// the next state after the currently-considered one
530
+			// will start off looking the same as the current, but we may make additions...
531
+			$theoretical_database_state = $next_database_state_to_consider;
532
+			// the next db state to consider is
533
+			// "what would the DB be like had we run all the scripts we found that applied last time?"
534
+			foreach ($script_class_and_filepaths_available as $classname => $filepath) {
535
+				$migrates_to_version         = $this->script_migrates_to_version($classname);
536
+				$script_converts_plugin_slug = $migrates_to_version['slug'];
537
+				$script_converts_to_version  = $migrates_to_version['version'];
538
+				// check if this version script is DONE or not; or if it's never been run
539
+				if (
540
+					! $scripts_ran
541
+					|| ! isset($scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ])
542
+				) {
543
+					// we haven't run this conversion script before
544
+					// now check if it applies...
545
+					// note that we've added an autoloader for it on get_all_data_migration_scripts_available
546
+					// Also, make sure we get a new one. It's possible this is being ran during a multisite migration,
547
+					// in which case we don't want to reuse a DMS script from a different blog!
548
+					$script = LoaderFactory::getLoader()->getNew($classname);
549
+					/* @var $script EE_Data_Migration_Script_Base */
550
+					$can_migrate = $script->can_migrate_from_version($theoretical_database_state);
551
+					if ($can_migrate) {
552
+						$script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
553
+						$migrates_to_version                                                                 =
554
+							$script->migrates_to_version();
555
+						$next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
556
+							$migrates_to_version['version'];
557
+						unset($script_class_and_filepaths_available[ $classname ]);
558
+					}
559
+				} elseif (
560
+					$scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ]
561
+					instanceof
562
+					EE_Data_Migration_Script_Base
563
+				) {
564
+					// this script has been run, or at least started
565
+					$script = $scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ];
566
+					if ($script->get_status() !== self::status_completed) {
567
+						// this script is already underway... keep going with it
568
+						$script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
569
+						$migrates_to_version                                                                 =
570
+							$script->migrates_to_version();
571
+						$next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
572
+							$migrates_to_version['version'];
573
+						unset($script_class_and_filepaths_available[ $classname ]);
574
+					}
575
+					// else it must have a status that indicates it has finished,
576
+					// so we don't want to try and run it again
577
+				}
578
+				// else it exists, but it's not  a proper data migration script maybe the script got renamed?
579
+				// or was simply removed from EE? either way, it's certainly not runnable!
580
+			}
581
+			$iteration++;
582
+		} while ($next_database_state_to_consider !== $theoretical_database_state && $iteration < 6);
583
+		// ok we have all the scripts that should run, now let's make them into flat array
584
+		$scripts_that_should_run = [];
585
+		foreach ($script_classes_that_should_run_per_iteration as $scripts_at_priority) {
586
+			ksort($scripts_at_priority);
587
+			foreach ($scripts_at_priority as $scripts) {
588
+				foreach ($scripts as $script) {
589
+					$scripts_that_should_run[ get_class($script) ] = $script;
590
+				}
591
+			}
592
+		}
593
+
594
+		do_action(
595
+			'AHEE__EE_Data_Migration_Manager__check_for_applicable_data_migration_scripts__scripts_that_should_run',
596
+			$scripts_that_should_run
597
+		);
598
+		return $scripts_that_should_run;
599
+	}
600
+
601
+
602
+	/**
603
+	 * Gets the script which is currently being run, if there is one. If $include_completed_scripts is set to TRUE
604
+	 * it will return the last run script even if it's complete.
605
+	 * This means: if you want to find the currently-executing script, leave it as FALSE.
606
+	 * If you really just want to find the script which ran most recently, regardless of status, leave it as TRUE.
607
+	 *
608
+	 * @param bool $include_completed_scripts
609
+	 * @return EE_Data_Migration_Script_Base
610
+	 * @throws EE_Error
611
+	 */
612
+	public function get_last_ran_script($include_completed_scripts = false)
613
+	{
614
+		// make sure we've set up the class properties _last_ran_script and _last_ran_incomplete_script
615
+		if (! $this->_data_migrations_ran) {
616
+			$this->get_data_migrations_ran();
617
+		}
618
+		if ($include_completed_scripts) {
619
+			return $this->_last_ran_script;
620
+		} else {
621
+			return $this->_last_ran_incomplete_script;
622
+		}
623
+	}
624
+
625
+
626
+	/**
627
+	 * Runs the data migration scripts (well, each request to this method calls one of the
628
+	 * data migration scripts' migration_step() functions).
629
+	 *
630
+	 * @param int   $step_size
631
+	 * @return array {
632
+	 *                                  // where the first item is one EE_Data_Migration_Script_Base's stati,
633
+	 *                                  //and the second item is a string describing what was done
634
+	 * @type int    $records_to_migrate from the current migration script
635
+	 * @type int    $records_migrated
636
+	 * @type string $status             one of EE_Data_Migration_Manager::status_*
637
+	 * @type string $script             verbose name of the current DMS
638
+	 * @type string $message            string describing what was done during this step
639
+	 *                                  }
640
+	 * @throws EE_Error
641
+	 */
642
+	public function migration_step($step_size = 0)
643
+	{
644
+		// bandaid fix for issue https://events.codebasehq.com/projects/event-espresso/tickets/7535
645
+		if (class_exists('EE_CPT_Strategy')) {
646
+			remove_action('pre_get_posts', [EE_CPT_Strategy::instance(), 'pre_get_posts'], 5);
647
+		}
648
+
649
+		try {
650
+			$currently_executing_script = $this->get_last_ran_script();
651
+			if (! $currently_executing_script) {
652
+				// Find the next script that needs to execute
653
+				$scripts = $this->check_for_applicable_data_migration_scripts();
654
+				if (! $scripts) {
655
+					// huh, no more scripts to run... apparently we're done!
656
+					// but don't forget to make sure initial data is there
657
+					// we should be good to allow them to exit maintenance mode now
658
+					EE_Maintenance_Mode::instance()->set_maintenance_level(
659
+						EE_Maintenance_Mode::level_0_not_in_maintenance
660
+					);
661
+					// saving migrations run should actually be unnecessary,
662
+					// but leaving in place just in case... remember this migration was finished
663
+					// (even if we time out while initializing db for core and plugins)
664
+					$this->_save_migrations_ran();
665
+					// make sure DB was updated AFTER we've recorded the migration was done
666
+					$this->initialize_db_for_enqueued_ee_plugins();
667
+					return [
668
+						'records_to_migrate' => 1,
669
+						'records_migrated'   => 1,
670
+						'status'             => self::status_no_more_migration_scripts,
671
+						'script'             => esc_html__("Data Migration Completed Successfully", "event_espresso"),
672
+						'message'            => esc_html__("All done!", "event_espresso"),
673
+					];
674
+				}
675
+				$currently_executing_script = array_shift($scripts);
676
+				// and add to the array/wp option showing the scripts run
677
+
678
+				$migrates_to                                            =
679
+					$this->script_migrates_to_version(get_class($currently_executing_script));
680
+				$plugin_slug                                            = $migrates_to['slug'];
681
+				$version                                                = $migrates_to['version'];
682
+				$this->_data_migrations_ran[ $plugin_slug ][ $version ] = $currently_executing_script;
683
+			}
684
+			$current_script_name = get_class($currently_executing_script);
685
+		} catch (Exception $e) {
686
+			// an exception occurred while trying to get migration scripts
687
+
688
+			$message = sprintf(
689
+				esc_html__("Error Message: %sStack Trace:%s", "event_espresso"),
690
+				$e->getMessage() . '<br>',
691
+				$e->getTraceAsString()
692
+			);
693
+			// record it on the array of data migration scripts run. This will be overwritten next time we try and try to run data migrations
694
+			// but that's ok-- it's just an FYI to support that we couldn't even run any data migrations
695
+			$this->add_error_to_migrations_ran(
696
+				sprintf(esc_html__("Could not run data migrations because: %s", "event_espresso"), $message)
697
+			);
698
+			return [
699
+				'records_to_migrate' => 1,
700
+				'records_migrated'   => 0,
701
+				'status'             => self::status_fatal_error,
702
+				'script'             => esc_html__("Error loading data migration scripts", "event_espresso"),
703
+				'message'            => $message,
704
+			];
705
+		}
706
+		// can we wrap it up and verify default data?
707
+		$init_dbs = false;
708
+		// ok so we definitely have a data migration script
709
+		try {
710
+			$init_dbs = false;
711
+			// how big of a bite do we want to take? Allow users to easily override via their wp-config
712
+			if (absint($step_size) < 1) {
713
+				$step_size = defined('EE_MIGRATION_STEP_SIZE') && absint(EE_MIGRATION_STEP_SIZE)
714
+					? EE_MIGRATION_STEP_SIZE
715
+					: EE_Data_Migration_Manager::step_size;
716
+			}
717
+			// do what we came to do!
718
+			$currently_executing_script->migration_step($step_size);
719
+			switch ($currently_executing_script->get_status()) {
720
+				case EE_Data_Migration_Manager::status_continue:
721
+					$response_array = [
722
+						'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
723
+						'records_migrated'   => $currently_executing_script->count_records_migrated(),
724
+						'status'             => EE_Data_Migration_Manager::status_continue,
725
+						'message'            => $currently_executing_script->get_feedback_message(),
726
+						'script'             => $currently_executing_script->pretty_name(),
727
+					];
728
+					break;
729
+				case EE_Data_Migration_Manager::status_completed:
730
+					// ok so THAT script has completed
731
+					$this->update_current_database_state_to($this->script_migrates_to_version($current_script_name));
732
+					$response_array = [
733
+						'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
734
+						'records_migrated'   => $currently_executing_script->count_records_migrated(),
735
+						'status'             => EE_Data_Migration_Manager::status_completed,
736
+						'message'            => $currently_executing_script->get_feedback_message(),
737
+						'script'             => sprintf(
738
+							esc_html__("%s Completed", 'event_espresso'),
739
+							$currently_executing_script->pretty_name()
740
+						),
741
+					];
742
+					// check if there are any more after this one.
743
+					$scripts_remaining = $this->check_for_applicable_data_migration_scripts();
744
+					if (! $scripts_remaining) {
745
+						// we should be good to allow them to exit maintenance mode now
746
+						EE_Maintenance_Mode::instance()->set_maintenance_level(
747
+							EE_Maintenance_Mode::level_0_not_in_maintenance
748
+						);
749
+						// huh, no more scripts to run... apparently we're done!
750
+						// but don't forget to make sure initial data is there
751
+						$init_dbs                 = true;
752
+						$response_array['status'] = self::status_no_more_migration_scripts;
753
+					}
754
+					break;
755
+				default:
756
+					$response_array = [
757
+						'records_to_migrate' => $currently_executing_script->count_records_to_migrate(),
758
+						'records_migrated'   => $currently_executing_script->count_records_migrated(),
759
+						'status'             => $currently_executing_script->get_status(),
760
+						'message'            => sprintf(
761
+							esc_html__("Minor errors occurred during %s: %s", "event_espresso"),
762
+							$currently_executing_script->pretty_name(),
763
+							implode(", ", $currently_executing_script->get_errors())
764
+						),
765
+						'script'             => $currently_executing_script->pretty_name(),
766
+					];
767
+					break;
768
+			}
769
+		} catch (Exception $e) {
770
+			// ok so some exception was thrown which killed the data migration script
771
+			// double-check we have a real script
772
+			if ($currently_executing_script instanceof EE_Data_Migration_Script_Base) {
773
+				$script_name = $currently_executing_script->pretty_name();
774
+				$currently_executing_script->set_broken();
775
+				$currently_executing_script->add_error($e->getMessage());
776
+			} else {
777
+				$script_name = esc_html__("Error getting Migration Script", "event_espresso");
778
+			}
779
+			$response_array = [
780
+				'records_to_migrate' => 1,
781
+				'records_migrated'   => 0,
782
+				'status'             => self::status_fatal_error,
783
+				'message'            => sprintf(
784
+					esc_html__("A fatal error occurred during the migration: %s", "event_espresso"),
785
+					$e->getMessage()
786
+				),
787
+				'script'             => $script_name,
788
+			];
789
+		}
790
+		$successful_save = $this->_save_migrations_ran();
791
+		if ($successful_save !== true) {
792
+			// ok so the current wp option didn't save. that's tricky, because we'd like to update it
793
+			// and mark it as having a fatal error, but remember- WE CAN'T SAVE THIS WP OPTION!
794
+			// however, if we throw an exception, and return that, then the next request
795
+			// won't have as much info in it, and it may be able to save
796
+			throw new EE_Error(
797
+				sprintf(
798
+					esc_html__(
799
+						"The error '%s' occurred updating the status of the migration. This is a FATAL ERROR, but the error is preventing the system from remembering that. Please contact event espresso support.",
800
+						"event_espresso"
801
+					),
802
+					$successful_save
803
+				)
804
+			);
805
+		}
806
+		// if we're all done, initialize EE plugins' default data etc.
807
+		if ($init_dbs) {
808
+			$this->initialize_db_for_enqueued_ee_plugins();
809
+		}
810
+		return $response_array;
811
+	}
812
+
813
+
814
+	/**
815
+	 * Echo out JSON response to migration script AJAX requests. Takes precautions
816
+	 * to buffer output so that we don't throw junk into our json.
817
+	 *
818
+	 * @return array with keys:
819
+	 * 'records_to_migrate' which counts ALL the records for the current migration, and should remain constant. (ie,
820
+	 * it's NOT the count of hwo many remain)
821
+	 * 'records_migrated' which also counts ALL the records which have been migrated (ie, percent_complete =
822
+	 * records_migrated/records_to_migrate)
823
+	 * 'status'=>a string, one of EE_Data_migration_Manager::status_*
824
+	 * 'message'=>a string, containing any message you want to show to the user. We may decide to split this up into
825
+	 * errors, notifications, and successes
826
+	 * 'script'=>a pretty name of the script currently running
827
+	 */
828
+	public function response_to_migration_ajax_request()
829
+	{
830
+		ob_start();
831
+		try {
832
+			$response = $this->migration_step();
833
+		} catch (Exception $e) {
834
+			$response = [
835
+				'records_to_migrate' => 0,
836
+				'records_migrated'   => 0,
837
+				'status'             => EE_Data_Migration_Manager::status_fatal_error,
838
+				'message'            => sprintf(
839
+					esc_html__("Unknown fatal error occurred: %s", "event_espresso"),
840
+					$e->getMessage()
841
+				),
842
+				'script'             => 'Unknown',
843
+			];
844
+			$this->add_error_to_migrations_ran($e->getMessage() . "; Stack trace:" . $e->getTraceAsString());
845
+		}
846
+		$warnings_etc = @ob_get_contents();
847
+		ob_end_clean();
848
+		$response['message'] .= $warnings_etc;
849
+		return $response;
850
+	}
851
+
852
+
853
+	/**
854
+	 * Updates the WordPress option that keeps track of which EE version the database
855
+	 * is at (ie, the code may be at 4.1.0, but the database is still at 3.1.35)
856
+	 *
857
+	 * @param array $slug_and_version {
858
+	 * @type string $slug             like 'Core' or 'Calendar',
859
+	 * @type string $version          like '4.1.0'
860
+	 *                                }
861
+	 * @return void
862
+	 */
863
+	public function update_current_database_state_to($slug_and_version = null)
864
+	{
865
+		if (! $slug_and_version) {
866
+			// no version was provided, assume it should be at the current code version
867
+			$slug_and_version = ['slug' => 'Core', 'version' => espresso_version()];
868
+		}
869
+		$current_database_state                              = get_option(self::current_database_state, []);
870
+		$current_database_state[ $slug_and_version['slug'] ] = $slug_and_version['version'];
871
+		update_option(self::current_database_state, $current_database_state);
872
+	}
873
+
874
+
875
+	/**
876
+	 * Determines if the database is currently at a state matching what's indicated in $slug and $version.
877
+	 *
878
+	 * @param array $slug_and_version {
879
+	 * @type string $slug             like 'Core' or 'Calendar',
880
+	 * @type string $version          like '4.1.0'
881
+	 *                                }
882
+	 * @return boolean
883
+	 */
884
+	public function database_needs_updating_to($slug_and_version)
885
+	{
886
+		$slug                   = $slug_and_version['slug'];
887
+		$version                = $slug_and_version['version'];
888
+		$current_database_state = get_option(self::current_database_state, []);
889
+		if (! isset($current_database_state[ $slug ])) {
890
+			return true;
891
+		}
892
+		// just compare the first 3 parts of version string, eg "4.7.1", not "4.7.1.dev.032" because DBs shouldn't change on nano version changes
893
+		$version_parts_current_db_state     = array_slice(explode('.', $current_database_state[ $slug ]), 0, 3);
894
+		$version_parts_of_provided_db_state = array_slice(explode('.', $version), 0, 3);
895
+		$needs_updating                     = false;
896
+		foreach ($version_parts_current_db_state as $offset => $version_part_in_current_db_state) {
897
+			if ($version_part_in_current_db_state < $version_parts_of_provided_db_state[ $offset ]) {
898
+				$needs_updating = true;
899
+				break;
900
+			}
901
+		}
902
+		return $needs_updating;
903
+	}
904
+
905
+
906
+	/**
907
+	 * Gets all the data migration scripts available in the core folder and folders
908
+	 * in addons. Has the side effect of adding them for autoloading
909
+	 *
910
+	 * @return array keys are expected classnames, values are their filepaths
911
+	 * @throws InvalidInterfaceException
912
+	 * @throws InvalidDataTypeException
913
+	 * @throws EE_Error
914
+	 * @throws InvalidArgumentException
915
+	 */
916
+	public function get_all_data_migration_scripts_available()
917
+	{
918
+		if (! $this->_data_migration_class_to_filepath_map) {
919
+			$this->_data_migration_class_to_filepath_map = [];
920
+			foreach ($this->get_data_migration_script_folders() as $eeAddonClass => $folder_path) {
921
+				// strip any placeholders added to classname to make it a unique array key
922
+				$eeAddonClass = trim($eeAddonClass, '*');
923
+				$eeAddonClass = $eeAddonClass === 'Core' || class_exists($eeAddonClass)
924
+					? $eeAddonClass
925
+					: '';
926
+				$folder_path  = EEH_File::end_with_directory_separator($folder_path);
927
+				$files        = glob($folder_path . '*.dms.php');
928
+				if (empty($files)) {
929
+					continue;
930
+				}
931
+				natsort($files);
932
+				foreach ($files as $file) {
933
+					$pos_of_last_slash = strrpos($file, '/');
934
+					$classname         = str_replace('.dms.php', '', substr($file, $pos_of_last_slash + 1));
935
+					$migrates_to       = $this->script_migrates_to_version($classname, $eeAddonClass);
936
+					$slug              = $migrates_to['slug'];
937
+					// check that the slug as contained in the DMS is associated with
938
+					// the slug of an addon or core
939
+					if ($slug !== 'Core' && EE_Registry::instance()->get_addon_by_name($slug) === null) {
940
+						EE_Error::doing_it_wrong(
941
+							__FUNCTION__,
942
+							sprintf(
943
+								esc_html__(
944
+									'The data migration script "%s" migrates the "%s" data, but there is no EE addon with that name. There is only: %s. ',
945
+									'event_espresso'
946
+								),
947
+								$classname,
948
+								$slug,
949
+								implode(', ', array_keys(EE_Registry::instance()->get_addons_by_name()))
950
+							),
951
+							'4.3.0.alpha.019'
952
+						);
953
+					}
954
+					$this->_data_migration_class_to_filepath_map[ $classname ] = $file;
955
+				}
956
+			}
957
+			EEH_Autoloader::register_autoloader($this->_data_migration_class_to_filepath_map);
958
+		}
959
+		return $this->_data_migration_class_to_filepath_map;
960
+	}
961
+
962
+
963
+	/**
964
+	 * Once we have an addon that works with EE4.1, we will actually want to fetch the PUE slugs
965
+	 * from each addon, and check if they need updating,
966
+	 *
967
+	 * @return boolean
968
+	 */
969
+	public function addons_need_updating()
970
+	{
971
+		return false;
972
+	}
973
+
974
+
975
+	/**
976
+	 * Adds this error string to the data_migrations_ran array, but we don't necessarily know
977
+	 * where to put it, so we just throw it in there... better than nothing...
978
+	 *
979
+	 * @param string $error_message
980
+	 */
981
+	public function add_error_to_migrations_ran($error_message)
982
+	{
983
+		// get last-run migration script
984
+		global $wpdb;
985
+		$last_migration_script_option = $wpdb->get_row(
986
+			"SELECT * FROM $wpdb->options WHERE option_name like '"
987
+			. EE_Data_Migration_Manager::data_migration_script_option_prefix
988
+			. "%' ORDER BY option_id DESC LIMIT 1",
989
+			ARRAY_A
990
+		);
991
+
992
+		$last_ran_migration_script_properties = isset($last_migration_script_option['option_value'])
993
+			? maybe_unserialize($last_migration_script_option['option_value'])
994
+			: null;
995
+		// now, tread lightly because we're here because a FATAL non-catchable error
996
+		// was thrown last time when we were trying to run a data migration script
997
+		// so the fatal error could have happened while getting the migration script
998
+		// or doing running it...
999
+		$versions_migrated_to = isset($last_migration_script_option['option_name'])
1000
+			? str_replace(
1001
+				EE_Data_Migration_Manager::data_migration_script_option_prefix,
1002
+				"",
1003
+				$last_migration_script_option['option_name']
1004
+			)
1005
+			: null;
1006
+
1007
+		// check if it THINKS it's a data migration script and especially if it's one that HASN'T finished yet
1008
+		// because if it has finished, then it obviously couldn't be the cause of this error, right? (because it's all done)
1009
+		if (
1010
+			isset($last_ran_migration_script_properties['class'])
1011
+			&& isset($last_ran_migration_script_properties['_status'])
1012
+			&& $last_ran_migration_script_properties['_status'] != self::status_completed
1013
+		) {
1014
+			// ok then just add this error to its list of errors
1015
+			$last_ran_migration_script_properties['_errors'][] = $error_message;
1016
+			$last_ran_migration_script_properties['_status']   = self::status_fatal_error;
1017
+		} else {
1018
+			// so we don't even know which script was last running
1019
+			// use the data migration error stub, which is designed specifically for this type of thing
1020
+			$general_migration_error = new EE_DMS_Unknown_1_0_0();
1021
+			$general_migration_error->add_error($error_message);
1022
+			$general_migration_error->set_broken();
1023
+			$last_ran_migration_script_properties = $general_migration_error->properties_as_array();
1024
+			$versions_migrated_to                 = 'Unknown.1.0.0';
1025
+			// now just to make sure appears as last (in case the were previously a fatal error like this)
1026
+			// delete the old one
1027
+			delete_option(self::data_migration_script_option_prefix . $versions_migrated_to);
1028
+		}
1029
+		update_option(
1030
+			self::data_migration_script_option_prefix . $versions_migrated_to,
1031
+			$last_ran_migration_script_properties
1032
+		);
1033
+	}
1034
+
1035
+
1036
+	/**
1037
+	 * saves what data migrations have run to the database
1038
+	 *
1039
+	 * @return bool|string TRUE if successfully saved migrations ran, string if an error occurred
1040
+	 * @throws EE_Error
1041
+	 */
1042
+	protected function _save_migrations_ran()
1043
+	{
1044
+		if ($this->_data_migrations_ran == null) {
1045
+			$this->get_data_migrations_ran();
1046
+		}
1047
+		// now, we don't want to save actual classes to the DB because that's messy
1048
+		$successful_updates = true;
1049
+		foreach ($this->_data_migrations_ran as $plugin_slug => $migrations_ran_for_plugin) {
1050
+			foreach ($migrations_ran_for_plugin as $version_string => $array_or_migration_obj) {
1051
+				$plugin_slug_for_use_in_option_name = $plugin_slug . ".";
1052
+				$option_name                        =
1053
+					self::data_migration_script_option_prefix . $plugin_slug_for_use_in_option_name . $version_string;
1054
+				$old_option_value                   = get_option($option_name);
1055
+				if ($array_or_migration_obj instanceof EE_Data_Migration_Script_Base) {
1056
+					$script_array_for_saving = $array_or_migration_obj->properties_as_array();
1057
+					if ($old_option_value != $script_array_for_saving) {
1058
+						$successful_updates = update_option($option_name, $script_array_for_saving);
1059
+					}
1060
+				} else {// we don't know what this array-thing is. So just save it as-is
1061
+					if ($old_option_value != $array_or_migration_obj) {
1062
+						$successful_updates = update_option($option_name, $array_or_migration_obj);
1063
+					}
1064
+				}
1065
+				if (! $successful_updates) {
1066
+					global $wpdb;
1067
+					return $wpdb->last_error;
1068
+				}
1069
+			}
1070
+		}
1071
+		return true;
1072
+		// $updated = update_option(self::data_migrations_option_name, $array_of_migrations);
1073
+		// if ($updated !== true) {
1074
+		//     global $wpdb;
1075
+		//     return $wpdb->last_error;
1076
+		// } else {
1077
+		//     return true;
1078
+		// }
1079
+		// wp_mail(
1080
+		//     "[email protected]",
1081
+		//     time() . " price debug info",
1082
+		//     "updated: $updated, last error: $last_error, byte length of option: " . strlen(
1083
+		//         serialize($array_of_migrations)
1084
+		//     )
1085
+		// );
1086
+	}
1087
+
1088
+
1089
+	/**
1090
+	 * Takes an array of data migration script properties and re-creates the class from
1091
+	 * them. The argument $properties_array is assumed to have been made by
1092
+	 * EE_Data_Migration_Script_Base::properties_as_array()
1093
+	 *
1094
+	 * @param array $properties_array
1095
+	 * @return EE_Data_Migration_Script_Base
1096
+	 * @throws EE_Error
1097
+	 */
1098
+	public function _instantiate_script_from_properties_array($properties_array)
1099
+	{
1100
+		if (! isset($properties_array['class'])) {
1101
+			throw new EE_Error(
1102
+				sprintf(
1103
+					esc_html__("Properties array  has no 'class' properties. Here's what it has: %s", "event_espresso"),
1104
+					implode(",", $properties_array)
1105
+				)
1106
+			);
1107
+		}
1108
+		$class_name = $properties_array['class'];
1109
+		if (! class_exists($class_name)) {
1110
+			throw new EE_Error(sprintf(
1111
+				esc_html__("There is no migration script named %s", "event_espresso"),
1112
+				$class_name
1113
+			));
1114
+		}
1115
+		$class = new $class_name();
1116
+		if (! $class instanceof EE_Data_Migration_Script_Base) {
1117
+			throw new EE_Error(
1118
+				sprintf(
1119
+					esc_html__(
1120
+						"Class '%s' is supposed to be a migration script. Its not, its a '%s'",
1121
+						"event_espresso"
1122
+					),
1123
+					$class_name,
1124
+					get_class($class)
1125
+				)
1126
+			);
1127
+		}
1128
+		$class->instantiate_from_array_of_properties($properties_array);
1129
+		return $class;
1130
+	}
1131
+
1132
+
1133
+	/**
1134
+	 * Gets the classname for the most up-to-date DMS (ie, the one that will finally
1135
+	 * leave the DB in a state usable by the current plugin code).
1136
+	 *
1137
+	 * @param string $plugin_slug the slug for the ee plugin we are searching for. Default is 'Core'
1138
+	 * @return string
1139
+	 * @throws EE_Error
1140
+	 */
1141
+	public function get_most_up_to_date_dms($plugin_slug = 'Core')
1142
+	{
1143
+		$class_to_filepath_map         = $this->get_all_data_migration_scripts_available();
1144
+		$most_up_to_date_dms_classname = null;
1145
+		foreach ($class_to_filepath_map as $classname => $filepath) {
1146
+			if ($most_up_to_date_dms_classname === null) {
1147
+				$migrates_to      = $this->script_migrates_to_version($classname);
1148
+				$this_plugin_slug = $migrates_to['slug'];
1149
+				if ($this_plugin_slug == $plugin_slug) {
1150
+					// if it's for core, it wins
1151
+					$most_up_to_date_dms_classname = $classname;
1152
+				}
1153
+				// if it wasn't for core, we must keep searching for one that is!
1154
+				continue;
1155
+			}
1156
+			$champion_migrates_to  = $this->script_migrates_to_version($most_up_to_date_dms_classname);
1157
+			$contender_migrates_to = $this->script_migrates_to_version($classname);
1158
+			if (
1159
+				$contender_migrates_to['slug'] == $plugin_slug
1160
+				&& version_compare(
1161
+					$champion_migrates_to['version'],
1162
+					$contender_migrates_to['version'],
1163
+					'<'
1164
+				)
1165
+			) {
1166
+				// so the contenders version is higher, and it's for Core
1167
+				$most_up_to_date_dms_classname = $classname;
1168
+			}
1169
+		}
1170
+		return $most_up_to_date_dms_classname;
1171
+	}
1172
+
1173
+
1174
+	/**
1175
+	 * Gets the migration script specified but ONLY if it has already run.
1176
+	 * Eg, if you wanted to see if 'EE_DMS_Core_4_1_0' has run, you would run the following code:
1177
+	 * <code> $core_4_1_0_dms_ran = EE_Data_Migration_Manager::instance()->get_migration_ran( '4.1.0', 'Core' ) !==
1178
+	 * NULL;</code> This is especially useful in addons' data migration scripts, this way they can tell if a core (or
1179
+	 * other addon) DMS has run, in case the current DMS depends on it.
1180
+	 *
1181
+	 * @param string $version     the version the DMS searched for migrates to. Usually just the content before the 3rd
1182
+	 *                            period. Eg '4.1.0'
1183
+	 * @param string $plugin_slug like 'Core', 'Mailchimp', 'Calendar', etc
1184
+	 * @return EE_Data_Migration_Script_Base
1185
+	 * @throws EE_Error
1186
+	 */
1187
+	public function get_migration_ran($version, $plugin_slug = 'Core')
1188
+	{
1189
+		$migrations_ran = $this->get_data_migrations_ran();
1190
+		if (isset($migrations_ran[ $plugin_slug ]) && isset($migrations_ran[ $plugin_slug ][ $version ])) {
1191
+			return $migrations_ran[ $plugin_slug ][ $version ];
1192
+		} else {
1193
+			return null;
1194
+		}
1195
+	}
1196
+
1197
+
1198
+	/**
1199
+	 * Resets the borked data migration scripts, so they're no longer borked, and we can again attempt to migrate
1200
+	 *
1201
+	 * @return bool
1202
+	 * @throws EE_Error
1203
+	 */
1204
+	public function reattempt()
1205
+	{
1206
+		// find if the last-run script was borked
1207
+		// set it as being non-borked (we shouldn't ever get DMSs that we don't recognize)
1208
+		// add an 'error' saying that we attempted to reset
1209
+		// does it have a stage that was borked too? if so make it no longer borked
1210
+		// add an 'error' saying we attempted to reset
1211
+		$last_ran_script = $this->get_last_ran_script();
1212
+		if ($last_ran_script instanceof EE_DMS_Unknown_1_0_0) {
1213
+			// if it was an error DMS, just mark it as complete (if another error occurs it will overwrite it)
1214
+			$last_ran_script->set_completed();
1215
+		} elseif ($last_ran_script instanceof EE_Data_Migration_Script_Base) {
1216
+			$last_ran_script->reattempt();
1217
+		} else {
1218
+			throw new EE_Error(
1219
+				sprintf(
1220
+					esc_html__(
1221
+						'Unable to reattempt the last ran migration script because it was not a valid migration script. || It was %s',
1222
+						'event_espresso'
1223
+					),
1224
+					print_r($last_ran_script, true)
1225
+				)
1226
+			);
1227
+		}
1228
+		return $this->_save_migrations_ran();
1229
+	}
1230
+
1231
+
1232
+	/**
1233
+	 * Gets whether this particular migration has run or not
1234
+	 *
1235
+	 * @param string $version     the version the DMS searched for migrates to. Usually just the content before the 3rd
1236
+	 *                            period. Eg '4.1.0'
1237
+	 * @param string $plugin_slug like 'Core', 'Mailchimp', 'Calendar', etc
1238
+	 * @return boolean
1239
+	 * @throws EE_Error
1240
+	 */
1241
+	public function migration_has_ran($version, $plugin_slug = 'Core')
1242
+	{
1243
+		return $this->get_migration_ran($version, $plugin_slug) !== null;
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * Enqueues this ee plugin to have its data initialized
1249
+	 *
1250
+	 * @param string $plugin_slug either 'Core' or EE_Addon::name()'s return value
1251
+	 */
1252
+	public function enqueue_db_initialization_for($plugin_slug)
1253
+	{
1254
+		$queue = $this->get_db_initialization_queue();
1255
+		if (! in_array($plugin_slug, $queue)) {
1256
+			$queue[] = $plugin_slug;
1257
+		}
1258
+		update_option(self::db_init_queue_option_name, $queue);
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Calls EE_Addon::initialize_db_if_no_migrations_required() on each addon
1264
+	 * specified in EE_Data_Migration_Manager::get_db_init_queue(), and if 'Core' is
1265
+	 * in the queue, calls EE_System::initialize_db_if_no_migrations_required().
1266
+	 *
1267
+	 * @throws EE_Error
1268
+	 */
1269
+	public function initialize_db_for_enqueued_ee_plugins()
1270
+	{
1271
+		$queue = $this->get_db_initialization_queue();
1272
+		foreach ($queue as $plugin_slug) {
1273
+			$most_up_to_date_dms = $this->get_most_up_to_date_dms($plugin_slug);
1274
+			if (! $most_up_to_date_dms) {
1275
+				// if there is NO DMS for this plugin, obviously there's no schema to verify anyways
1276
+				$verify_db = false;
1277
+			} else {
1278
+				$most_up_to_date_dms_migrates_to = $this->script_migrates_to_version($most_up_to_date_dms);
1279
+				$verify_db                       = $this->database_needs_updating_to($most_up_to_date_dms_migrates_to);
1280
+			}
1281
+			if ($plugin_slug == 'Core') {
1282
+				EE_System::instance()->initialize_db_if_no_migrations_required(
1283
+					false,
1284
+					$verify_db
1285
+				);
1286
+			} else {
1287
+				// just loop through the addons to make sure their database is set up
1288
+				foreach (EE_Registry::instance()->addons as $addon) {
1289
+					if ($addon->name() == $plugin_slug) {
1290
+						$addon->initialize_db_if_no_migrations_required($verify_db);
1291
+						break;
1292
+					}
1293
+				}
1294
+			}
1295
+		}
1296
+		// because we just initialized the DBs for the enqueued ee plugins
1297
+		// we don't need to keep remembering which ones needed to be initialized
1298
+		delete_option(self::db_init_queue_option_name);
1299
+	}
1300
+
1301
+
1302
+	/**
1303
+	 * Gets a numerically-indexed array of plugin slugs that need to have their databases
1304
+	 * (re-)initialized after migrations are complete. ie, each element should be either
1305
+	 * 'Core', or the return value of EE_Addon::name() for an addon
1306
+	 *
1307
+	 * @return array
1308
+	 */
1309
+	public function get_db_initialization_queue()
1310
+	{
1311
+		return get_option(self::db_init_queue_option_name, []);
1312
+	}
1313
+
1314
+
1315
+	/**
1316
+	 * Gets the injected table analyzer, or throws an exception
1317
+	 *
1318
+	 * @return TableAnalysis
1319
+	 * @throws EE_Error
1320
+	 */
1321
+	protected function _get_table_analysis()
1322
+	{
1323
+		if ($this->_table_analysis instanceof TableAnalysis) {
1324
+			return $this->_table_analysis;
1325
+		} else {
1326
+			throw new EE_Error(
1327
+				sprintf(
1328
+					esc_html__('Table analysis class on class %1$s is not set properly.', 'event_espresso'),
1329
+					get_class($this)
1330
+				)
1331
+			);
1332
+		}
1333
+	}
1334
+
1335
+
1336
+	/**
1337
+	 * Gets the injected table manager, or throws an exception
1338
+	 *
1339
+	 * @return TableManager
1340
+	 * @throws EE_Error
1341
+	 */
1342
+	protected function _get_table_manager()
1343
+	{
1344
+		if ($this->_table_manager instanceof TableManager) {
1345
+			return $this->_table_manager;
1346
+		} else {
1347
+			throw new EE_Error(
1348
+				sprintf(
1349
+					esc_html__('Table manager class on class %1$s is not set properly.', 'event_espresso'),
1350
+					get_class($this)
1351
+				)
1352
+			);
1353
+		}
1354
+	}
1355 1355
 }
Please login to merge, or discard this patch.
Spacing   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
     public static function instance()
164 164
     {
165 165
         // check if class object is instantiated
166
-        if (! self::$_instance instanceof EE_Data_Migration_Manager) {
166
+        if ( ! self::$_instance instanceof EE_Data_Migration_Manager) {
167 167
             self::$_instance = new self();
168 168
         }
169 169
         return self::$_instance;
@@ -189,11 +189,11 @@  discard block
 block discarded – undo
189 189
      */
190 190
     private function __construct()
191 191
     {
192
-        $this->stati_that_indicate_to_continue_migrations              = [
192
+        $this->stati_that_indicate_to_continue_migrations = [
193 193
             self::status_continue,
194 194
             self::status_completed,
195 195
         ];
196
-        $this->stati_that_indicate_to_stop_migrations                  = [
196
+        $this->stati_that_indicate_to_stop_migrations = [
197 197
             self::status_fatal_error,
198 198
             self::status_no_more_migration_scripts,
199 199
         ];
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
             self::status_fatal_error,
206 206
             // note: status_no_more_migration_scripts doesn't apply
207 207
         ];
208
-        $this->dms_folders                                             = [];
208
+        $this->dms_folders = [];
209 209
         // make sure we've included the base migration script, because we may need the EE_DMS_Unknown_1_0_0 class
210 210
         // to be defined, because right now it doesn't get autoloaded on its own
211 211
         EE_Registry::instance()->load_core('Data_Migration_Class_Base', [], true);
@@ -242,12 +242,12 @@  discard block
 block discarded – undo
242 242
 
243 243
         if (count($parts) == 4) {
244 244
             // it's 4.2-style.eg Core.4.1.0
245
-            $plugin_slug    = $parts[0];                                     // eg Core
246
-            $version_string = $parts[1] . "." . $parts[2] . "." . $parts[3]; // eg 4.1.0
245
+            $plugin_slug    = $parts[0]; // eg Core
246
+            $version_string = $parts[1].".".$parts[2].".".$parts[3]; // eg 4.1.0
247 247
         } else {
248 248
             // it's 4.1-style: eg 4.1.0
249 249
             $plugin_slug    = 'Core';
250
-            $version_string = $plugin_slug_and_version_string;// eg 4.1.0
250
+            $version_string = $plugin_slug_and_version_string; // eg 4.1.0
251 251
         }
252 252
         return [$plugin_slug, $version_string];
253 253
     }
@@ -309,11 +309,11 @@  discard block
 block discarded – undo
309 309
      */
310 310
     public function get_data_migrations_ran()
311 311
     {
312
-        if (! $this->_data_migrations_ran) {
312
+        if ( ! $this->_data_migrations_ran) {
313 313
             // setup autoloaders for each of the scripts in there
314 314
             $this->get_all_data_migration_scripts_available();
315 315
             $data_migrations_options =
316
-                $this->get_all_migration_script_options();// get_option(EE_Data_Migration_Manager::data_migrations_option_name,get_option('espresso_data_migrations',array()));
316
+                $this->get_all_migration_script_options(); // get_option(EE_Data_Migration_Manager::data_migrations_option_name,get_option('espresso_data_migrations',array()));
317 317
 
318 318
             $data_migrations_ran = [];
319 319
             // convert into data migration script classes where possible
@@ -323,27 +323,27 @@  discard block
 block discarded – undo
323 323
                 );
324 324
 
325 325
                 try {
326
-                    $class                                                  = $this->_get_dms_class_from_wp_option(
326
+                    $class = $this->_get_dms_class_from_wp_option(
327 327
                         $data_migration_option['option_name'],
328 328
                         $data_migration_option['option_value']
329 329
                     );
330
-                    $data_migrations_ran[ $plugin_slug ][ $version_string ] = $class;
330
+                    $data_migrations_ran[$plugin_slug][$version_string] = $class;
331 331
                     // ok so far THIS is the 'last-run-script'... unless we find another on next iteration
332 332
                     $this->_last_ran_script = $class;
333
-                    if (! $class->is_completed()) {
333
+                    if ( ! $class->is_completed()) {
334 334
                         // sometimes we also like to know which was the last incomplete script (or if there are any at all)
335 335
                         $this->_last_ran_incomplete_script = $class;
336 336
                     }
337 337
                 } catch (EE_Error $e) {
338 338
                     // ok so it's not a DMS. We'll just keep it, although other code will need to expect non-DMSs
339
-                    $data_migrations_ran[ $plugin_slug ][ $version_string ] = maybe_unserialize(
339
+                    $data_migrations_ran[$plugin_slug][$version_string] = maybe_unserialize(
340 340
                         $data_migration_option['option_value']
341 341
                     );
342 342
                 }
343 343
             }
344 344
             // so here the array of $data_migrations_ran is actually a mix of classes and a few legacy arrays
345 345
             $this->_data_migrations_ran = $data_migrations_ran;
346
-            if (! $this->_data_migrations_ran || ! is_array($this->_data_migrations_ran)) {
346
+            if ( ! $this->_data_migrations_ran || ! is_array($this->_data_migrations_ran)) {
347 347
                 $this->_data_migrations_ran = [];
348 348
             }
349 349
         }
@@ -396,7 +396,7 @@  discard block
 block discarded – undo
396 396
         if (empty($this->dms_folders)) {
397 397
             $this->dms_folders = (array) apply_filters(
398 398
                 'FHEE__EE_Data_Migration_Manager__get_data_migration_script_folders',
399
-                ['Core' => EE_CORE . 'data_migration_scripts']
399
+                ['Core' => EE_CORE.'data_migration_scripts']
400 400
             );
401 401
         }
402 402
         return $this->dms_folders;
@@ -415,11 +415,11 @@  discard block
 block discarded – undo
415 415
      */
416 416
     public function script_migrates_to_version($migration_script_name, $eeAddonClass = '')
417 417
     {
418
-        if (isset($this->script_migration_versions[ $migration_script_name ])) {
419
-            return $this->script_migration_versions[ $migration_script_name ];
418
+        if (isset($this->script_migration_versions[$migration_script_name])) {
419
+            return $this->script_migration_versions[$migration_script_name];
420 420
         }
421 421
         $dms_info                                                  = $this->parse_dms_classname($migration_script_name);
422
-        $this->script_migration_versions[ $migration_script_name ] = [
422
+        $this->script_migration_versions[$migration_script_name] = [
423 423
             'slug'    => $eeAddonClass !== ''
424 424
                 ? $eeAddonClass
425 425
                 : $dms_info['slug'],
@@ -429,7 +429,7 @@  discard block
 block discarded – undo
429 429
                          . "."
430 430
                          . $dms_info['micro_version'],
431 431
         ];
432
-        return $this->script_migration_versions[ $migration_script_name ];
432
+        return $this->script_migration_versions[$migration_script_name];
433 433
     }
434 434
 
435 435
 
@@ -444,7 +444,7 @@  discard block
 block discarded – undo
444 444
     {
445 445
         $matches = [];
446 446
         preg_match('~EE_DMS_(.*)_([0-9]*)_([0-9]*)_([0-9]*)~', $classname, $matches);
447
-        if (! $matches || ! (isset($matches[1]) && isset($matches[2]) && isset($matches[3]))) {
447
+        if ( ! $matches || ! (isset($matches[1]) && isset($matches[2]) && isset($matches[3]))) {
448 448
             throw new EE_Error(
449 449
                 sprintf(
450 450
                     esc_html__(
@@ -476,7 +476,7 @@  discard block
 block discarded – undo
476 476
     {
477 477
         $espresso_db_core_updates = get_option('espresso_db_update', []);
478 478
         $db_state                 = get_option(EE_Data_Migration_Manager::current_database_state);
479
-        if (! $db_state) {
479
+        if ( ! $db_state) {
480 480
             // mark the DB as being in the state as the last version in there.
481 481
             // this is done to trigger maintenance mode and do data migration scripts
482 482
             // if the admin installed this version of EE over 3.1.x or 4.0.x
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
         // in 4.1, $db_state would have only been a simple string like '4.1.0',
496 496
         // but in 4.2+ it should be an array with at least key 'Core' and the value of that plugin's
497 497
         // db, and possibly other keys for other addons like 'Calendar','Permissions',etc
498
-        if (! is_array($db_state)) {
498
+        if ( ! is_array($db_state)) {
499 499
             $db_state = ['Core' => $db_state];
500 500
             update_option(EE_Data_Migration_Manager::current_database_state, $db_state);
501 501
         }
@@ -538,7 +538,7 @@  discard block
 block discarded – undo
538 538
                 // check if this version script is DONE or not; or if it's never been run
539 539
                 if (
540 540
                     ! $scripts_ran
541
-                    || ! isset($scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ])
541
+                    || ! isset($scripts_ran[$script_converts_plugin_slug][$script_converts_to_version])
542 542
                 ) {
543 543
                     // we haven't run this conversion script before
544 544
                     // now check if it applies...
@@ -549,28 +549,28 @@  discard block
 block discarded – undo
549 549
                     /* @var $script EE_Data_Migration_Script_Base */
550 550
                     $can_migrate = $script->can_migrate_from_version($theoretical_database_state);
551 551
                     if ($can_migrate) {
552
-                        $script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
552
+                        $script_classes_that_should_run_per_iteration[$iteration][$script->priority()][] = $script;
553 553
                         $migrates_to_version                                                                 =
554 554
                             $script->migrates_to_version();
555
-                        $next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
555
+                        $next_database_state_to_consider[$migrates_to_version['slug']]                     =
556 556
                             $migrates_to_version['version'];
557
-                        unset($script_class_and_filepaths_available[ $classname ]);
557
+                        unset($script_class_and_filepaths_available[$classname]);
558 558
                     }
559 559
                 } elseif (
560
-                    $scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ]
560
+                    $scripts_ran[$script_converts_plugin_slug][$script_converts_to_version]
561 561
                     instanceof
562 562
                     EE_Data_Migration_Script_Base
563 563
                 ) {
564 564
                     // this script has been run, or at least started
565
-                    $script = $scripts_ran[ $script_converts_plugin_slug ][ $script_converts_to_version ];
565
+                    $script = $scripts_ran[$script_converts_plugin_slug][$script_converts_to_version];
566 566
                     if ($script->get_status() !== self::status_completed) {
567 567
                         // this script is already underway... keep going with it
568
-                        $script_classes_that_should_run_per_iteration[ $iteration ][ $script->priority() ][] = $script;
568
+                        $script_classes_that_should_run_per_iteration[$iteration][$script->priority()][] = $script;
569 569
                         $migrates_to_version                                                                 =
570 570
                             $script->migrates_to_version();
571
-                        $next_database_state_to_consider[ $migrates_to_version['slug'] ]                     =
571
+                        $next_database_state_to_consider[$migrates_to_version['slug']]                     =
572 572
                             $migrates_to_version['version'];
573
-                        unset($script_class_and_filepaths_available[ $classname ]);
573
+                        unset($script_class_and_filepaths_available[$classname]);
574 574
                     }
575 575
                     // else it must have a status that indicates it has finished,
576 576
                     // so we don't want to try and run it again
@@ -579,14 +579,14 @@  discard block
 block discarded – undo
579 579
                 // or was simply removed from EE? either way, it's certainly not runnable!
580 580
             }
581 581
             $iteration++;
582
-        } while ($next_database_state_to_consider !== $theoretical_database_state && $iteration < 6);
582
+        }while ($next_database_state_to_consider !== $theoretical_database_state && $iteration < 6);
583 583
         // ok we have all the scripts that should run, now let's make them into flat array
584 584
         $scripts_that_should_run = [];
585 585
         foreach ($script_classes_that_should_run_per_iteration as $scripts_at_priority) {
586 586
             ksort($scripts_at_priority);
587 587
             foreach ($scripts_at_priority as $scripts) {
588 588
                 foreach ($scripts as $script) {
589
-                    $scripts_that_should_run[ get_class($script) ] = $script;
589
+                    $scripts_that_should_run[get_class($script)] = $script;
590 590
                 }
591 591
             }
592 592
         }
@@ -612,7 +612,7 @@  discard block
 block discarded – undo
612 612
     public function get_last_ran_script($include_completed_scripts = false)
613 613
     {
614 614
         // make sure we've set up the class properties _last_ran_script and _last_ran_incomplete_script
615
-        if (! $this->_data_migrations_ran) {
615
+        if ( ! $this->_data_migrations_ran) {
616 616
             $this->get_data_migrations_ran();
617 617
         }
618 618
         if ($include_completed_scripts) {
@@ -648,10 +648,10 @@  discard block
 block discarded – undo
648 648
 
649 649
         try {
650 650
             $currently_executing_script = $this->get_last_ran_script();
651
-            if (! $currently_executing_script) {
651
+            if ( ! $currently_executing_script) {
652 652
                 // Find the next script that needs to execute
653 653
                 $scripts = $this->check_for_applicable_data_migration_scripts();
654
-                if (! $scripts) {
654
+                if ( ! $scripts) {
655 655
                     // huh, no more scripts to run... apparently we're done!
656 656
                     // but don't forget to make sure initial data is there
657 657
                     // we should be good to allow them to exit maintenance mode now
@@ -679,7 +679,7 @@  discard block
 block discarded – undo
679 679
                     $this->script_migrates_to_version(get_class($currently_executing_script));
680 680
                 $plugin_slug                                            = $migrates_to['slug'];
681 681
                 $version                                                = $migrates_to['version'];
682
-                $this->_data_migrations_ran[ $plugin_slug ][ $version ] = $currently_executing_script;
682
+                $this->_data_migrations_ran[$plugin_slug][$version] = $currently_executing_script;
683 683
             }
684 684
             $current_script_name = get_class($currently_executing_script);
685 685
         } catch (Exception $e) {
@@ -687,7 +687,7 @@  discard block
 block discarded – undo
687 687
 
688 688
             $message = sprintf(
689 689
                 esc_html__("Error Message: %sStack Trace:%s", "event_espresso"),
690
-                $e->getMessage() . '<br>',
690
+                $e->getMessage().'<br>',
691 691
                 $e->getTraceAsString()
692 692
             );
693 693
             // record it on the array of data migration scripts run. This will be overwritten next time we try and try to run data migrations
@@ -741,7 +741,7 @@  discard block
 block discarded – undo
741 741
                     ];
742 742
                     // check if there are any more after this one.
743 743
                     $scripts_remaining = $this->check_for_applicable_data_migration_scripts();
744
-                    if (! $scripts_remaining) {
744
+                    if ( ! $scripts_remaining) {
745 745
                         // we should be good to allow them to exit maintenance mode now
746 746
                         EE_Maintenance_Mode::instance()->set_maintenance_level(
747 747
                             EE_Maintenance_Mode::level_0_not_in_maintenance
@@ -841,7 +841,7 @@  discard block
 block discarded – undo
841 841
                 ),
842 842
                 'script'             => 'Unknown',
843 843
             ];
844
-            $this->add_error_to_migrations_ran($e->getMessage() . "; Stack trace:" . $e->getTraceAsString());
844
+            $this->add_error_to_migrations_ran($e->getMessage()."; Stack trace:".$e->getTraceAsString());
845 845
         }
846 846
         $warnings_etc = @ob_get_contents();
847 847
         ob_end_clean();
@@ -862,12 +862,12 @@  discard block
 block discarded – undo
862 862
      */
863 863
     public function update_current_database_state_to($slug_and_version = null)
864 864
     {
865
-        if (! $slug_and_version) {
865
+        if ( ! $slug_and_version) {
866 866
             // no version was provided, assume it should be at the current code version
867 867
             $slug_and_version = ['slug' => 'Core', 'version' => espresso_version()];
868 868
         }
869 869
         $current_database_state                              = get_option(self::current_database_state, []);
870
-        $current_database_state[ $slug_and_version['slug'] ] = $slug_and_version['version'];
870
+        $current_database_state[$slug_and_version['slug']] = $slug_and_version['version'];
871 871
         update_option(self::current_database_state, $current_database_state);
872 872
     }
873 873
 
@@ -886,15 +886,15 @@  discard block
 block discarded – undo
886 886
         $slug                   = $slug_and_version['slug'];
887 887
         $version                = $slug_and_version['version'];
888 888
         $current_database_state = get_option(self::current_database_state, []);
889
-        if (! isset($current_database_state[ $slug ])) {
889
+        if ( ! isset($current_database_state[$slug])) {
890 890
             return true;
891 891
         }
892 892
         // just compare the first 3 parts of version string, eg "4.7.1", not "4.7.1.dev.032" because DBs shouldn't change on nano version changes
893
-        $version_parts_current_db_state     = array_slice(explode('.', $current_database_state[ $slug ]), 0, 3);
893
+        $version_parts_current_db_state     = array_slice(explode('.', $current_database_state[$slug]), 0, 3);
894 894
         $version_parts_of_provided_db_state = array_slice(explode('.', $version), 0, 3);
895 895
         $needs_updating                     = false;
896 896
         foreach ($version_parts_current_db_state as $offset => $version_part_in_current_db_state) {
897
-            if ($version_part_in_current_db_state < $version_parts_of_provided_db_state[ $offset ]) {
897
+            if ($version_part_in_current_db_state < $version_parts_of_provided_db_state[$offset]) {
898 898
                 $needs_updating = true;
899 899
                 break;
900 900
             }
@@ -915,7 +915,7 @@  discard block
 block discarded – undo
915 915
      */
916 916
     public function get_all_data_migration_scripts_available()
917 917
     {
918
-        if (! $this->_data_migration_class_to_filepath_map) {
918
+        if ( ! $this->_data_migration_class_to_filepath_map) {
919 919
             $this->_data_migration_class_to_filepath_map = [];
920 920
             foreach ($this->get_data_migration_script_folders() as $eeAddonClass => $folder_path) {
921 921
                 // strip any placeholders added to classname to make it a unique array key
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
                     ? $eeAddonClass
925 925
                     : '';
926 926
                 $folder_path  = EEH_File::end_with_directory_separator($folder_path);
927
-                $files        = glob($folder_path . '*.dms.php');
927
+                $files        = glob($folder_path.'*.dms.php');
928 928
                 if (empty($files)) {
929 929
                     continue;
930 930
                 }
@@ -951,7 +951,7 @@  discard block
 block discarded – undo
951 951
                             '4.3.0.alpha.019'
952 952
                         );
953 953
                     }
954
-                    $this->_data_migration_class_to_filepath_map[ $classname ] = $file;
954
+                    $this->_data_migration_class_to_filepath_map[$classname] = $file;
955 955
                 }
956 956
             }
957 957
             EEH_Autoloader::register_autoloader($this->_data_migration_class_to_filepath_map);
@@ -1024,10 +1024,10 @@  discard block
 block discarded – undo
1024 1024
             $versions_migrated_to                 = 'Unknown.1.0.0';
1025 1025
             // now just to make sure appears as last (in case the were previously a fatal error like this)
1026 1026
             // delete the old one
1027
-            delete_option(self::data_migration_script_option_prefix . $versions_migrated_to);
1027
+            delete_option(self::data_migration_script_option_prefix.$versions_migrated_to);
1028 1028
         }
1029 1029
         update_option(
1030
-            self::data_migration_script_option_prefix . $versions_migrated_to,
1030
+            self::data_migration_script_option_prefix.$versions_migrated_to,
1031 1031
             $last_ran_migration_script_properties
1032 1032
         );
1033 1033
     }
@@ -1048,9 +1048,9 @@  discard block
 block discarded – undo
1048 1048
         $successful_updates = true;
1049 1049
         foreach ($this->_data_migrations_ran as $plugin_slug => $migrations_ran_for_plugin) {
1050 1050
             foreach ($migrations_ran_for_plugin as $version_string => $array_or_migration_obj) {
1051
-                $plugin_slug_for_use_in_option_name = $plugin_slug . ".";
1051
+                $plugin_slug_for_use_in_option_name = $plugin_slug.".";
1052 1052
                 $option_name                        =
1053
-                    self::data_migration_script_option_prefix . $plugin_slug_for_use_in_option_name . $version_string;
1053
+                    self::data_migration_script_option_prefix.$plugin_slug_for_use_in_option_name.$version_string;
1054 1054
                 $old_option_value                   = get_option($option_name);
1055 1055
                 if ($array_or_migration_obj instanceof EE_Data_Migration_Script_Base) {
1056 1056
                     $script_array_for_saving = $array_or_migration_obj->properties_as_array();
@@ -1062,7 +1062,7 @@  discard block
 block discarded – undo
1062 1062
                         $successful_updates = update_option($option_name, $array_or_migration_obj);
1063 1063
                     }
1064 1064
                 }
1065
-                if (! $successful_updates) {
1065
+                if ( ! $successful_updates) {
1066 1066
                     global $wpdb;
1067 1067
                     return $wpdb->last_error;
1068 1068
                 }
@@ -1097,7 +1097,7 @@  discard block
 block discarded – undo
1097 1097
      */
1098 1098
     public function _instantiate_script_from_properties_array($properties_array)
1099 1099
     {
1100
-        if (! isset($properties_array['class'])) {
1100
+        if ( ! isset($properties_array['class'])) {
1101 1101
             throw new EE_Error(
1102 1102
                 sprintf(
1103 1103
                     esc_html__("Properties array  has no 'class' properties. Here's what it has: %s", "event_espresso"),
@@ -1106,14 +1106,14 @@  discard block
 block discarded – undo
1106 1106
             );
1107 1107
         }
1108 1108
         $class_name = $properties_array['class'];
1109
-        if (! class_exists($class_name)) {
1109
+        if ( ! class_exists($class_name)) {
1110 1110
             throw new EE_Error(sprintf(
1111 1111
                 esc_html__("There is no migration script named %s", "event_espresso"),
1112 1112
                 $class_name
1113 1113
             ));
1114 1114
         }
1115 1115
         $class = new $class_name();
1116
-        if (! $class instanceof EE_Data_Migration_Script_Base) {
1116
+        if ( ! $class instanceof EE_Data_Migration_Script_Base) {
1117 1117
             throw new EE_Error(
1118 1118
                 sprintf(
1119 1119
                     esc_html__(
@@ -1187,8 +1187,8 @@  discard block
 block discarded – undo
1187 1187
     public function get_migration_ran($version, $plugin_slug = 'Core')
1188 1188
     {
1189 1189
         $migrations_ran = $this->get_data_migrations_ran();
1190
-        if (isset($migrations_ran[ $plugin_slug ]) && isset($migrations_ran[ $plugin_slug ][ $version ])) {
1191
-            return $migrations_ran[ $plugin_slug ][ $version ];
1190
+        if (isset($migrations_ran[$plugin_slug]) && isset($migrations_ran[$plugin_slug][$version])) {
1191
+            return $migrations_ran[$plugin_slug][$version];
1192 1192
         } else {
1193 1193
             return null;
1194 1194
         }
@@ -1252,7 +1252,7 @@  discard block
 block discarded – undo
1252 1252
     public function enqueue_db_initialization_for($plugin_slug)
1253 1253
     {
1254 1254
         $queue = $this->get_db_initialization_queue();
1255
-        if (! in_array($plugin_slug, $queue)) {
1255
+        if ( ! in_array($plugin_slug, $queue)) {
1256 1256
             $queue[] = $plugin_slug;
1257 1257
         }
1258 1258
         update_option(self::db_init_queue_option_name, $queue);
@@ -1271,7 +1271,7 @@  discard block
 block discarded – undo
1271 1271
         $queue = $this->get_db_initialization_queue();
1272 1272
         foreach ($queue as $plugin_slug) {
1273 1273
             $most_up_to_date_dms = $this->get_most_up_to_date_dms($plugin_slug);
1274
-            if (! $most_up_to_date_dms) {
1274
+            if ( ! $most_up_to_date_dms) {
1275 1275
                 // if there is NO DMS for this plugin, obviously there's no schema to verify anyways
1276 1276
                 $verify_db = false;
1277 1277
             } else {
Please login to merge, or discard this patch.
core/db_classes/EE_Registration_Payment.class.php 1 patch
Indentation   +83 added lines, -83 removed lines patch added patch discarded remove patch
@@ -11,101 +11,101 @@
 block discarded – undo
11 11
  */
12 12
 class EE_Registration_Payment extends EE_Base_Class
13 13
 {
14
-    /**
15
-     * @param array  $props_n_values
16
-     * @param string $timezone
17
-     * @param array  $date_formats
18
-     * @return EE_Registration_Payment
19
-     * @throws EE_Error
20
-     * @throws ReflectionException
21
-     */
22
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
23
-    {
24
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
25
-        return $has_object
26
-            ? $has_object
27
-            : new self($props_n_values, false, $timezone, $date_formats);
28
-    }
14
+	/**
15
+	 * @param array  $props_n_values
16
+	 * @param string $timezone
17
+	 * @param array  $date_formats
18
+	 * @return EE_Registration_Payment
19
+	 * @throws EE_Error
20
+	 * @throws ReflectionException
21
+	 */
22
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
23
+	{
24
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
25
+		return $has_object
26
+			? $has_object
27
+			: new self($props_n_values, false, $timezone, $date_formats);
28
+	}
29 29
 
30 30
 
31
-    /**
32
-     * @param array  $props_n_values
33
-     * @param string $timezone
34
-     * @return EE_Registration_Payment
35
-     * @throws EE_Error
36
-     * @throws ReflectionException
37
-     */
38
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
39
-    {
40
-        return new self($props_n_values, true, $timezone);
41
-    }
31
+	/**
32
+	 * @param array  $props_n_values
33
+	 * @param string $timezone
34
+	 * @return EE_Registration_Payment
35
+	 * @throws EE_Error
36
+	 * @throws ReflectionException
37
+	 */
38
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
39
+	{
40
+		return new self($props_n_values, true, $timezone);
41
+	}
42 42
 
43 43
 
44
-    /**
45
-     * @return    int
46
-     * @throws EE_Error
47
-     * @throws ReflectionException
48
-     */
49
-    public function registration_ID(): int
50
-    {
51
-        return (int) $this->get('REG_ID');
52
-    }
44
+	/**
45
+	 * @return    int
46
+	 * @throws EE_Error
47
+	 * @throws ReflectionException
48
+	 */
49
+	public function registration_ID(): int
50
+	{
51
+		return (int) $this->get('REG_ID');
52
+	}
53 53
 
54 54
 
55
-    /**
56
-     * @return    int
57
-     * @throws EE_Error
58
-     * @throws ReflectionException
59
-     */
60
-    public function payment_ID(): int
61
-    {
62
-        return (int) $this->get('PAY_ID');
63
-    }
55
+	/**
56
+	 * @return    int
57
+	 * @throws EE_Error
58
+	 * @throws ReflectionException
59
+	 */
60
+	public function payment_ID(): int
61
+	{
62
+		return (int) $this->get('PAY_ID');
63
+	}
64 64
 
65 65
 
66
-    /**
67
-     * amount
68
-     *
69
-     * @access    public
70
-     * @return    float
71
-     * @throws EE_Error
72
-     * @throws ReflectionException
73
-     */
74
-    public function amount(): float
75
-    {
76
-        return (float) $this->get('RPY_amount');
77
-    }
66
+	/**
67
+	 * amount
68
+	 *
69
+	 * @access    public
70
+	 * @return    float
71
+	 * @throws EE_Error
72
+	 * @throws ReflectionException
73
+	 */
74
+	public function amount(): float
75
+	{
76
+		return (float) $this->get('RPY_amount');
77
+	}
78 78
 
79 79
 
80
-    /**
81
-     * @param float|int|string $amount
82
-     * @throws EE_Error
83
-     * @throws ReflectionException
84
-     */
85
-    public function set_amount($amount = 0.000)
86
-    {
87
-        $this->set('RPY_amount', (float) $amount);
88
-    }
80
+	/**
81
+	 * @param float|int|string $amount
82
+	 * @throws EE_Error
83
+	 * @throws ReflectionException
84
+	 */
85
+	public function set_amount($amount = 0.000)
86
+	{
87
+		$this->set('RPY_amount', (float) $amount);
88
+	}
89 89
 
90 90
 
91
-    /**
92
-     * @return EE_Registration
93
-     * @throws EE_Error
94
-     * @throws ReflectionException
95
-     */
96
-    public function registration(): EE_Registration
97
-    {
98
-        return $this->get_first_related('Registration');
99
-    }
91
+	/**
92
+	 * @return EE_Registration
93
+	 * @throws EE_Error
94
+	 * @throws ReflectionException
95
+	 */
96
+	public function registration(): EE_Registration
97
+	{
98
+		return $this->get_first_related('Registration');
99
+	}
100 100
 
101 101
 
102
-    /**
103
-     * @return EE_Payment
104
-     * @throws EE_Error
105
-     * @throws ReflectionException
106
-     */
107
-    public function payment(): EE_Payment
108
-    {
109
-        return $this->get_first_related('Payment');
110
-    }
102
+	/**
103
+	 * @return EE_Payment
104
+	 * @throws EE_Error
105
+	 * @throws ReflectionException
106
+	 */
107
+	public function payment(): EE_Payment
108
+	{
109
+		return $this->get_first_related('Payment');
110
+	}
111 111
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 2 patches
Indentation   +2139 added lines, -2139 removed lines patch added patch discarded remove patch
@@ -14,2147 +14,2147 @@
 block discarded – undo
14 14
  */
15 15
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
16 16
 {
17
-    /**
18
-     * TicKet Archived:
19
-     * constant used by ticket_status() to indicate that a ticket is archived
20
-     * and no longer available for purchase
21
-     */
22
-    const archived = 'TKA';
23
-
24
-    /**
25
-     * TicKet Expired:
26
-     * constant used by ticket_status() to indicate that a ticket is expired
27
-     * and no longer available for purchase
28
-     */
29
-    const expired = 'TKE';
30
-
31
-    /**
32
-     * TicKet On sale:
33
-     * constant used by ticket_status() to indicate that a ticket is On Sale
34
-     * and IS available for purchase
35
-     */
36
-    const onsale = 'TKO';
37
-
38
-    /**
39
-     * TicKet Pending:
40
-     * constant used by ticket_status() to indicate that a ticket is pending
41
-     * and is NOT YET available for purchase
42
-     */
43
-    const pending = 'TKP';
44
-
45
-    /**
46
-     * TicKet Sold out:
47
-     * constant used by ticket_status() to indicate that a ticket is sold out
48
-     * and no longer available for purchases
49
-     */
50
-    const sold_out = 'TKS';
51
-
52
-    /**
53
-     * extra meta key for tracking ticket reservations
54
-     *
55
-     * @type string
56
-     */
57
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
-
59
-    /**
60
-     * override of parent property
61
-     *
62
-     * @var EEM_Ticket
63
-     */
64
-    protected $_model;
65
-
66
-    /**
67
-     * cached result from method of the same name
68
-     *
69
-     * @var float $_ticket_total_with_taxes
70
-     */
71
-    private $_ticket_total_with_taxes;
72
-
73
-    /**
74
-     * @var TicketPriceModifiers
75
-     */
76
-    protected $ticket_price_modifiers;
77
-
78
-
79
-    /**
80
-     * @param array  $props_n_values          incoming values
81
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
82
-     *                                        used.)
83
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
84
-     *                                        date_format and the second value is the time format
85
-     * @return EE_Ticket
86
-     * @throws EE_Error
87
-     * @throws ReflectionException
88
-     */
89
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
90
-    {
91
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
92
-        return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
93
-    }
94
-
95
-
96
-    /**
97
-     * @param array  $props_n_values  incoming values from the database
98
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
99
-     *                                the website will be used.
100
-     * @return EE_Ticket
101
-     * @throws EE_Error
102
-     * @throws ReflectionException
103
-     */
104
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
105
-    {
106
-        return new self($props_n_values, true, $timezone);
107
-    }
108
-
109
-
110
-    /**
111
-     * @param array  $fieldValues
112
-     * @param false  $bydb
113
-     * @param string $timezone
114
-     * @param array  $date_formats
115
-     * @throws EE_Error
116
-     * @throws ReflectionException
117
-     */
118
-    public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
119
-    {
120
-        parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
121
-        $this->ticket_price_modifiers = new TicketPriceModifiers($this);
122
-    }
123
-
124
-
125
-    /**
126
-     * @return bool
127
-     * @throws EE_Error
128
-     * @throws ReflectionException
129
-     */
130
-    public function parent()
131
-    {
132
-        return $this->get('TKT_parent');
133
-    }
134
-
135
-
136
-    /**
137
-     * return if a ticket has quantities available for purchase
138
-     *
139
-     * @param int $DTT_ID the primary key for a particular datetime
140
-     * @return boolean
141
-     * @throws EE_Error
142
-     * @throws ReflectionException
143
-     */
144
-    public function available($DTT_ID = 0)
145
-    {
146
-        // are we checking availability for a particular datetime ?
147
-        if ($DTT_ID) {
148
-            // get that datetime object
149
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
150
-            // if  ticket sales for this datetime have exceeded the reg limit...
151
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
152
-                return false;
153
-            }
154
-        }
155
-        // datetime is still open for registration, but is this ticket sold out ?
156
-        return $this->qty() < 1 || $this->qty() > $this->sold();
157
-    }
158
-
159
-
160
-    /**
161
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
162
-     *
163
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
164
-     *                               relevant status const
165
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
166
-     *                               further processing
167
-     * @return mixed status int if the display string isn't requested
168
-     * @throws EE_Error
169
-     * @throws ReflectionException
170
-     */
171
-    public function ticket_status($display = false, $remaining = null)
172
-    {
173
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
174
-        if (! $remaining) {
175
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
176
-        }
177
-        if ($this->get('TKT_deleted')) {
178
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
179
-        }
180
-        if ($this->is_expired()) {
181
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
182
-        }
183
-        if ($this->is_pending()) {
184
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
185
-        }
186
-        if ($this->is_on_sale()) {
187
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
188
-        }
189
-        return '';
190
-    }
191
-
192
-
193
-    /**
194
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
195
-     * considering ALL the factors used for figuring that out.
196
-     *
197
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
198
-     * @return boolean         true = tickets remaining, false not.
199
-     * @throws EE_Error
200
-     * @throws ReflectionException
201
-     */
202
-    public function is_remaining($DTT_ID = 0)
203
-    {
204
-        $num_remaining = $this->remaining($DTT_ID);
205
-        if ($num_remaining === 0) {
206
-            return false;
207
-        }
208
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
209
-            return false;
210
-        }
211
-        return true;
212
-    }
213
-
214
-
215
-    /**
216
-     * return the total number of tickets available for purchase
217
-     *
218
-     * @param int $DTT_ID  the primary key for a particular datetime.
219
-     *                     set to 0 for all related datetimes
220
-     * @return int
221
-     * @throws EE_Error
222
-     * @throws ReflectionException
223
-     */
224
-    public function remaining($DTT_ID = 0)
225
-    {
226
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
227
-    }
228
-
229
-
230
-    /**
231
-     * Gets min
232
-     *
233
-     * @return int
234
-     * @throws EE_Error
235
-     * @throws ReflectionException
236
-     */
237
-    public function min()
238
-    {
239
-        return $this->get('TKT_min');
240
-    }
241
-
242
-
243
-    /**
244
-     * return if a ticket is no longer available cause its available dates have expired.
245
-     *
246
-     * @return boolean
247
-     * @throws EE_Error
248
-     * @throws ReflectionException
249
-     */
250
-    public function is_expired()
251
-    {
252
-        return ($this->get_raw('TKT_end_date') < time());
253
-    }
254
-
255
-
256
-    /**
257
-     * Return if a ticket is yet to go on sale or not
258
-     *
259
-     * @return boolean
260
-     * @throws EE_Error
261
-     * @throws ReflectionException
262
-     */
263
-    public function is_pending()
264
-    {
265
-        return ($this->get_raw('TKT_start_date') >= time());
266
-    }
267
-
268
-
269
-    /**
270
-     * Return if a ticket is on sale or not
271
-     *
272
-     * @return boolean
273
-     * @throws EE_Error
274
-     * @throws ReflectionException
275
-     */
276
-    public function is_on_sale()
277
-    {
278
-        return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
279
-    }
280
-
281
-
282
-    /**
283
-     * This returns the chronologically last datetime that this ticket is associated with
284
-     *
285
-     * @param string $date_format
286
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
287
-     *                            the end date ie: Jan 01 "to" Dec 31
288
-     * @return string
289
-     * @throws EE_Error
290
-     * @throws ReflectionException
291
-     */
292
-    public function date_range($date_format = '', $conjunction = ' - ')
293
-    {
294
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
295
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
296
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
297
-            : '';
298
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
299
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
300
-            : '';
301
-
302
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
303
-    }
304
-
305
-
306
-    /**
307
-     * This returns the chronologically first datetime that this ticket is associated with
308
-     *
309
-     * @return EE_Datetime
310
-     * @throws EE_Error
311
-     * @throws ReflectionException
312
-     */
313
-    public function first_datetime()
314
-    {
315
-        $datetimes = $this->datetimes(['limit' => 1]);
316
-        return reset($datetimes);
317
-    }
318
-
319
-
320
-    /**
321
-     * Gets all the datetimes this ticket can be used for attending.
322
-     * Unless otherwise specified, orders datetimes by start date.
323
-     *
324
-     * @param array $query_params
325
-     * @return EE_Datetime[]|EE_Base_Class[]
326
-     * @throws EE_Error
327
-     * @throws ReflectionException
328
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
329
-     */
330
-    public function datetimes($query_params = [])
331
-    {
332
-        if (! isset($query_params['order_by'])) {
333
-            $query_params['order_by']['DTT_order'] = 'ASC';
334
-        }
335
-        return $this->get_many_related('Datetime', $query_params);
336
-    }
337
-
338
-
339
-    /**
340
-     * This returns the chronologically last datetime that this ticket is associated with
341
-     *
342
-     * @return EE_Datetime
343
-     * @throws EE_Error
344
-     * @throws ReflectionException
345
-     */
346
-    public function last_datetime()
347
-    {
348
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
349
-        return end($datetimes);
350
-    }
351
-
352
-
353
-    /**
354
-     * This returns the total tickets sold depending on the given parameters.
355
-     *
356
-     * @param string $what    Can be one of two options: 'ticket', 'datetime'.
357
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
358
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
359
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
360
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
361
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
362
-     * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
363
-     * @return mixed (array|int)          how many tickets have sold
364
-     * @throws EE_Error
365
-     * @throws ReflectionException
366
-     */
367
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
368
-    {
369
-        $total        = 0;
370
-        $tickets_sold = $this->_all_tickets_sold();
371
-        switch ($what) {
372
-            case 'ticket':
373
-                return $tickets_sold['ticket'];
374
-
375
-            case 'datetime':
376
-                if (empty($tickets_sold['datetime'])) {
377
-                    return $total;
378
-                }
379
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
380
-                    EE_Error::add_error(
381
-                        esc_html__(
382
-                            'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
383
-                            'event_espresso'
384
-                        ),
385
-                        __FILE__,
386
-                        __FUNCTION__,
387
-                        __LINE__
388
-                    );
389
-                    return $total;
390
-                }
391
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
392
-
393
-            default:
394
-                return $total;
395
-        }
396
-    }
397
-
398
-
399
-    /**
400
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
401
-     *
402
-     * @return EE_Ticket[]
403
-     * @throws EE_Error
404
-     * @throws ReflectionException
405
-     */
406
-    protected function _all_tickets_sold()
407
-    {
408
-        $datetimes    = $this->get_many_related('Datetime');
409
-        $tickets_sold = [];
410
-        if (! empty($datetimes)) {
411
-            foreach ($datetimes as $datetime) {
412
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
413
-            }
414
-        }
415
-        // Tickets sold
416
-        $tickets_sold['ticket'] = $this->sold();
417
-        return $tickets_sold;
418
-    }
419
-
420
-
421
-    /**
422
-     * This returns the base price object for the ticket.
423
-     *
424
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
425
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
426
-     * @throws EE_Error
427
-     * @throws ReflectionException
428
-     */
429
-    public function base_price(bool $return_array = false)
430
-    {
431
-        $base_price = $this->ticket_price_modifiers->getBasePrice();
432
-        if (! empty($base_price)) {
433
-            return $return_array ? $base_price : reset($base_price);
434
-        }
435
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
436
-        return $return_array
437
-            ? $this->get_many_related('Price', [$_where])
438
-            : $this->get_first_related('Price', [$_where]);
439
-    }
440
-
441
-
442
-    /**
443
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
444
-     *
445
-     * @return EE_Price[]
446
-     * @throws EE_Error
447
-     * @throws ReflectionException
448
-     */
449
-    public function price_modifiers(): array
450
-    {
451
-        $price_modifiers = $this->usesGlobalTaxes()
452
-            ? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
453
-            : $this->ticket_price_modifiers->getAllModifiersForTicket();
454
-        if (! empty($price_modifiers)) {
455
-            return $price_modifiers;
456
-        }
457
-        return $this->prices(
458
-            [
459
-                [
460
-                    'Price_Type.PBT_ID' => [
461
-                        'NOT IN',
462
-                        [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
463
-                    ],
464
-                ],
465
-            ]
466
-        );
467
-    }
468
-
469
-
470
-    /**
471
-     * This returns ONLY the TAX price modifiers for the ticket
472
-     *
473
-     * @return EE_Price[]
474
-     * @throws EE_Error
475
-     * @throws ReflectionException
476
-     */
477
-    public function tax_price_modifiers(): array
478
-    {
479
-        $tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
480
-        if (! empty($tax_price_modifiers)) {
481
-            return $tax_price_modifiers;
482
-        }
483
-        return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
484
-    }
485
-
486
-
487
-    /**
488
-     * Gets all the prices that combine to form the final price of this ticket
489
-     *
490
-     * @param array $query_params
491
-     * @return EE_Price[]|EE_Base_Class[]
492
-     * @throws EE_Error
493
-     * @throws ReflectionException
494
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
495
-     */
496
-    public function prices(array $query_params = []): array
497
-    {
498
-        if (! isset($query_params['order_by'])) {
499
-            $query_params['order_by']['PRC_order'] = 'ASC';
500
-        }
501
-        return $this->get_many_related('Price', $query_params);
502
-    }
503
-
504
-
505
-    /**
506
-     * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
507
-     *
508
-     * @param array $query_params
509
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
510
-     * @throws EE_Error
511
-     * @throws ReflectionException
512
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
513
-     */
514
-    public function datetime_tickets($query_params = [])
515
-    {
516
-        return $this->get_many_related('Datetime_Ticket', $query_params);
517
-    }
518
-
519
-
520
-    /**
521
-     * Gets all the datetimes from the db ordered by DTT_order
522
-     *
523
-     * @param boolean $show_expired
524
-     * @param boolean $show_deleted
525
-     * @return EE_Datetime[]
526
-     * @throws EE_Error
527
-     * @throws ReflectionException
528
-     */
529
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
530
-    {
531
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
532
-            $this->ID(),
533
-            $show_expired,
534
-            $show_deleted
535
-        );
536
-    }
537
-
538
-
539
-    /**
540
-     * Gets ID
541
-     *
542
-     * @return int
543
-     * @throws EE_Error
544
-     * @throws ReflectionException
545
-     */
546
-    public function ID()
547
-    {
548
-        return (int) $this->get('TKT_ID');
549
-    }
550
-
551
-
552
-    /**
553
-     * get the author of the ticket.
554
-     *
555
-     * @return int
556
-     * @throws EE_Error
557
-     * @throws ReflectionException
558
-     * @since 4.5.0
559
-     */
560
-    public function wp_user()
561
-    {
562
-        return $this->get('TKT_wp_user');
563
-    }
564
-
565
-
566
-    /**
567
-     * Gets the template for the ticket
568
-     *
569
-     * @return EE_Ticket_Template|EE_Base_Class
570
-     * @throws EE_Error
571
-     * @throws ReflectionException
572
-     */
573
-    public function template()
574
-    {
575
-        return $this->get_first_related('Ticket_Template');
576
-    }
577
-
578
-
579
-    /**
580
-     * Simply returns an array of EE_Price objects that are taxes.
581
-     *
582
-     * @return EE_Price[]
583
-     * @throws EE_Error
584
-     * @throws ReflectionException
585
-     */
586
-    public function get_ticket_taxes_for_admin(): array
587
-    {
588
-        return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
589
-    }
590
-
591
-
592
-    /**
593
-     * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
594
-     * as opposed to having tax price modifiers added directly to each ticket
595
-     *
596
-     * @return bool
597
-     * @throws EE_Error
598
-     * @throws ReflectionException
599
-     * @since   5.0.0.p
600
-     */
601
-    public function usesGlobalTaxes(): bool
602
-    {
603
-        return $this->taxable();
604
-    }
605
-
606
-
607
-    /**
608
-     * @return float
609
-     * @throws EE_Error
610
-     * @throws ReflectionException
611
-     */
612
-    public function ticket_price()
613
-    {
614
-        return $this->get('TKT_price');
615
-    }
616
-
617
-
618
-    /**
619
-     * @return mixed
620
-     * @throws EE_Error
621
-     * @throws ReflectionException
622
-     */
623
-    public function pretty_price()
624
-    {
625
-        return $this->get_pretty('TKT_price');
626
-    }
627
-
628
-
629
-    /**
630
-     * @return bool
631
-     * @throws EE_Error
632
-     * @throws ReflectionException
633
-     */
634
-    public function is_free()
635
-    {
636
-        return $this->get_ticket_total_with_taxes() === (float) 0;
637
-    }
638
-
639
-
640
-    /**
641
-     * get_ticket_total_with_taxes
642
-     *
643
-     * @param bool $no_cache
644
-     * @return float
645
-     * @throws EE_Error
646
-     * @throws ReflectionException
647
-     */
648
-    public function get_ticket_total_with_taxes($no_cache = false)
649
-    {
650
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
651
-            $this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
652
-                ? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
653
-                : $this->ticket_price();
654
-        }
655
-        return (float) $this->_ticket_total_with_taxes;
656
-    }
657
-
658
-
659
-    /**
660
-     * @throws EE_Error
661
-     * @throws ReflectionException
662
-     */
663
-    public function ensure_TKT_Price_correct()
664
-    {
665
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
666
-        $this->save();
667
-    }
668
-
669
-
670
-    /**
671
-     * @return float
672
-     * @throws EE_Error
673
-     * @throws ReflectionException
674
-     */
675
-    public function get_ticket_subtotal()
676
-    {
677
-        return EE_Taxes::get_subtotal_for_admin($this);
678
-    }
679
-
680
-
681
-    /**
682
-     * Returns the total taxes applied to this ticket
683
-     *
684
-     * @return float
685
-     * @throws EE_Error
686
-     * @throws ReflectionException
687
-     */
688
-    public function get_ticket_taxes_total_for_admin()
689
-    {
690
-        return EE_Taxes::get_total_taxes_for_admin($this);
691
-    }
692
-
693
-
694
-    /**
695
-     * Sets name
696
-     *
697
-     * @param string $name
698
-     * @throws EE_Error
699
-     * @throws ReflectionException
700
-     */
701
-    public function set_name($name)
702
-    {
703
-        $this->set('TKT_name', $name);
704
-    }
705
-
706
-
707
-    /**
708
-     * Gets description
709
-     *
710
-     * @return string
711
-     * @throws EE_Error
712
-     * @throws ReflectionException
713
-     */
714
-    public function description()
715
-    {
716
-        return $this->get('TKT_description');
717
-    }
718
-
719
-
720
-    /**
721
-     * Sets description
722
-     *
723
-     * @param string $description
724
-     * @throws EE_Error
725
-     * @throws ReflectionException
726
-     */
727
-    public function set_description($description)
728
-    {
729
-        $this->set('TKT_description', $description);
730
-    }
731
-
732
-
733
-    /**
734
-     * Gets start_date
735
-     *
736
-     * @param string|null $date_format
737
-     * @param string|null $time_format
738
-     * @return string
739
-     * @throws EE_Error
740
-     * @throws ReflectionException
741
-     */
742
-    public function start_date(?string $date_format = '', ?string $time_format = ''): string
743
-    {
744
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
745
-    }
746
-
747
-
748
-    /**
749
-     * Sets start_date
750
-     *
751
-     * @param string $start_date
752
-     * @return void
753
-     * @throws EE_Error
754
-     * @throws ReflectionException
755
-     */
756
-    public function set_start_date($start_date)
757
-    {
758
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
759
-    }
760
-
761
-
762
-    /**
763
-     * Gets end_date
764
-     *
765
-     * @param string|null $date_format
766
-     * @param string|null $time_format
767
-     * @return string
768
-     * @throws EE_Error
769
-     * @throws ReflectionException
770
-     */
771
-    public function end_date(?string $date_format = '', ?string $time_format = ''): string
772
-    {
773
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
774
-    }
775
-
776
-
777
-    /**
778
-     * Sets end_date
779
-     *
780
-     * @param string $end_date
781
-     * @return void
782
-     * @throws EE_Error
783
-     * @throws ReflectionException
784
-     */
785
-    public function set_end_date($end_date)
786
-    {
787
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
788
-    }
789
-
790
-
791
-    /**
792
-     * Sets sell until time
793
-     *
794
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
795
-     * @throws EE_Error
796
-     * @throws ReflectionException
797
-     * @since 4.5.0
798
-     */
799
-    public function set_end_time($time)
800
-    {
801
-        $this->_set_time_for($time, 'TKT_end_date');
802
-    }
803
-
804
-
805
-    /**
806
-     * Sets min
807
-     *
808
-     * @param int $min
809
-     * @return void
810
-     * @throws EE_Error
811
-     * @throws ReflectionException
812
-     */
813
-    public function set_min($min)
814
-    {
815
-        $this->set('TKT_min', $min);
816
-    }
817
-
818
-
819
-    /**
820
-     * Gets max
821
-     *
822
-     * @return int
823
-     * @throws EE_Error
824
-     * @throws ReflectionException
825
-     */
826
-    public function max()
827
-    {
828
-        return $this->get('TKT_max');
829
-    }
830
-
831
-
832
-    /**
833
-     * Sets max
834
-     *
835
-     * @param int $max
836
-     * @return void
837
-     * @throws EE_Error
838
-     * @throws ReflectionException
839
-     */
840
-    public function set_max($max)
841
-    {
842
-        $this->set('TKT_max', $max);
843
-    }
844
-
845
-
846
-    /**
847
-     * Sets price
848
-     *
849
-     * @param float $price
850
-     * @return void
851
-     * @throws EE_Error
852
-     * @throws ReflectionException
853
-     */
854
-    public function set_price($price)
855
-    {
856
-        $this->set('TKT_price', $price);
857
-    }
858
-
859
-
860
-    /**
861
-     * Gets sold
862
-     *
863
-     * @return int
864
-     * @throws EE_Error
865
-     * @throws ReflectionException
866
-     */
867
-    public function sold(): int
868
-    {
869
-        return (int) $this->get_raw('TKT_sold');
870
-    }
871
-
872
-
873
-    /**
874
-     * Sets sold
875
-     *
876
-     * @param int $sold
877
-     * @return void
878
-     * @throws EE_Error
879
-     * @throws ReflectionException
880
-     */
881
-    public function set_sold($sold)
882
-    {
883
-        // sold can not go below zero
884
-        $sold = max(0, $sold);
885
-        $this->set('TKT_sold', $sold);
886
-    }
887
-
888
-
889
-    /**
890
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
891
-     * associated datetimes.
892
-     *
893
-     * @param int $qty
894
-     * @return boolean
895
-     * @throws EE_Error
896
-     * @throws InvalidArgumentException
897
-     * @throws InvalidDataTypeException
898
-     * @throws InvalidInterfaceException
899
-     * @throws ReflectionException
900
-     * @since 4.9.80.p
901
-     */
902
-    public function increaseSold($qty = 1)
903
-    {
904
-        $qty = absint($qty);
905
-        // increment sold and decrement reserved datetime quantities simultaneously
906
-        // don't worry about failures, because they must have already had a spot reserved
907
-        $this->increaseSoldForDatetimes($qty);
908
-        // Increment and decrement ticket quantities simultaneously
909
-        $success = $this->adjustNumericFieldsInDb(
910
-            [
911
-                'TKT_reserved' => $qty * -1,
912
-                'TKT_sold'     => $qty,
913
-            ]
914
-        );
915
-        do_action(
916
-            'AHEE__EE_Ticket__increase_sold',
917
-            $this,
918
-            $qty,
919
-            $this->sold(),
920
-            $success
921
-        );
922
-        return $success;
923
-    }
924
-
925
-
926
-    /**
927
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
928
-     *
929
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
930
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
931
-     * @param EE_Datetime[] $datetimes
932
-     * @throws EE_Error
933
-     * @throws InvalidArgumentException
934
-     * @throws InvalidDataTypeException
935
-     * @throws InvalidInterfaceException
936
-     * @throws ReflectionException
937
-     * @since 4.9.80.p
938
-     */
939
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
940
-    {
941
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
942
-        foreach ($datetimes as $datetime) {
943
-            $datetime->increaseSold($qty);
944
-        }
945
-    }
946
-
947
-
948
-    /**
949
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
950
-     * DB and then updates the model objects.
951
-     * Does not affect the reserved counts.
952
-     *
953
-     * @param int $qty
954
-     * @return boolean
955
-     * @throws EE_Error
956
-     * @throws InvalidArgumentException
957
-     * @throws InvalidDataTypeException
958
-     * @throws InvalidInterfaceException
959
-     * @throws ReflectionException
960
-     * @since 4.9.80.p
961
-     */
962
-    public function decreaseSold($qty = 1)
963
-    {
964
-        $qty = absint($qty);
965
-        $this->decreaseSoldForDatetimes($qty);
966
-        $success = $this->adjustNumericFieldsInDb(
967
-            [
968
-                'TKT_sold' => $qty * -1,
969
-            ]
970
-        );
971
-        do_action(
972
-            'AHEE__EE_Ticket__decrease_sold',
973
-            $this,
974
-            $qty,
975
-            $this->sold(),
976
-            $success
977
-        );
978
-        return $success;
979
-    }
980
-
981
-
982
-    /**
983
-     * Decreases sold on related datetimes
984
-     *
985
-     * @param int           $qty
986
-     * @param EE_Datetime[] $datetimes
987
-     * @return void
988
-     * @throws EE_Error
989
-     * @throws InvalidArgumentException
990
-     * @throws InvalidDataTypeException
991
-     * @throws InvalidInterfaceException
992
-     * @throws ReflectionException
993
-     * @since 4.9.80.p
994
-     */
995
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
996
-    {
997
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
998
-        if (is_array($datetimes)) {
999
-            foreach ($datetimes as $datetime) {
1000
-                if ($datetime instanceof EE_Datetime) {
1001
-                    $datetime->decreaseSold($qty);
1002
-                }
1003
-            }
1004
-        }
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * Gets qty of reserved tickets
1010
-     *
1011
-     * @return int
1012
-     * @throws EE_Error
1013
-     * @throws ReflectionException
1014
-     */
1015
-    public function reserved(): int
1016
-    {
1017
-        return (int) $this->get_raw('TKT_reserved');
1018
-    }
1019
-
1020
-
1021
-    /**
1022
-     * Sets reserved
1023
-     *
1024
-     * @param int $reserved
1025
-     * @return void
1026
-     * @throws EE_Error
1027
-     * @throws ReflectionException
1028
-     */
1029
-    public function set_reserved($reserved)
1030
-    {
1031
-        // reserved can not go below zero
1032
-        $reserved = max(0, (int) $reserved);
1033
-        $this->set('TKT_reserved', $reserved);
1034
-    }
1035
-
1036
-
1037
-    /**
1038
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1039
-     *
1040
-     * @param int    $qty
1041
-     * @param string $source
1042
-     * @return bool whether we successfully reserved the ticket or not.
1043
-     * @throws EE_Error
1044
-     * @throws InvalidArgumentException
1045
-     * @throws ReflectionException
1046
-     * @throws InvalidDataTypeException
1047
-     * @throws InvalidInterfaceException
1048
-     * @since 4.9.80.p
1049
-     */
1050
-    public function increaseReserved($qty = 1, $source = 'unknown')
1051
-    {
1052
-        $qty = absint($qty);
1053
-        do_action(
1054
-            'AHEE__EE_Ticket__increase_reserved__begin',
1055
-            $this,
1056
-            $qty,
1057
-            $source
1058
-        );
1059
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1060
-        $success                         = false;
1061
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1062
-        if ($datetimes_adjusted_successfully) {
1063
-            $success = $this->incrementFieldConditionallyInDb(
1064
-                'TKT_reserved',
1065
-                'TKT_sold',
1066
-                'TKT_qty',
1067
-                $qty
1068
-            );
1069
-            if (! $success) {
1070
-                // The datetimes were successfully bumped, but not the
1071
-                // ticket. So we need to manually rollback the datetimes.
1072
-                $this->decreaseReservedForDatetimes($qty);
1073
-            }
1074
-        }
1075
-        do_action(
1076
-            'AHEE__EE_Ticket__increase_reserved',
1077
-            $this,
1078
-            $qty,
1079
-            $this->reserved(),
1080
-            $success
1081
-        );
1082
-        return $success;
1083
-    }
1084
-
1085
-
1086
-    /**
1087
-     * Increases reserved counts on related datetimes
1088
-     *
1089
-     * @param int           $qty
1090
-     * @param EE_Datetime[] $datetimes
1091
-     * @return boolean indicating success
1092
-     * @throws EE_Error
1093
-     * @throws InvalidArgumentException
1094
-     * @throws InvalidDataTypeException
1095
-     * @throws InvalidInterfaceException
1096
-     * @throws ReflectionException
1097
-     * @since 4.9.80.p
1098
-     */
1099
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1100
-    {
1101
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1102
-        $datetimes_updated = [];
1103
-        $limit_exceeded    = false;
1104
-        if (is_array($datetimes)) {
1105
-            foreach ($datetimes as $datetime) {
1106
-                if ($datetime instanceof EE_Datetime) {
1107
-                    if ($datetime->increaseReserved($qty)) {
1108
-                        $datetimes_updated[] = $datetime;
1109
-                    } else {
1110
-                        $limit_exceeded = true;
1111
-                        break;
1112
-                    }
1113
-                }
1114
-            }
1115
-            // If somewhere along the way we detected a datetime whose
1116
-            // limit was exceeded, do a manual rollback.
1117
-            if ($limit_exceeded) {
1118
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1119
-                return false;
1120
-            }
1121
-        }
1122
-        return true;
1123
-    }
1124
-
1125
-
1126
-    /**
1127
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1128
-     *
1129
-     * @param int    $qty
1130
-     * @param bool   $adjust_datetimes
1131
-     * @param string $source
1132
-     * @return boolean
1133
-     * @throws EE_Error
1134
-     * @throws InvalidArgumentException
1135
-     * @throws ReflectionException
1136
-     * @throws InvalidDataTypeException
1137
-     * @throws InvalidInterfaceException
1138
-     * @since 4.9.80.p
1139
-     */
1140
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1141
-    {
1142
-        $qty = absint($qty);
1143
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1144
-        if ($adjust_datetimes) {
1145
-            $this->decreaseReservedForDatetimes($qty);
1146
-        }
1147
-        $success = $this->adjustNumericFieldsInDb(
1148
-            [
1149
-                'TKT_reserved' => $qty * -1,
1150
-            ]
1151
-        );
1152
-        do_action(
1153
-            'AHEE__EE_Ticket__decrease_reserved',
1154
-            $this,
1155
-            $qty,
1156
-            $this->reserved(),
1157
-            $success
1158
-        );
1159
-        return $success;
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * Decreases the reserved count on the specified datetimes.
1165
-     *
1166
-     * @param int           $qty
1167
-     * @param EE_Datetime[] $datetimes
1168
-     * @throws EE_Error
1169
-     * @throws InvalidArgumentException
1170
-     * @throws ReflectionException
1171
-     * @throws InvalidDataTypeException
1172
-     * @throws InvalidInterfaceException
1173
-     * @since 4.9.80.p
1174
-     */
1175
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1176
-    {
1177
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1178
-        foreach ($datetimes as $datetime) {
1179
-            if ($datetime instanceof EE_Datetime) {
1180
-                $datetime->decreaseReserved($qty);
1181
-            }
1182
-        }
1183
-    }
1184
-
1185
-
1186
-    /**
1187
-     * Gets ticket quantity
1188
-     *
1189
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1190
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1191
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1192
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1193
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1194
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1195
-     * @return int
1196
-     * @throws EE_Error
1197
-     * @throws ReflectionException
1198
-     */
1199
-    public function qty($context = '')
1200
-    {
1201
-        switch ($context) {
1202
-            case 'reg_limit':
1203
-                return $this->real_quantity_on_ticket();
1204
-            case 'saleable':
1205
-                return $this->real_quantity_on_ticket('saleable');
1206
-            default:
1207
-                return $this->get_raw('TKT_qty');
1208
-        }
1209
-    }
1210
-
1211
-
1212
-    /**
1213
-     * Gets ticket quantity
1214
-     *
1215
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1216
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1217
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1218
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1219
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1220
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1221
-     *                            set to 0 for all related datetimes
1222
-     * @return int|float          int for finite quantity or float for INF
1223
-     * @throws EE_Error
1224
-     * @throws ReflectionException
1225
-     */
1226
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1227
-    {
1228
-        $raw = $this->get_raw('TKT_qty');
1229
-        // return immediately if it's zero
1230
-        if ($raw === 0) {
1231
-            return $raw;
1232
-        }
1233
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1234
-        // ensure qty doesn't exceed raw value for THIS ticket
1235
-        $qty = min(EE_INF, $raw);
1236
-        // echo "\n . qty: " . $qty . '<br />';
1237
-        // calculate this ticket's total sales and reservations
1238
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1239
-        // echo "\n . sold: " . $this->sold() . '<br />';
1240
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1241
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1242
-        // first we need to calculate the maximum number of tickets available for the datetime
1243
-        // do we want data for one datetime or all of them ?
1244
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1245
-        $datetimes    = $this->datetimes($query_params);
1246
-        if (is_array($datetimes) && ! empty($datetimes)) {
1247
-            foreach ($datetimes as $datetime) {
1248
-                if ($datetime instanceof EE_Datetime) {
1249
-                    $datetime->refresh_from_db();
1250
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1251
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1252
-                    // initialize with no restrictions for each datetime
1253
-                    // but adjust datetime qty based on datetime reg limit
1254
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1255
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1256
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1257
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1258
-                    // and reservations for this datetime, that do NOT include sales and reservations
1259
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1260
-                    if ($context === 'saleable') {
1261
-                        $datetime_qty = max(
1262
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1263
-                            0
1264
-                        );
1265
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1266
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1267
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1268
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1269
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1270
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1271
-                    }
1272
-                    $qty = min($datetime_qty, $qty);
1273
-                    // echo "\n . . qty: " . $qty . '<br />';
1274
-                }
1275
-            }
1276
-        }
1277
-        // NOW that we know the  maximum number of tickets available for the datetime
1278
-        // we can finally factor in the details for this specific ticket
1279
-        if ($qty > 0 && $context === 'saleable') {
1280
-            // and subtract the sales for THIS ticket
1281
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1282
-            // echo "\n . qty: " . $qty . '<br />';
1283
-        }
1284
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1285
-        return $qty;
1286
-    }
1287
-
1288
-
1289
-    /**
1290
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1291
-     *
1292
-     * @param int $qty
1293
-     * @return void
1294
-     * @throws EE_Error
1295
-     * @throws ReflectionException
1296
-     */
1297
-    public function set_qty($qty)
1298
-    {
1299
-        $datetimes = $this->datetimes();
1300
-        foreach ($datetimes as $datetime) {
1301
-            if ($datetime instanceof EE_Datetime) {
1302
-                $qty = min($qty, $datetime->reg_limit());
1303
-            }
1304
-        }
1305
-        $this->set('TKT_qty', $qty);
1306
-    }
1307
-
1308
-
1309
-    /**
1310
-     * Gets uses
1311
-     *
1312
-     * @return int
1313
-     * @throws EE_Error
1314
-     * @throws ReflectionException
1315
-     */
1316
-    public function uses()
1317
-    {
1318
-        return $this->get('TKT_uses');
1319
-    }
1320
-
1321
-
1322
-    /**
1323
-     * Sets uses
1324
-     *
1325
-     * @param int $uses
1326
-     * @return void
1327
-     * @throws EE_Error
1328
-     * @throws ReflectionException
1329
-     */
1330
-    public function set_uses($uses)
1331
-    {
1332
-        $this->set('TKT_uses', $uses);
1333
-    }
1334
-
1335
-
1336
-    /**
1337
-     * returns whether ticket is required or not.
1338
-     *
1339
-     * @return boolean
1340
-     * @throws EE_Error
1341
-     * @throws ReflectionException
1342
-     */
1343
-    public function required()
1344
-    {
1345
-        return $this->get('TKT_required');
1346
-    }
1347
-
1348
-
1349
-    /**
1350
-     * sets the TKT_required property
1351
-     *
1352
-     * @param boolean $required
1353
-     * @return void
1354
-     * @throws EE_Error
1355
-     * @throws ReflectionException
1356
-     */
1357
-    public function set_required($required)
1358
-    {
1359
-        $this->set('TKT_required', $required);
1360
-    }
1361
-
1362
-
1363
-    /**
1364
-     * Gets taxable
1365
-     *
1366
-     * @return boolean
1367
-     * @throws EE_Error
1368
-     * @throws ReflectionException
1369
-     */
1370
-    public function taxable()
1371
-    {
1372
-        return $this->get('TKT_taxable');
1373
-    }
1374
-
1375
-
1376
-    /**
1377
-     * Sets taxable
1378
-     *
1379
-     * @param boolean $taxable
1380
-     * @return void
1381
-     * @throws EE_Error
1382
-     * @throws ReflectionException
1383
-     */
1384
-    public function set_taxable($taxable)
1385
-    {
1386
-        $this->set('TKT_taxable', $taxable);
1387
-    }
1388
-
1389
-
1390
-    /**
1391
-     * Gets is_default
1392
-     *
1393
-     * @return boolean
1394
-     * @throws EE_Error
1395
-     * @throws ReflectionException
1396
-     */
1397
-    public function is_default()
1398
-    {
1399
-        return $this->get('TKT_is_default');
1400
-    }
1401
-
1402
-
1403
-    /**
1404
-     * Sets is_default
1405
-     *
1406
-     * @param boolean $is_default
1407
-     * @return void
1408
-     * @throws EE_Error
1409
-     * @throws ReflectionException
1410
-     */
1411
-    public function set_is_default($is_default)
1412
-    {
1413
-        $this->set('TKT_is_default', $is_default);
1414
-    }
1415
-
1416
-
1417
-    /**
1418
-     * Gets order
1419
-     *
1420
-     * @return int
1421
-     * @throws EE_Error
1422
-     * @throws ReflectionException
1423
-     */
1424
-    public function order()
1425
-    {
1426
-        return $this->get('TKT_order');
1427
-    }
1428
-
1429
-
1430
-    /**
1431
-     * Sets order
1432
-     *
1433
-     * @param int $order
1434
-     * @return void
1435
-     * @throws EE_Error
1436
-     * @throws ReflectionException
1437
-     */
1438
-    public function set_order($order)
1439
-    {
1440
-        $this->set('TKT_order', $order);
1441
-    }
1442
-
1443
-
1444
-    /**
1445
-     * Gets row
1446
-     *
1447
-     * @return int
1448
-     * @throws EE_Error
1449
-     * @throws ReflectionException
1450
-     */
1451
-    public function row()
1452
-    {
1453
-        return $this->get('TKT_row');
1454
-    }
1455
-
1456
-
1457
-    /**
1458
-     * Sets row
1459
-     *
1460
-     * @param int $row
1461
-     * @return void
1462
-     * @throws EE_Error
1463
-     * @throws ReflectionException
1464
-     */
1465
-    public function set_row($row)
1466
-    {
1467
-        $this->set('TKT_row', $row);
1468
-    }
1469
-
1470
-
1471
-    /**
1472
-     * Gets deleted
1473
-     *
1474
-     * @return boolean
1475
-     * @throws EE_Error
1476
-     * @throws ReflectionException
1477
-     */
1478
-    public function deleted()
1479
-    {
1480
-        return $this->get('TKT_deleted');
1481
-    }
1482
-
1483
-
1484
-    /**
1485
-     * Sets deleted
1486
-     *
1487
-     * @param boolean $deleted
1488
-     * @return void
1489
-     * @throws EE_Error
1490
-     * @throws ReflectionException
1491
-     */
1492
-    public function set_deleted($deleted)
1493
-    {
1494
-        $this->set('TKT_deleted', $deleted);
1495
-    }
1496
-
1497
-
1498
-    /**
1499
-     * Gets parent
1500
-     *
1501
-     * @return int
1502
-     * @throws EE_Error
1503
-     * @throws ReflectionException
1504
-     */
1505
-    public function parent_ID()
1506
-    {
1507
-        return $this->get('TKT_parent');
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Sets parent
1513
-     *
1514
-     * @param int $parent
1515
-     * @return void
1516
-     * @throws EE_Error
1517
-     * @throws ReflectionException
1518
-     */
1519
-    public function set_parent_ID($parent)
1520
-    {
1521
-        $this->set('TKT_parent', $parent);
1522
-    }
1523
-
1524
-
1525
-    /**
1526
-     * @return boolean
1527
-     * @throws EE_Error
1528
-     * @throws InvalidArgumentException
1529
-     * @throws InvalidDataTypeException
1530
-     * @throws InvalidInterfaceException
1531
-     * @throws ReflectionException
1532
-     */
1533
-    public function reverse_calculate()
1534
-    {
1535
-        return $this->get('TKT_reverse_calculate');
1536
-    }
1537
-
1538
-
1539
-    /**
1540
-     * @param boolean $reverse_calculate
1541
-     * @throws EE_Error
1542
-     * @throws InvalidArgumentException
1543
-     * @throws InvalidDataTypeException
1544
-     * @throws InvalidInterfaceException
1545
-     * @throws ReflectionException
1546
-     */
1547
-    public function set_reverse_calculate($reverse_calculate)
1548
-    {
1549
-        $this->set('TKT_reverse_calculate', $reverse_calculate);
1550
-    }
1551
-
1552
-
1553
-    /**
1554
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1555
-     *
1556
-     * @return string
1557
-     * @throws EE_Error
1558
-     * @throws ReflectionException
1559
-     */
1560
-    public function name_and_info()
1561
-    {
1562
-        $times = [];
1563
-        foreach ($this->datetimes() as $datetime) {
1564
-            $times[] = $datetime->start_date_and_time();
1565
-        }
1566
-        /* translators: %1$s ticket name, %2$s start datetimes separated by comma, %3$s ticket price */
1567
-        return sprintf(
1568
-            esc_html__('%1$s @ %2$s for %3$s', 'event_espresso'),
1569
-            $this->name(),
1570
-            implode(', ', $times),
1571
-            $this->pretty_price()
1572
-        );
1573
-    }
1574
-
1575
-
1576
-    /**
1577
-     * Gets name
1578
-     *
1579
-     * @return string
1580
-     * @throws EE_Error
1581
-     * @throws ReflectionException
1582
-     */
1583
-    public function name()
1584
-    {
1585
-        return $this->get('TKT_name');
1586
-    }
1587
-
1588
-
1589
-    /**
1590
-     * Gets price
1591
-     *
1592
-     * @return float
1593
-     * @throws EE_Error
1594
-     * @throws ReflectionException
1595
-     */
1596
-    public function price()
1597
-    {
1598
-        return $this->get('TKT_price');
1599
-    }
1600
-
1601
-
1602
-    /**
1603
-     * Gets all the registrations for this ticket
1604
-     *
1605
-     * @param array $query_params
1606
-     * @return EE_Registration[]|EE_Base_Class[]
1607
-     * @throws EE_Error
1608
-     * @throws ReflectionException
1609
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1610
-     */
1611
-    public function registrations($query_params = [])
1612
-    {
1613
-        return $this->get_many_related('Registration', $query_params);
1614
-    }
1615
-
1616
-
1617
-    /**
1618
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1619
-     *
1620
-     * @return int
1621
-     * @throws EE_Error
1622
-     * @throws ReflectionException
1623
-     */
1624
-    public function update_tickets_sold()
1625
-    {
1626
-        $count_regs_for_this_ticket = $this->count_registrations(
1627
-            [
1628
-                [
1629
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1630
-                    'REG_deleted' => 0,
1631
-                ],
1632
-            ]
1633
-        );
1634
-        $this->set_sold($count_regs_for_this_ticket);
1635
-        $this->save();
1636
-        return $count_regs_for_this_ticket;
1637
-    }
1638
-
1639
-
1640
-    /**
1641
-     * Counts the registrations for this ticket
1642
-     *
1643
-     * @param array $query_params
1644
-     * @return int
1645
-     * @throws EE_Error
1646
-     * @throws ReflectionException
1647
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1648
-     */
1649
-    public function count_registrations($query_params = [])
1650
-    {
1651
-        return $this->count_related('Registration', $query_params);
1652
-    }
1653
-
1654
-
1655
-    /**
1656
-     * Implementation for EEI_Has_Icon interface method.
1657
-     *
1658
-     * @return string
1659
-     * @see EEI_Visual_Representation for comments
1660
-     */
1661
-    public function get_icon()
1662
-    {
1663
-        return '<span class="dashicons dashicons-tickets-alt"></span>';
1664
-    }
1665
-
1666
-
1667
-    /**
1668
-     * Implementation of the EEI_Event_Relation interface method
1669
-     *
1670
-     * @return EE_Event
1671
-     * @throws EE_Error
1672
-     * @throws UnexpectedEntityException
1673
-     * @throws ReflectionException
1674
-     * @see EEI_Event_Relation for comments
1675
-     */
1676
-    public function get_related_event()
1677
-    {
1678
-        // get one datetime to use for getting the event
1679
-        $datetime = $this->first_datetime();
1680
-        if (! $datetime instanceof EE_Datetime) {
1681
-            throw new UnexpectedEntityException(
1682
-                $datetime,
1683
-                'EE_Datetime',
1684
-                sprintf(
1685
-                    esc_html__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1686
-                    $this->name()
1687
-                )
1688
-            );
1689
-        }
1690
-        $event = $datetime->event();
1691
-        if (! $event instanceof EE_Event) {
1692
-            throw new UnexpectedEntityException(
1693
-                $event,
1694
-                'EE_Event',
1695
-                sprintf(
1696
-                    esc_html__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1697
-                    $this->name()
1698
-                )
1699
-            );
1700
-        }
1701
-        return $event;
1702
-    }
1703
-
1704
-
1705
-    /**
1706
-     * Implementation of the EEI_Event_Relation interface method
1707
-     *
1708
-     * @return string
1709
-     * @throws UnexpectedEntityException
1710
-     * @throws EE_Error
1711
-     * @throws ReflectionException
1712
-     * @see EEI_Event_Relation for comments
1713
-     */
1714
-    public function get_event_name()
1715
-    {
1716
-        $event = $this->get_related_event();
1717
-        return $event instanceof EE_Event ? $event->name() : '';
1718
-    }
1719
-
1720
-
1721
-    /**
1722
-     * Implementation of the EEI_Event_Relation interface method
1723
-     *
1724
-     * @return int
1725
-     * @throws UnexpectedEntityException
1726
-     * @throws EE_Error
1727
-     * @throws ReflectionException
1728
-     * @see EEI_Event_Relation for comments
1729
-     */
1730
-    public function get_event_ID()
1731
-    {
1732
-        $event = $this->get_related_event();
1733
-        return $event instanceof EE_Event ? $event->ID() : 0;
1734
-    }
1735
-
1736
-
1737
-    /**
1738
-     * This simply returns whether a ticket can be permanently deleted or not.
1739
-     * The criteria for determining this is whether the ticket has any related registrations.
1740
-     * If there are none then it can be permanently deleted.
1741
-     *
1742
-     * @return bool
1743
-     * @throws EE_Error
1744
-     * @throws ReflectionException
1745
-     */
1746
-    public function is_permanently_deleteable()
1747
-    {
1748
-        return $this->count_registrations() === 0;
1749
-    }
1750
-
1751
-
1752
-    /**
1753
-     * @return int
1754
-     * @throws EE_Error
1755
-     * @throws ReflectionException
1756
-     * @since   5.0.0.p
1757
-     */
1758
-    public function visibility(): int
1759
-    {
1760
-        return $this->get('TKT_visibility');
1761
-    }
1762
-
1763
-
1764
-    /**
1765
-     * @return bool
1766
-     * @throws EE_Error
1767
-     * @throws ReflectionException
1768
-     * @since   5.0.0.p
1769
-     */
1770
-    public function isHidden(): bool
1771
-    {
1772
-        return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1773
-    }
1774
-
1775
-
1776
-    /**
1777
-     * @return bool
1778
-     * @throws EE_Error
1779
-     * @throws ReflectionException
1780
-     * @since   5.0.0.p
1781
-     */
1782
-    public function isNotHidden(): bool
1783
-    {
1784
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1785
-    }
1786
-
1787
-
1788
-    /**
1789
-     * @return bool
1790
-     * @throws EE_Error
1791
-     * @throws ReflectionException
1792
-     * @since   5.0.0.p
1793
-     */
1794
-    public function isPublicOnly(): bool
1795
-    {
1796
-        return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1797
-    }
1798
-
1799
-
1800
-    /**
1801
-     * @return bool
1802
-     * @throws EE_Error
1803
-     * @throws ReflectionException
1804
-     * @since   5.0.0.p
1805
-     */
1806
-    public function isMembersOnly(): bool
1807
-    {
1808
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1809
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1810
-    }
1811
-
1812
-
1813
-    /**
1814
-     * @return bool
1815
-     * @throws EE_Error
1816
-     * @throws ReflectionException
1817
-     * @since   5.0.0.p
1818
-     */
1819
-    public function isAdminsOnly(): bool
1820
-    {
1821
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1822
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1823
-    }
1824
-
1825
-
1826
-    /**
1827
-     * @return bool
1828
-     * @throws EE_Error
1829
-     * @throws ReflectionException
1830
-     * @since   5.0.0.p
1831
-     */
1832
-    public function isAdminUiOnly(): bool
1833
-    {
1834
-        return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1835
-               && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1836
-    }
1837
-
1838
-
1839
-    /**
1840
-     * @param int $visibility
1841
-     * @throws EE_Error
1842
-     * @throws ReflectionException
1843
-     * @since   5.0.0.p
1844
-     */
1845
-    public function set_visibility(int $visibility)
1846
-    {
1847
-        $ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1848
-        $ticket_visibility         = -1;
1849
-        foreach ($ticket_visibility_options as $ticket_visibility_option) {
1850
-            if ($visibility === $ticket_visibility_option) {
1851
-                $ticket_visibility = $visibility;
1852
-            }
1853
-        }
1854
-        if ($ticket_visibility === -1) {
1855
-            throw new DomainException(
1856
-                sprintf(
1857
-                    esc_html__(
1858
-                        'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1859
-                        'event_espresso'
1860
-                    ),
1861
-                    $visibility,
1862
-                    '<br />',
1863
-                    var_export($ticket_visibility_options, true)
1864
-                )
1865
-            );
1866
-        }
1867
-        $this->set('TKT_visibility', $ticket_visibility);
1868
-    }
1869
-
1870
-
1871
-    /**
1872
-     * @param EE_Base_Class|int|string $otherObjectModelObjectOrID
1873
-     * @param string                   $relationName
1874
-     * @param array                    $extra_join_model_fields_n_values
1875
-     * @param string|null              $cache_id
1876
-     * @return EE_Base_Class
1877
-     * @throws EE_Error
1878
-     * @throws ReflectionException
1879
-     * @since   5.0.0.p
1880
-     */
1881
-    public function _add_relation_to(
1882
-        $otherObjectModelObjectOrID,
1883
-        $relationName,
1884
-        $extra_join_model_fields_n_values = [],
1885
-        $cache_id = null
1886
-    ) {
1887
-        if ($relationName === 'Datetime' && ! $this->hasRelation($otherObjectModelObjectOrID, $relationName)) {
1888
-            /** @var EE_Datetime $datetime */
1889
-            $datetime = EEM_Datetime::instance()->ensure_is_obj($otherObjectModelObjectOrID);
1890
-            $datetime->increaseSold($this->sold(), false);
1891
-            $datetime->increaseReserved($this->reserved());
1892
-            $datetime->save();
1893
-            $otherObjectModelObjectOrID = $datetime;
1894
-        }
1895
-        return parent::_add_relation_to(
1896
-            $otherObjectModelObjectOrID,
1897
-            $relationName,
1898
-            $extra_join_model_fields_n_values,
1899
-            $cache_id
1900
-        );
1901
-    }
1902
-
1903
-
1904
-    /**
1905
-     * @param EE_Base_Class|int|string $otherObjectModelObjectOrID
1906
-     * @param string                   $relationName
1907
-     * @param array                    $where_query
1908
-     * @return bool|EE_Base_Class|null
1909
-     * @throws EE_Error
1910
-     * @throws ReflectionException
1911
-     * @since   5.0.0.p
1912
-     */
1913
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
1914
-    {
1915
-        // if we're adding a new relation to a datetime
1916
-        if ($relationName === 'Datetime' && $this->hasRelation($otherObjectModelObjectOrID, $relationName)) {
1917
-            /** @var EE_Datetime $datetime */
1918
-            $datetime = EEM_Datetime::instance()->ensure_is_obj($otherObjectModelObjectOrID);
1919
-            $datetime->decreaseSold($this->sold());
1920
-            $datetime->decreaseReserved($this->reserved());
1921
-            $datetime->save();
1922
-            $otherObjectModelObjectOrID = $datetime;
1923
-        }
1924
-        return parent::_remove_relation_to(
1925
-            $otherObjectModelObjectOrID,
1926
-            $relationName,
1927
-            $where_query
1928
-        );
1929
-    }
1930
-
1931
-
1932
-    /**
1933
-     * Removes ALL the related things for the $relationName.
1934
-     *
1935
-     * @param string $relationName
1936
-     * @param array  $where_query_params
1937
-     * @return EE_Base_Class
1938
-     * @throws ReflectionException
1939
-     * @throws InvalidArgumentException
1940
-     * @throws InvalidInterfaceException
1941
-     * @throws InvalidDataTypeException
1942
-     * @throws EE_Error
1943
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1944
-     */
1945
-    public function _remove_relations($relationName, $where_query_params = [])
1946
-    {
1947
-        if ($relationName === 'Datetime') {
1948
-            $datetimes = $this->datetimes();
1949
-            foreach ($datetimes as $datetime) {
1950
-                $datetime->decreaseSold($this->sold());
1951
-                $datetime->decreaseReserved($this->reserved());
1952
-                $datetime->save();
1953
-            }
1954
-        }
1955
-        return parent::_remove_relations($relationName, $where_query_params);
1956
-    }
1957
-
1958
-
1959
-    /*******************************************************************
17
+	/**
18
+	 * TicKet Archived:
19
+	 * constant used by ticket_status() to indicate that a ticket is archived
20
+	 * and no longer available for purchase
21
+	 */
22
+	const archived = 'TKA';
23
+
24
+	/**
25
+	 * TicKet Expired:
26
+	 * constant used by ticket_status() to indicate that a ticket is expired
27
+	 * and no longer available for purchase
28
+	 */
29
+	const expired = 'TKE';
30
+
31
+	/**
32
+	 * TicKet On sale:
33
+	 * constant used by ticket_status() to indicate that a ticket is On Sale
34
+	 * and IS available for purchase
35
+	 */
36
+	const onsale = 'TKO';
37
+
38
+	/**
39
+	 * TicKet Pending:
40
+	 * constant used by ticket_status() to indicate that a ticket is pending
41
+	 * and is NOT YET available for purchase
42
+	 */
43
+	const pending = 'TKP';
44
+
45
+	/**
46
+	 * TicKet Sold out:
47
+	 * constant used by ticket_status() to indicate that a ticket is sold out
48
+	 * and no longer available for purchases
49
+	 */
50
+	const sold_out = 'TKS';
51
+
52
+	/**
53
+	 * extra meta key for tracking ticket reservations
54
+	 *
55
+	 * @type string
56
+	 */
57
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
+
59
+	/**
60
+	 * override of parent property
61
+	 *
62
+	 * @var EEM_Ticket
63
+	 */
64
+	protected $_model;
65
+
66
+	/**
67
+	 * cached result from method of the same name
68
+	 *
69
+	 * @var float $_ticket_total_with_taxes
70
+	 */
71
+	private $_ticket_total_with_taxes;
72
+
73
+	/**
74
+	 * @var TicketPriceModifiers
75
+	 */
76
+	protected $ticket_price_modifiers;
77
+
78
+
79
+	/**
80
+	 * @param array  $props_n_values          incoming values
81
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
82
+	 *                                        used.)
83
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
84
+	 *                                        date_format and the second value is the time format
85
+	 * @return EE_Ticket
86
+	 * @throws EE_Error
87
+	 * @throws ReflectionException
88
+	 */
89
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
90
+	{
91
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
92
+		return $has_object ?: new self($props_n_values, false, $timezone, $date_formats);
93
+	}
94
+
95
+
96
+	/**
97
+	 * @param array  $props_n_values  incoming values from the database
98
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
99
+	 *                                the website will be used.
100
+	 * @return EE_Ticket
101
+	 * @throws EE_Error
102
+	 * @throws ReflectionException
103
+	 */
104
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
105
+	{
106
+		return new self($props_n_values, true, $timezone);
107
+	}
108
+
109
+
110
+	/**
111
+	 * @param array  $fieldValues
112
+	 * @param false  $bydb
113
+	 * @param string $timezone
114
+	 * @param array  $date_formats
115
+	 * @throws EE_Error
116
+	 * @throws ReflectionException
117
+	 */
118
+	public function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
119
+	{
120
+		parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
121
+		$this->ticket_price_modifiers = new TicketPriceModifiers($this);
122
+	}
123
+
124
+
125
+	/**
126
+	 * @return bool
127
+	 * @throws EE_Error
128
+	 * @throws ReflectionException
129
+	 */
130
+	public function parent()
131
+	{
132
+		return $this->get('TKT_parent');
133
+	}
134
+
135
+
136
+	/**
137
+	 * return if a ticket has quantities available for purchase
138
+	 *
139
+	 * @param int $DTT_ID the primary key for a particular datetime
140
+	 * @return boolean
141
+	 * @throws EE_Error
142
+	 * @throws ReflectionException
143
+	 */
144
+	public function available($DTT_ID = 0)
145
+	{
146
+		// are we checking availability for a particular datetime ?
147
+		if ($DTT_ID) {
148
+			// get that datetime object
149
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
150
+			// if  ticket sales for this datetime have exceeded the reg limit...
151
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
152
+				return false;
153
+			}
154
+		}
155
+		// datetime is still open for registration, but is this ticket sold out ?
156
+		return $this->qty() < 1 || $this->qty() > $this->sold();
157
+	}
158
+
159
+
160
+	/**
161
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
162
+	 *
163
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
164
+	 *                               relevant status const
165
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
166
+	 *                               further processing
167
+	 * @return mixed status int if the display string isn't requested
168
+	 * @throws EE_Error
169
+	 * @throws ReflectionException
170
+	 */
171
+	public function ticket_status($display = false, $remaining = null)
172
+	{
173
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
174
+		if (! $remaining) {
175
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
176
+		}
177
+		if ($this->get('TKT_deleted')) {
178
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
179
+		}
180
+		if ($this->is_expired()) {
181
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
182
+		}
183
+		if ($this->is_pending()) {
184
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
185
+		}
186
+		if ($this->is_on_sale()) {
187
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
188
+		}
189
+		return '';
190
+	}
191
+
192
+
193
+	/**
194
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
195
+	 * considering ALL the factors used for figuring that out.
196
+	 *
197
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
198
+	 * @return boolean         true = tickets remaining, false not.
199
+	 * @throws EE_Error
200
+	 * @throws ReflectionException
201
+	 */
202
+	public function is_remaining($DTT_ID = 0)
203
+	{
204
+		$num_remaining = $this->remaining($DTT_ID);
205
+		if ($num_remaining === 0) {
206
+			return false;
207
+		}
208
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
209
+			return false;
210
+		}
211
+		return true;
212
+	}
213
+
214
+
215
+	/**
216
+	 * return the total number of tickets available for purchase
217
+	 *
218
+	 * @param int $DTT_ID  the primary key for a particular datetime.
219
+	 *                     set to 0 for all related datetimes
220
+	 * @return int
221
+	 * @throws EE_Error
222
+	 * @throws ReflectionException
223
+	 */
224
+	public function remaining($DTT_ID = 0)
225
+	{
226
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
227
+	}
228
+
229
+
230
+	/**
231
+	 * Gets min
232
+	 *
233
+	 * @return int
234
+	 * @throws EE_Error
235
+	 * @throws ReflectionException
236
+	 */
237
+	public function min()
238
+	{
239
+		return $this->get('TKT_min');
240
+	}
241
+
242
+
243
+	/**
244
+	 * return if a ticket is no longer available cause its available dates have expired.
245
+	 *
246
+	 * @return boolean
247
+	 * @throws EE_Error
248
+	 * @throws ReflectionException
249
+	 */
250
+	public function is_expired()
251
+	{
252
+		return ($this->get_raw('TKT_end_date') < time());
253
+	}
254
+
255
+
256
+	/**
257
+	 * Return if a ticket is yet to go on sale or not
258
+	 *
259
+	 * @return boolean
260
+	 * @throws EE_Error
261
+	 * @throws ReflectionException
262
+	 */
263
+	public function is_pending()
264
+	{
265
+		return ($this->get_raw('TKT_start_date') >= time());
266
+	}
267
+
268
+
269
+	/**
270
+	 * Return if a ticket is on sale or not
271
+	 *
272
+	 * @return boolean
273
+	 * @throws EE_Error
274
+	 * @throws ReflectionException
275
+	 */
276
+	public function is_on_sale()
277
+	{
278
+		return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
279
+	}
280
+
281
+
282
+	/**
283
+	 * This returns the chronologically last datetime that this ticket is associated with
284
+	 *
285
+	 * @param string $date_format
286
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
287
+	 *                            the end date ie: Jan 01 "to" Dec 31
288
+	 * @return string
289
+	 * @throws EE_Error
290
+	 * @throws ReflectionException
291
+	 */
292
+	public function date_range($date_format = '', $conjunction = ' - ')
293
+	{
294
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
295
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
296
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
297
+			: '';
298
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
299
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
300
+			: '';
301
+
302
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
303
+	}
304
+
305
+
306
+	/**
307
+	 * This returns the chronologically first datetime that this ticket is associated with
308
+	 *
309
+	 * @return EE_Datetime
310
+	 * @throws EE_Error
311
+	 * @throws ReflectionException
312
+	 */
313
+	public function first_datetime()
314
+	{
315
+		$datetimes = $this->datetimes(['limit' => 1]);
316
+		return reset($datetimes);
317
+	}
318
+
319
+
320
+	/**
321
+	 * Gets all the datetimes this ticket can be used for attending.
322
+	 * Unless otherwise specified, orders datetimes by start date.
323
+	 *
324
+	 * @param array $query_params
325
+	 * @return EE_Datetime[]|EE_Base_Class[]
326
+	 * @throws EE_Error
327
+	 * @throws ReflectionException
328
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
329
+	 */
330
+	public function datetimes($query_params = [])
331
+	{
332
+		if (! isset($query_params['order_by'])) {
333
+			$query_params['order_by']['DTT_order'] = 'ASC';
334
+		}
335
+		return $this->get_many_related('Datetime', $query_params);
336
+	}
337
+
338
+
339
+	/**
340
+	 * This returns the chronologically last datetime that this ticket is associated with
341
+	 *
342
+	 * @return EE_Datetime
343
+	 * @throws EE_Error
344
+	 * @throws ReflectionException
345
+	 */
346
+	public function last_datetime()
347
+	{
348
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
349
+		return end($datetimes);
350
+	}
351
+
352
+
353
+	/**
354
+	 * This returns the total tickets sold depending on the given parameters.
355
+	 *
356
+	 * @param string $what    Can be one of two options: 'ticket', 'datetime'.
357
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
358
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
359
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
360
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
361
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
362
+	 * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
363
+	 * @return mixed (array|int)          how many tickets have sold
364
+	 * @throws EE_Error
365
+	 * @throws ReflectionException
366
+	 */
367
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
368
+	{
369
+		$total        = 0;
370
+		$tickets_sold = $this->_all_tickets_sold();
371
+		switch ($what) {
372
+			case 'ticket':
373
+				return $tickets_sold['ticket'];
374
+
375
+			case 'datetime':
376
+				if (empty($tickets_sold['datetime'])) {
377
+					return $total;
378
+				}
379
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
380
+					EE_Error::add_error(
381
+						esc_html__(
382
+							'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
383
+							'event_espresso'
384
+						),
385
+						__FILE__,
386
+						__FUNCTION__,
387
+						__LINE__
388
+					);
389
+					return $total;
390
+				}
391
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
392
+
393
+			default:
394
+				return $total;
395
+		}
396
+	}
397
+
398
+
399
+	/**
400
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
401
+	 *
402
+	 * @return EE_Ticket[]
403
+	 * @throws EE_Error
404
+	 * @throws ReflectionException
405
+	 */
406
+	protected function _all_tickets_sold()
407
+	{
408
+		$datetimes    = $this->get_many_related('Datetime');
409
+		$tickets_sold = [];
410
+		if (! empty($datetimes)) {
411
+			foreach ($datetimes as $datetime) {
412
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
413
+			}
414
+		}
415
+		// Tickets sold
416
+		$tickets_sold['ticket'] = $this->sold();
417
+		return $tickets_sold;
418
+	}
419
+
420
+
421
+	/**
422
+	 * This returns the base price object for the ticket.
423
+	 *
424
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
425
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
426
+	 * @throws EE_Error
427
+	 * @throws ReflectionException
428
+	 */
429
+	public function base_price(bool $return_array = false)
430
+	{
431
+		$base_price = $this->ticket_price_modifiers->getBasePrice();
432
+		if (! empty($base_price)) {
433
+			return $return_array ? $base_price : reset($base_price);
434
+		}
435
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
436
+		return $return_array
437
+			? $this->get_many_related('Price', [$_where])
438
+			: $this->get_first_related('Price', [$_where]);
439
+	}
440
+
441
+
442
+	/**
443
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
444
+	 *
445
+	 * @return EE_Price[]
446
+	 * @throws EE_Error
447
+	 * @throws ReflectionException
448
+	 */
449
+	public function price_modifiers(): array
450
+	{
451
+		$price_modifiers = $this->usesGlobalTaxes()
452
+			? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
453
+			: $this->ticket_price_modifiers->getAllModifiersForTicket();
454
+		if (! empty($price_modifiers)) {
455
+			return $price_modifiers;
456
+		}
457
+		return $this->prices(
458
+			[
459
+				[
460
+					'Price_Type.PBT_ID' => [
461
+						'NOT IN',
462
+						[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
463
+					],
464
+				],
465
+			]
466
+		);
467
+	}
468
+
469
+
470
+	/**
471
+	 * This returns ONLY the TAX price modifiers for the ticket
472
+	 *
473
+	 * @return EE_Price[]
474
+	 * @throws EE_Error
475
+	 * @throws ReflectionException
476
+	 */
477
+	public function tax_price_modifiers(): array
478
+	{
479
+		$tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
480
+		if (! empty($tax_price_modifiers)) {
481
+			return $tax_price_modifiers;
482
+		}
483
+		return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
484
+	}
485
+
486
+
487
+	/**
488
+	 * Gets all the prices that combine to form the final price of this ticket
489
+	 *
490
+	 * @param array $query_params
491
+	 * @return EE_Price[]|EE_Base_Class[]
492
+	 * @throws EE_Error
493
+	 * @throws ReflectionException
494
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
495
+	 */
496
+	public function prices(array $query_params = []): array
497
+	{
498
+		if (! isset($query_params['order_by'])) {
499
+			$query_params['order_by']['PRC_order'] = 'ASC';
500
+		}
501
+		return $this->get_many_related('Price', $query_params);
502
+	}
503
+
504
+
505
+	/**
506
+	 * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
507
+	 *
508
+	 * @param array $query_params
509
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
510
+	 * @throws EE_Error
511
+	 * @throws ReflectionException
512
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
513
+	 */
514
+	public function datetime_tickets($query_params = [])
515
+	{
516
+		return $this->get_many_related('Datetime_Ticket', $query_params);
517
+	}
518
+
519
+
520
+	/**
521
+	 * Gets all the datetimes from the db ordered by DTT_order
522
+	 *
523
+	 * @param boolean $show_expired
524
+	 * @param boolean $show_deleted
525
+	 * @return EE_Datetime[]
526
+	 * @throws EE_Error
527
+	 * @throws ReflectionException
528
+	 */
529
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
530
+	{
531
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
532
+			$this->ID(),
533
+			$show_expired,
534
+			$show_deleted
535
+		);
536
+	}
537
+
538
+
539
+	/**
540
+	 * Gets ID
541
+	 *
542
+	 * @return int
543
+	 * @throws EE_Error
544
+	 * @throws ReflectionException
545
+	 */
546
+	public function ID()
547
+	{
548
+		return (int) $this->get('TKT_ID');
549
+	}
550
+
551
+
552
+	/**
553
+	 * get the author of the ticket.
554
+	 *
555
+	 * @return int
556
+	 * @throws EE_Error
557
+	 * @throws ReflectionException
558
+	 * @since 4.5.0
559
+	 */
560
+	public function wp_user()
561
+	{
562
+		return $this->get('TKT_wp_user');
563
+	}
564
+
565
+
566
+	/**
567
+	 * Gets the template for the ticket
568
+	 *
569
+	 * @return EE_Ticket_Template|EE_Base_Class
570
+	 * @throws EE_Error
571
+	 * @throws ReflectionException
572
+	 */
573
+	public function template()
574
+	{
575
+		return $this->get_first_related('Ticket_Template');
576
+	}
577
+
578
+
579
+	/**
580
+	 * Simply returns an array of EE_Price objects that are taxes.
581
+	 *
582
+	 * @return EE_Price[]
583
+	 * @throws EE_Error
584
+	 * @throws ReflectionException
585
+	 */
586
+	public function get_ticket_taxes_for_admin(): array
587
+	{
588
+		return $this->usesGlobalTaxes() ? EE_Taxes::get_taxes_for_admin() : $this->tax_price_modifiers();
589
+	}
590
+
591
+
592
+	/**
593
+	 * alias of taxable() to better indicate that ticket uses the legacy method of applying default "global" taxes
594
+	 * as opposed to having tax price modifiers added directly to each ticket
595
+	 *
596
+	 * @return bool
597
+	 * @throws EE_Error
598
+	 * @throws ReflectionException
599
+	 * @since   5.0.0.p
600
+	 */
601
+	public function usesGlobalTaxes(): bool
602
+	{
603
+		return $this->taxable();
604
+	}
605
+
606
+
607
+	/**
608
+	 * @return float
609
+	 * @throws EE_Error
610
+	 * @throws ReflectionException
611
+	 */
612
+	public function ticket_price()
613
+	{
614
+		return $this->get('TKT_price');
615
+	}
616
+
617
+
618
+	/**
619
+	 * @return mixed
620
+	 * @throws EE_Error
621
+	 * @throws ReflectionException
622
+	 */
623
+	public function pretty_price()
624
+	{
625
+		return $this->get_pretty('TKT_price');
626
+	}
627
+
628
+
629
+	/**
630
+	 * @return bool
631
+	 * @throws EE_Error
632
+	 * @throws ReflectionException
633
+	 */
634
+	public function is_free()
635
+	{
636
+		return $this->get_ticket_total_with_taxes() === (float) 0;
637
+	}
638
+
639
+
640
+	/**
641
+	 * get_ticket_total_with_taxes
642
+	 *
643
+	 * @param bool $no_cache
644
+	 * @return float
645
+	 * @throws EE_Error
646
+	 * @throws ReflectionException
647
+	 */
648
+	public function get_ticket_total_with_taxes($no_cache = false)
649
+	{
650
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
651
+			$this->_ticket_total_with_taxes = $this->usesGlobalTaxes()
652
+				? $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin()
653
+				: $this->ticket_price();
654
+		}
655
+		return (float) $this->_ticket_total_with_taxes;
656
+	}
657
+
658
+
659
+	/**
660
+	 * @throws EE_Error
661
+	 * @throws ReflectionException
662
+	 */
663
+	public function ensure_TKT_Price_correct()
664
+	{
665
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
666
+		$this->save();
667
+	}
668
+
669
+
670
+	/**
671
+	 * @return float
672
+	 * @throws EE_Error
673
+	 * @throws ReflectionException
674
+	 */
675
+	public function get_ticket_subtotal()
676
+	{
677
+		return EE_Taxes::get_subtotal_for_admin($this);
678
+	}
679
+
680
+
681
+	/**
682
+	 * Returns the total taxes applied to this ticket
683
+	 *
684
+	 * @return float
685
+	 * @throws EE_Error
686
+	 * @throws ReflectionException
687
+	 */
688
+	public function get_ticket_taxes_total_for_admin()
689
+	{
690
+		return EE_Taxes::get_total_taxes_for_admin($this);
691
+	}
692
+
693
+
694
+	/**
695
+	 * Sets name
696
+	 *
697
+	 * @param string $name
698
+	 * @throws EE_Error
699
+	 * @throws ReflectionException
700
+	 */
701
+	public function set_name($name)
702
+	{
703
+		$this->set('TKT_name', $name);
704
+	}
705
+
706
+
707
+	/**
708
+	 * Gets description
709
+	 *
710
+	 * @return string
711
+	 * @throws EE_Error
712
+	 * @throws ReflectionException
713
+	 */
714
+	public function description()
715
+	{
716
+		return $this->get('TKT_description');
717
+	}
718
+
719
+
720
+	/**
721
+	 * Sets description
722
+	 *
723
+	 * @param string $description
724
+	 * @throws EE_Error
725
+	 * @throws ReflectionException
726
+	 */
727
+	public function set_description($description)
728
+	{
729
+		$this->set('TKT_description', $description);
730
+	}
731
+
732
+
733
+	/**
734
+	 * Gets start_date
735
+	 *
736
+	 * @param string|null $date_format
737
+	 * @param string|null $time_format
738
+	 * @return string
739
+	 * @throws EE_Error
740
+	 * @throws ReflectionException
741
+	 */
742
+	public function start_date(?string $date_format = '', ?string $time_format = ''): string
743
+	{
744
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
745
+	}
746
+
747
+
748
+	/**
749
+	 * Sets start_date
750
+	 *
751
+	 * @param string $start_date
752
+	 * @return void
753
+	 * @throws EE_Error
754
+	 * @throws ReflectionException
755
+	 */
756
+	public function set_start_date($start_date)
757
+	{
758
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
759
+	}
760
+
761
+
762
+	/**
763
+	 * Gets end_date
764
+	 *
765
+	 * @param string|null $date_format
766
+	 * @param string|null $time_format
767
+	 * @return string
768
+	 * @throws EE_Error
769
+	 * @throws ReflectionException
770
+	 */
771
+	public function end_date(?string $date_format = '', ?string $time_format = ''): string
772
+	{
773
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
774
+	}
775
+
776
+
777
+	/**
778
+	 * Sets end_date
779
+	 *
780
+	 * @param string $end_date
781
+	 * @return void
782
+	 * @throws EE_Error
783
+	 * @throws ReflectionException
784
+	 */
785
+	public function set_end_date($end_date)
786
+	{
787
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
788
+	}
789
+
790
+
791
+	/**
792
+	 * Sets sell until time
793
+	 *
794
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
795
+	 * @throws EE_Error
796
+	 * @throws ReflectionException
797
+	 * @since 4.5.0
798
+	 */
799
+	public function set_end_time($time)
800
+	{
801
+		$this->_set_time_for($time, 'TKT_end_date');
802
+	}
803
+
804
+
805
+	/**
806
+	 * Sets min
807
+	 *
808
+	 * @param int $min
809
+	 * @return void
810
+	 * @throws EE_Error
811
+	 * @throws ReflectionException
812
+	 */
813
+	public function set_min($min)
814
+	{
815
+		$this->set('TKT_min', $min);
816
+	}
817
+
818
+
819
+	/**
820
+	 * Gets max
821
+	 *
822
+	 * @return int
823
+	 * @throws EE_Error
824
+	 * @throws ReflectionException
825
+	 */
826
+	public function max()
827
+	{
828
+		return $this->get('TKT_max');
829
+	}
830
+
831
+
832
+	/**
833
+	 * Sets max
834
+	 *
835
+	 * @param int $max
836
+	 * @return void
837
+	 * @throws EE_Error
838
+	 * @throws ReflectionException
839
+	 */
840
+	public function set_max($max)
841
+	{
842
+		$this->set('TKT_max', $max);
843
+	}
844
+
845
+
846
+	/**
847
+	 * Sets price
848
+	 *
849
+	 * @param float $price
850
+	 * @return void
851
+	 * @throws EE_Error
852
+	 * @throws ReflectionException
853
+	 */
854
+	public function set_price($price)
855
+	{
856
+		$this->set('TKT_price', $price);
857
+	}
858
+
859
+
860
+	/**
861
+	 * Gets sold
862
+	 *
863
+	 * @return int
864
+	 * @throws EE_Error
865
+	 * @throws ReflectionException
866
+	 */
867
+	public function sold(): int
868
+	{
869
+		return (int) $this->get_raw('TKT_sold');
870
+	}
871
+
872
+
873
+	/**
874
+	 * Sets sold
875
+	 *
876
+	 * @param int $sold
877
+	 * @return void
878
+	 * @throws EE_Error
879
+	 * @throws ReflectionException
880
+	 */
881
+	public function set_sold($sold)
882
+	{
883
+		// sold can not go below zero
884
+		$sold = max(0, $sold);
885
+		$this->set('TKT_sold', $sold);
886
+	}
887
+
888
+
889
+	/**
890
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
891
+	 * associated datetimes.
892
+	 *
893
+	 * @param int $qty
894
+	 * @return boolean
895
+	 * @throws EE_Error
896
+	 * @throws InvalidArgumentException
897
+	 * @throws InvalidDataTypeException
898
+	 * @throws InvalidInterfaceException
899
+	 * @throws ReflectionException
900
+	 * @since 4.9.80.p
901
+	 */
902
+	public function increaseSold($qty = 1)
903
+	{
904
+		$qty = absint($qty);
905
+		// increment sold and decrement reserved datetime quantities simultaneously
906
+		// don't worry about failures, because they must have already had a spot reserved
907
+		$this->increaseSoldForDatetimes($qty);
908
+		// Increment and decrement ticket quantities simultaneously
909
+		$success = $this->adjustNumericFieldsInDb(
910
+			[
911
+				'TKT_reserved' => $qty * -1,
912
+				'TKT_sold'     => $qty,
913
+			]
914
+		);
915
+		do_action(
916
+			'AHEE__EE_Ticket__increase_sold',
917
+			$this,
918
+			$qty,
919
+			$this->sold(),
920
+			$success
921
+		);
922
+		return $success;
923
+	}
924
+
925
+
926
+	/**
927
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
928
+	 *
929
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
930
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
931
+	 * @param EE_Datetime[] $datetimes
932
+	 * @throws EE_Error
933
+	 * @throws InvalidArgumentException
934
+	 * @throws InvalidDataTypeException
935
+	 * @throws InvalidInterfaceException
936
+	 * @throws ReflectionException
937
+	 * @since 4.9.80.p
938
+	 */
939
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
940
+	{
941
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
942
+		foreach ($datetimes as $datetime) {
943
+			$datetime->increaseSold($qty);
944
+		}
945
+	}
946
+
947
+
948
+	/**
949
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
950
+	 * DB and then updates the model objects.
951
+	 * Does not affect the reserved counts.
952
+	 *
953
+	 * @param int $qty
954
+	 * @return boolean
955
+	 * @throws EE_Error
956
+	 * @throws InvalidArgumentException
957
+	 * @throws InvalidDataTypeException
958
+	 * @throws InvalidInterfaceException
959
+	 * @throws ReflectionException
960
+	 * @since 4.9.80.p
961
+	 */
962
+	public function decreaseSold($qty = 1)
963
+	{
964
+		$qty = absint($qty);
965
+		$this->decreaseSoldForDatetimes($qty);
966
+		$success = $this->adjustNumericFieldsInDb(
967
+			[
968
+				'TKT_sold' => $qty * -1,
969
+			]
970
+		);
971
+		do_action(
972
+			'AHEE__EE_Ticket__decrease_sold',
973
+			$this,
974
+			$qty,
975
+			$this->sold(),
976
+			$success
977
+		);
978
+		return $success;
979
+	}
980
+
981
+
982
+	/**
983
+	 * Decreases sold on related datetimes
984
+	 *
985
+	 * @param int           $qty
986
+	 * @param EE_Datetime[] $datetimes
987
+	 * @return void
988
+	 * @throws EE_Error
989
+	 * @throws InvalidArgumentException
990
+	 * @throws InvalidDataTypeException
991
+	 * @throws InvalidInterfaceException
992
+	 * @throws ReflectionException
993
+	 * @since 4.9.80.p
994
+	 */
995
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
996
+	{
997
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
998
+		if (is_array($datetimes)) {
999
+			foreach ($datetimes as $datetime) {
1000
+				if ($datetime instanceof EE_Datetime) {
1001
+					$datetime->decreaseSold($qty);
1002
+				}
1003
+			}
1004
+		}
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * Gets qty of reserved tickets
1010
+	 *
1011
+	 * @return int
1012
+	 * @throws EE_Error
1013
+	 * @throws ReflectionException
1014
+	 */
1015
+	public function reserved(): int
1016
+	{
1017
+		return (int) $this->get_raw('TKT_reserved');
1018
+	}
1019
+
1020
+
1021
+	/**
1022
+	 * Sets reserved
1023
+	 *
1024
+	 * @param int $reserved
1025
+	 * @return void
1026
+	 * @throws EE_Error
1027
+	 * @throws ReflectionException
1028
+	 */
1029
+	public function set_reserved($reserved)
1030
+	{
1031
+		// reserved can not go below zero
1032
+		$reserved = max(0, (int) $reserved);
1033
+		$this->set('TKT_reserved', $reserved);
1034
+	}
1035
+
1036
+
1037
+	/**
1038
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1039
+	 *
1040
+	 * @param int    $qty
1041
+	 * @param string $source
1042
+	 * @return bool whether we successfully reserved the ticket or not.
1043
+	 * @throws EE_Error
1044
+	 * @throws InvalidArgumentException
1045
+	 * @throws ReflectionException
1046
+	 * @throws InvalidDataTypeException
1047
+	 * @throws InvalidInterfaceException
1048
+	 * @since 4.9.80.p
1049
+	 */
1050
+	public function increaseReserved($qty = 1, $source = 'unknown')
1051
+	{
1052
+		$qty = absint($qty);
1053
+		do_action(
1054
+			'AHEE__EE_Ticket__increase_reserved__begin',
1055
+			$this,
1056
+			$qty,
1057
+			$source
1058
+		);
1059
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1060
+		$success                         = false;
1061
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1062
+		if ($datetimes_adjusted_successfully) {
1063
+			$success = $this->incrementFieldConditionallyInDb(
1064
+				'TKT_reserved',
1065
+				'TKT_sold',
1066
+				'TKT_qty',
1067
+				$qty
1068
+			);
1069
+			if (! $success) {
1070
+				// The datetimes were successfully bumped, but not the
1071
+				// ticket. So we need to manually rollback the datetimes.
1072
+				$this->decreaseReservedForDatetimes($qty);
1073
+			}
1074
+		}
1075
+		do_action(
1076
+			'AHEE__EE_Ticket__increase_reserved',
1077
+			$this,
1078
+			$qty,
1079
+			$this->reserved(),
1080
+			$success
1081
+		);
1082
+		return $success;
1083
+	}
1084
+
1085
+
1086
+	/**
1087
+	 * Increases reserved counts on related datetimes
1088
+	 *
1089
+	 * @param int           $qty
1090
+	 * @param EE_Datetime[] $datetimes
1091
+	 * @return boolean indicating success
1092
+	 * @throws EE_Error
1093
+	 * @throws InvalidArgumentException
1094
+	 * @throws InvalidDataTypeException
1095
+	 * @throws InvalidInterfaceException
1096
+	 * @throws ReflectionException
1097
+	 * @since 4.9.80.p
1098
+	 */
1099
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1100
+	{
1101
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1102
+		$datetimes_updated = [];
1103
+		$limit_exceeded    = false;
1104
+		if (is_array($datetimes)) {
1105
+			foreach ($datetimes as $datetime) {
1106
+				if ($datetime instanceof EE_Datetime) {
1107
+					if ($datetime->increaseReserved($qty)) {
1108
+						$datetimes_updated[] = $datetime;
1109
+					} else {
1110
+						$limit_exceeded = true;
1111
+						break;
1112
+					}
1113
+				}
1114
+			}
1115
+			// If somewhere along the way we detected a datetime whose
1116
+			// limit was exceeded, do a manual rollback.
1117
+			if ($limit_exceeded) {
1118
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1119
+				return false;
1120
+			}
1121
+		}
1122
+		return true;
1123
+	}
1124
+
1125
+
1126
+	/**
1127
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1128
+	 *
1129
+	 * @param int    $qty
1130
+	 * @param bool   $adjust_datetimes
1131
+	 * @param string $source
1132
+	 * @return boolean
1133
+	 * @throws EE_Error
1134
+	 * @throws InvalidArgumentException
1135
+	 * @throws ReflectionException
1136
+	 * @throws InvalidDataTypeException
1137
+	 * @throws InvalidInterfaceException
1138
+	 * @since 4.9.80.p
1139
+	 */
1140
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1141
+	{
1142
+		$qty = absint($qty);
1143
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1144
+		if ($adjust_datetimes) {
1145
+			$this->decreaseReservedForDatetimes($qty);
1146
+		}
1147
+		$success = $this->adjustNumericFieldsInDb(
1148
+			[
1149
+				'TKT_reserved' => $qty * -1,
1150
+			]
1151
+		);
1152
+		do_action(
1153
+			'AHEE__EE_Ticket__decrease_reserved',
1154
+			$this,
1155
+			$qty,
1156
+			$this->reserved(),
1157
+			$success
1158
+		);
1159
+		return $success;
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * Decreases the reserved count on the specified datetimes.
1165
+	 *
1166
+	 * @param int           $qty
1167
+	 * @param EE_Datetime[] $datetimes
1168
+	 * @throws EE_Error
1169
+	 * @throws InvalidArgumentException
1170
+	 * @throws ReflectionException
1171
+	 * @throws InvalidDataTypeException
1172
+	 * @throws InvalidInterfaceException
1173
+	 * @since 4.9.80.p
1174
+	 */
1175
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1176
+	{
1177
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1178
+		foreach ($datetimes as $datetime) {
1179
+			if ($datetime instanceof EE_Datetime) {
1180
+				$datetime->decreaseReserved($qty);
1181
+			}
1182
+		}
1183
+	}
1184
+
1185
+
1186
+	/**
1187
+	 * Gets ticket quantity
1188
+	 *
1189
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1190
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1191
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1192
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1193
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1194
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1195
+	 * @return int
1196
+	 * @throws EE_Error
1197
+	 * @throws ReflectionException
1198
+	 */
1199
+	public function qty($context = '')
1200
+	{
1201
+		switch ($context) {
1202
+			case 'reg_limit':
1203
+				return $this->real_quantity_on_ticket();
1204
+			case 'saleable':
1205
+				return $this->real_quantity_on_ticket('saleable');
1206
+			default:
1207
+				return $this->get_raw('TKT_qty');
1208
+		}
1209
+	}
1210
+
1211
+
1212
+	/**
1213
+	 * Gets ticket quantity
1214
+	 *
1215
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1216
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1217
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1218
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1219
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1220
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1221
+	 *                            set to 0 for all related datetimes
1222
+	 * @return int|float          int for finite quantity or float for INF
1223
+	 * @throws EE_Error
1224
+	 * @throws ReflectionException
1225
+	 */
1226
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1227
+	{
1228
+		$raw = $this->get_raw('TKT_qty');
1229
+		// return immediately if it's zero
1230
+		if ($raw === 0) {
1231
+			return $raw;
1232
+		}
1233
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1234
+		// ensure qty doesn't exceed raw value for THIS ticket
1235
+		$qty = min(EE_INF, $raw);
1236
+		// echo "\n . qty: " . $qty . '<br />';
1237
+		// calculate this ticket's total sales and reservations
1238
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1239
+		// echo "\n . sold: " . $this->sold() . '<br />';
1240
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1241
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1242
+		// first we need to calculate the maximum number of tickets available for the datetime
1243
+		// do we want data for one datetime or all of them ?
1244
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1245
+		$datetimes    = $this->datetimes($query_params);
1246
+		if (is_array($datetimes) && ! empty($datetimes)) {
1247
+			foreach ($datetimes as $datetime) {
1248
+				if ($datetime instanceof EE_Datetime) {
1249
+					$datetime->refresh_from_db();
1250
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1251
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1252
+					// initialize with no restrictions for each datetime
1253
+					// but adjust datetime qty based on datetime reg limit
1254
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1255
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1256
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1257
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1258
+					// and reservations for this datetime, that do NOT include sales and reservations
1259
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1260
+					if ($context === 'saleable') {
1261
+						$datetime_qty = max(
1262
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1263
+							0
1264
+						);
1265
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1266
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1267
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1268
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1269
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1270
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1271
+					}
1272
+					$qty = min($datetime_qty, $qty);
1273
+					// echo "\n . . qty: " . $qty . '<br />';
1274
+				}
1275
+			}
1276
+		}
1277
+		// NOW that we know the  maximum number of tickets available for the datetime
1278
+		// we can finally factor in the details for this specific ticket
1279
+		if ($qty > 0 && $context === 'saleable') {
1280
+			// and subtract the sales for THIS ticket
1281
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1282
+			// echo "\n . qty: " . $qty . '<br />';
1283
+		}
1284
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1285
+		return $qty;
1286
+	}
1287
+
1288
+
1289
+	/**
1290
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1291
+	 *
1292
+	 * @param int $qty
1293
+	 * @return void
1294
+	 * @throws EE_Error
1295
+	 * @throws ReflectionException
1296
+	 */
1297
+	public function set_qty($qty)
1298
+	{
1299
+		$datetimes = $this->datetimes();
1300
+		foreach ($datetimes as $datetime) {
1301
+			if ($datetime instanceof EE_Datetime) {
1302
+				$qty = min($qty, $datetime->reg_limit());
1303
+			}
1304
+		}
1305
+		$this->set('TKT_qty', $qty);
1306
+	}
1307
+
1308
+
1309
+	/**
1310
+	 * Gets uses
1311
+	 *
1312
+	 * @return int
1313
+	 * @throws EE_Error
1314
+	 * @throws ReflectionException
1315
+	 */
1316
+	public function uses()
1317
+	{
1318
+		return $this->get('TKT_uses');
1319
+	}
1320
+
1321
+
1322
+	/**
1323
+	 * Sets uses
1324
+	 *
1325
+	 * @param int $uses
1326
+	 * @return void
1327
+	 * @throws EE_Error
1328
+	 * @throws ReflectionException
1329
+	 */
1330
+	public function set_uses($uses)
1331
+	{
1332
+		$this->set('TKT_uses', $uses);
1333
+	}
1334
+
1335
+
1336
+	/**
1337
+	 * returns whether ticket is required or not.
1338
+	 *
1339
+	 * @return boolean
1340
+	 * @throws EE_Error
1341
+	 * @throws ReflectionException
1342
+	 */
1343
+	public function required()
1344
+	{
1345
+		return $this->get('TKT_required');
1346
+	}
1347
+
1348
+
1349
+	/**
1350
+	 * sets the TKT_required property
1351
+	 *
1352
+	 * @param boolean $required
1353
+	 * @return void
1354
+	 * @throws EE_Error
1355
+	 * @throws ReflectionException
1356
+	 */
1357
+	public function set_required($required)
1358
+	{
1359
+		$this->set('TKT_required', $required);
1360
+	}
1361
+
1362
+
1363
+	/**
1364
+	 * Gets taxable
1365
+	 *
1366
+	 * @return boolean
1367
+	 * @throws EE_Error
1368
+	 * @throws ReflectionException
1369
+	 */
1370
+	public function taxable()
1371
+	{
1372
+		return $this->get('TKT_taxable');
1373
+	}
1374
+
1375
+
1376
+	/**
1377
+	 * Sets taxable
1378
+	 *
1379
+	 * @param boolean $taxable
1380
+	 * @return void
1381
+	 * @throws EE_Error
1382
+	 * @throws ReflectionException
1383
+	 */
1384
+	public function set_taxable($taxable)
1385
+	{
1386
+		$this->set('TKT_taxable', $taxable);
1387
+	}
1388
+
1389
+
1390
+	/**
1391
+	 * Gets is_default
1392
+	 *
1393
+	 * @return boolean
1394
+	 * @throws EE_Error
1395
+	 * @throws ReflectionException
1396
+	 */
1397
+	public function is_default()
1398
+	{
1399
+		return $this->get('TKT_is_default');
1400
+	}
1401
+
1402
+
1403
+	/**
1404
+	 * Sets is_default
1405
+	 *
1406
+	 * @param boolean $is_default
1407
+	 * @return void
1408
+	 * @throws EE_Error
1409
+	 * @throws ReflectionException
1410
+	 */
1411
+	public function set_is_default($is_default)
1412
+	{
1413
+		$this->set('TKT_is_default', $is_default);
1414
+	}
1415
+
1416
+
1417
+	/**
1418
+	 * Gets order
1419
+	 *
1420
+	 * @return int
1421
+	 * @throws EE_Error
1422
+	 * @throws ReflectionException
1423
+	 */
1424
+	public function order()
1425
+	{
1426
+		return $this->get('TKT_order');
1427
+	}
1428
+
1429
+
1430
+	/**
1431
+	 * Sets order
1432
+	 *
1433
+	 * @param int $order
1434
+	 * @return void
1435
+	 * @throws EE_Error
1436
+	 * @throws ReflectionException
1437
+	 */
1438
+	public function set_order($order)
1439
+	{
1440
+		$this->set('TKT_order', $order);
1441
+	}
1442
+
1443
+
1444
+	/**
1445
+	 * Gets row
1446
+	 *
1447
+	 * @return int
1448
+	 * @throws EE_Error
1449
+	 * @throws ReflectionException
1450
+	 */
1451
+	public function row()
1452
+	{
1453
+		return $this->get('TKT_row');
1454
+	}
1455
+
1456
+
1457
+	/**
1458
+	 * Sets row
1459
+	 *
1460
+	 * @param int $row
1461
+	 * @return void
1462
+	 * @throws EE_Error
1463
+	 * @throws ReflectionException
1464
+	 */
1465
+	public function set_row($row)
1466
+	{
1467
+		$this->set('TKT_row', $row);
1468
+	}
1469
+
1470
+
1471
+	/**
1472
+	 * Gets deleted
1473
+	 *
1474
+	 * @return boolean
1475
+	 * @throws EE_Error
1476
+	 * @throws ReflectionException
1477
+	 */
1478
+	public function deleted()
1479
+	{
1480
+		return $this->get('TKT_deleted');
1481
+	}
1482
+
1483
+
1484
+	/**
1485
+	 * Sets deleted
1486
+	 *
1487
+	 * @param boolean $deleted
1488
+	 * @return void
1489
+	 * @throws EE_Error
1490
+	 * @throws ReflectionException
1491
+	 */
1492
+	public function set_deleted($deleted)
1493
+	{
1494
+		$this->set('TKT_deleted', $deleted);
1495
+	}
1496
+
1497
+
1498
+	/**
1499
+	 * Gets parent
1500
+	 *
1501
+	 * @return int
1502
+	 * @throws EE_Error
1503
+	 * @throws ReflectionException
1504
+	 */
1505
+	public function parent_ID()
1506
+	{
1507
+		return $this->get('TKT_parent');
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Sets parent
1513
+	 *
1514
+	 * @param int $parent
1515
+	 * @return void
1516
+	 * @throws EE_Error
1517
+	 * @throws ReflectionException
1518
+	 */
1519
+	public function set_parent_ID($parent)
1520
+	{
1521
+		$this->set('TKT_parent', $parent);
1522
+	}
1523
+
1524
+
1525
+	/**
1526
+	 * @return boolean
1527
+	 * @throws EE_Error
1528
+	 * @throws InvalidArgumentException
1529
+	 * @throws InvalidDataTypeException
1530
+	 * @throws InvalidInterfaceException
1531
+	 * @throws ReflectionException
1532
+	 */
1533
+	public function reverse_calculate()
1534
+	{
1535
+		return $this->get('TKT_reverse_calculate');
1536
+	}
1537
+
1538
+
1539
+	/**
1540
+	 * @param boolean $reverse_calculate
1541
+	 * @throws EE_Error
1542
+	 * @throws InvalidArgumentException
1543
+	 * @throws InvalidDataTypeException
1544
+	 * @throws InvalidInterfaceException
1545
+	 * @throws ReflectionException
1546
+	 */
1547
+	public function set_reverse_calculate($reverse_calculate)
1548
+	{
1549
+		$this->set('TKT_reverse_calculate', $reverse_calculate);
1550
+	}
1551
+
1552
+
1553
+	/**
1554
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1555
+	 *
1556
+	 * @return string
1557
+	 * @throws EE_Error
1558
+	 * @throws ReflectionException
1559
+	 */
1560
+	public function name_and_info()
1561
+	{
1562
+		$times = [];
1563
+		foreach ($this->datetimes() as $datetime) {
1564
+			$times[] = $datetime->start_date_and_time();
1565
+		}
1566
+		/* translators: %1$s ticket name, %2$s start datetimes separated by comma, %3$s ticket price */
1567
+		return sprintf(
1568
+			esc_html__('%1$s @ %2$s for %3$s', 'event_espresso'),
1569
+			$this->name(),
1570
+			implode(', ', $times),
1571
+			$this->pretty_price()
1572
+		);
1573
+	}
1574
+
1575
+
1576
+	/**
1577
+	 * Gets name
1578
+	 *
1579
+	 * @return string
1580
+	 * @throws EE_Error
1581
+	 * @throws ReflectionException
1582
+	 */
1583
+	public function name()
1584
+	{
1585
+		return $this->get('TKT_name');
1586
+	}
1587
+
1588
+
1589
+	/**
1590
+	 * Gets price
1591
+	 *
1592
+	 * @return float
1593
+	 * @throws EE_Error
1594
+	 * @throws ReflectionException
1595
+	 */
1596
+	public function price()
1597
+	{
1598
+		return $this->get('TKT_price');
1599
+	}
1600
+
1601
+
1602
+	/**
1603
+	 * Gets all the registrations for this ticket
1604
+	 *
1605
+	 * @param array $query_params
1606
+	 * @return EE_Registration[]|EE_Base_Class[]
1607
+	 * @throws EE_Error
1608
+	 * @throws ReflectionException
1609
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1610
+	 */
1611
+	public function registrations($query_params = [])
1612
+	{
1613
+		return $this->get_many_related('Registration', $query_params);
1614
+	}
1615
+
1616
+
1617
+	/**
1618
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1619
+	 *
1620
+	 * @return int
1621
+	 * @throws EE_Error
1622
+	 * @throws ReflectionException
1623
+	 */
1624
+	public function update_tickets_sold()
1625
+	{
1626
+		$count_regs_for_this_ticket = $this->count_registrations(
1627
+			[
1628
+				[
1629
+					'STS_ID'      => EEM_Registration::status_id_approved,
1630
+					'REG_deleted' => 0,
1631
+				],
1632
+			]
1633
+		);
1634
+		$this->set_sold($count_regs_for_this_ticket);
1635
+		$this->save();
1636
+		return $count_regs_for_this_ticket;
1637
+	}
1638
+
1639
+
1640
+	/**
1641
+	 * Counts the registrations for this ticket
1642
+	 *
1643
+	 * @param array $query_params
1644
+	 * @return int
1645
+	 * @throws EE_Error
1646
+	 * @throws ReflectionException
1647
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1648
+	 */
1649
+	public function count_registrations($query_params = [])
1650
+	{
1651
+		return $this->count_related('Registration', $query_params);
1652
+	}
1653
+
1654
+
1655
+	/**
1656
+	 * Implementation for EEI_Has_Icon interface method.
1657
+	 *
1658
+	 * @return string
1659
+	 * @see EEI_Visual_Representation for comments
1660
+	 */
1661
+	public function get_icon()
1662
+	{
1663
+		return '<span class="dashicons dashicons-tickets-alt"></span>';
1664
+	}
1665
+
1666
+
1667
+	/**
1668
+	 * Implementation of the EEI_Event_Relation interface method
1669
+	 *
1670
+	 * @return EE_Event
1671
+	 * @throws EE_Error
1672
+	 * @throws UnexpectedEntityException
1673
+	 * @throws ReflectionException
1674
+	 * @see EEI_Event_Relation for comments
1675
+	 */
1676
+	public function get_related_event()
1677
+	{
1678
+		// get one datetime to use for getting the event
1679
+		$datetime = $this->first_datetime();
1680
+		if (! $datetime instanceof EE_Datetime) {
1681
+			throw new UnexpectedEntityException(
1682
+				$datetime,
1683
+				'EE_Datetime',
1684
+				sprintf(
1685
+					esc_html__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1686
+					$this->name()
1687
+				)
1688
+			);
1689
+		}
1690
+		$event = $datetime->event();
1691
+		if (! $event instanceof EE_Event) {
1692
+			throw new UnexpectedEntityException(
1693
+				$event,
1694
+				'EE_Event',
1695
+				sprintf(
1696
+					esc_html__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1697
+					$this->name()
1698
+				)
1699
+			);
1700
+		}
1701
+		return $event;
1702
+	}
1703
+
1704
+
1705
+	/**
1706
+	 * Implementation of the EEI_Event_Relation interface method
1707
+	 *
1708
+	 * @return string
1709
+	 * @throws UnexpectedEntityException
1710
+	 * @throws EE_Error
1711
+	 * @throws ReflectionException
1712
+	 * @see EEI_Event_Relation for comments
1713
+	 */
1714
+	public function get_event_name()
1715
+	{
1716
+		$event = $this->get_related_event();
1717
+		return $event instanceof EE_Event ? $event->name() : '';
1718
+	}
1719
+
1720
+
1721
+	/**
1722
+	 * Implementation of the EEI_Event_Relation interface method
1723
+	 *
1724
+	 * @return int
1725
+	 * @throws UnexpectedEntityException
1726
+	 * @throws EE_Error
1727
+	 * @throws ReflectionException
1728
+	 * @see EEI_Event_Relation for comments
1729
+	 */
1730
+	public function get_event_ID()
1731
+	{
1732
+		$event = $this->get_related_event();
1733
+		return $event instanceof EE_Event ? $event->ID() : 0;
1734
+	}
1735
+
1736
+
1737
+	/**
1738
+	 * This simply returns whether a ticket can be permanently deleted or not.
1739
+	 * The criteria for determining this is whether the ticket has any related registrations.
1740
+	 * If there are none then it can be permanently deleted.
1741
+	 *
1742
+	 * @return bool
1743
+	 * @throws EE_Error
1744
+	 * @throws ReflectionException
1745
+	 */
1746
+	public function is_permanently_deleteable()
1747
+	{
1748
+		return $this->count_registrations() === 0;
1749
+	}
1750
+
1751
+
1752
+	/**
1753
+	 * @return int
1754
+	 * @throws EE_Error
1755
+	 * @throws ReflectionException
1756
+	 * @since   5.0.0.p
1757
+	 */
1758
+	public function visibility(): int
1759
+	{
1760
+		return $this->get('TKT_visibility');
1761
+	}
1762
+
1763
+
1764
+	/**
1765
+	 * @return bool
1766
+	 * @throws EE_Error
1767
+	 * @throws ReflectionException
1768
+	 * @since   5.0.0.p
1769
+	 */
1770
+	public function isHidden(): bool
1771
+	{
1772
+		return $this->visibility() === EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1773
+	}
1774
+
1775
+
1776
+	/**
1777
+	 * @return bool
1778
+	 * @throws EE_Error
1779
+	 * @throws ReflectionException
1780
+	 * @since   5.0.0.p
1781
+	 */
1782
+	public function isNotHidden(): bool
1783
+	{
1784
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_NONE_VALUE;
1785
+	}
1786
+
1787
+
1788
+	/**
1789
+	 * @return bool
1790
+	 * @throws EE_Error
1791
+	 * @throws ReflectionException
1792
+	 * @since   5.0.0.p
1793
+	 */
1794
+	public function isPublicOnly(): bool
1795
+	{
1796
+		return $this->isNotHidden() && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE;
1797
+	}
1798
+
1799
+
1800
+	/**
1801
+	 * @return bool
1802
+	 * @throws EE_Error
1803
+	 * @throws ReflectionException
1804
+	 * @since   5.0.0.p
1805
+	 */
1806
+	public function isMembersOnly(): bool
1807
+	{
1808
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_PUBLIC_VALUE
1809
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE;
1810
+	}
1811
+
1812
+
1813
+	/**
1814
+	 * @return bool
1815
+	 * @throws EE_Error
1816
+	 * @throws ReflectionException
1817
+	 * @since   5.0.0.p
1818
+	 */
1819
+	public function isAdminsOnly(): bool
1820
+	{
1821
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_MEMBERS_ONLY_VALUE
1822
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE;
1823
+	}
1824
+
1825
+
1826
+	/**
1827
+	 * @return bool
1828
+	 * @throws EE_Error
1829
+	 * @throws ReflectionException
1830
+	 * @since   5.0.0.p
1831
+	 */
1832
+	public function isAdminUiOnly(): bool
1833
+	{
1834
+		return $this->visibility() > EEM_Ticket::TICKET_VISIBILITY_ADMINS_ONLY_VALUE
1835
+			   && $this->visibility() <= EEM_Ticket::TICKET_VISIBILITY_ADMIN_UI_ONLY_VALUE;
1836
+	}
1837
+
1838
+
1839
+	/**
1840
+	 * @param int $visibility
1841
+	 * @throws EE_Error
1842
+	 * @throws ReflectionException
1843
+	 * @since   5.0.0.p
1844
+	 */
1845
+	public function set_visibility(int $visibility)
1846
+	{
1847
+		$ticket_visibility_options = $this->_model->ticketVisibilityOptions();
1848
+		$ticket_visibility         = -1;
1849
+		foreach ($ticket_visibility_options as $ticket_visibility_option) {
1850
+			if ($visibility === $ticket_visibility_option) {
1851
+				$ticket_visibility = $visibility;
1852
+			}
1853
+		}
1854
+		if ($ticket_visibility === -1) {
1855
+			throw new DomainException(
1856
+				sprintf(
1857
+					esc_html__(
1858
+						'The supplied ticket visibility setting of "%1$s" is not valid. It needs to match one of the keys in the following array:%2$s %3$s ',
1859
+						'event_espresso'
1860
+					),
1861
+					$visibility,
1862
+					'<br />',
1863
+					var_export($ticket_visibility_options, true)
1864
+				)
1865
+			);
1866
+		}
1867
+		$this->set('TKT_visibility', $ticket_visibility);
1868
+	}
1869
+
1870
+
1871
+	/**
1872
+	 * @param EE_Base_Class|int|string $otherObjectModelObjectOrID
1873
+	 * @param string                   $relationName
1874
+	 * @param array                    $extra_join_model_fields_n_values
1875
+	 * @param string|null              $cache_id
1876
+	 * @return EE_Base_Class
1877
+	 * @throws EE_Error
1878
+	 * @throws ReflectionException
1879
+	 * @since   5.0.0.p
1880
+	 */
1881
+	public function _add_relation_to(
1882
+		$otherObjectModelObjectOrID,
1883
+		$relationName,
1884
+		$extra_join_model_fields_n_values = [],
1885
+		$cache_id = null
1886
+	) {
1887
+		if ($relationName === 'Datetime' && ! $this->hasRelation($otherObjectModelObjectOrID, $relationName)) {
1888
+			/** @var EE_Datetime $datetime */
1889
+			$datetime = EEM_Datetime::instance()->ensure_is_obj($otherObjectModelObjectOrID);
1890
+			$datetime->increaseSold($this->sold(), false);
1891
+			$datetime->increaseReserved($this->reserved());
1892
+			$datetime->save();
1893
+			$otherObjectModelObjectOrID = $datetime;
1894
+		}
1895
+		return parent::_add_relation_to(
1896
+			$otherObjectModelObjectOrID,
1897
+			$relationName,
1898
+			$extra_join_model_fields_n_values,
1899
+			$cache_id
1900
+		);
1901
+	}
1902
+
1903
+
1904
+	/**
1905
+	 * @param EE_Base_Class|int|string $otherObjectModelObjectOrID
1906
+	 * @param string                   $relationName
1907
+	 * @param array                    $where_query
1908
+	 * @return bool|EE_Base_Class|null
1909
+	 * @throws EE_Error
1910
+	 * @throws ReflectionException
1911
+	 * @since   5.0.0.p
1912
+	 */
1913
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = [])
1914
+	{
1915
+		// if we're adding a new relation to a datetime
1916
+		if ($relationName === 'Datetime' && $this->hasRelation($otherObjectModelObjectOrID, $relationName)) {
1917
+			/** @var EE_Datetime $datetime */
1918
+			$datetime = EEM_Datetime::instance()->ensure_is_obj($otherObjectModelObjectOrID);
1919
+			$datetime->decreaseSold($this->sold());
1920
+			$datetime->decreaseReserved($this->reserved());
1921
+			$datetime->save();
1922
+			$otherObjectModelObjectOrID = $datetime;
1923
+		}
1924
+		return parent::_remove_relation_to(
1925
+			$otherObjectModelObjectOrID,
1926
+			$relationName,
1927
+			$where_query
1928
+		);
1929
+	}
1930
+
1931
+
1932
+	/**
1933
+	 * Removes ALL the related things for the $relationName.
1934
+	 *
1935
+	 * @param string $relationName
1936
+	 * @param array  $where_query_params
1937
+	 * @return EE_Base_Class
1938
+	 * @throws ReflectionException
1939
+	 * @throws InvalidArgumentException
1940
+	 * @throws InvalidInterfaceException
1941
+	 * @throws InvalidDataTypeException
1942
+	 * @throws EE_Error
1943
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
1944
+	 */
1945
+	public function _remove_relations($relationName, $where_query_params = [])
1946
+	{
1947
+		if ($relationName === 'Datetime') {
1948
+			$datetimes = $this->datetimes();
1949
+			foreach ($datetimes as $datetime) {
1950
+				$datetime->decreaseSold($this->sold());
1951
+				$datetime->decreaseReserved($this->reserved());
1952
+				$datetime->save();
1953
+			}
1954
+		}
1955
+		return parent::_remove_relations($relationName, $where_query_params);
1956
+	}
1957
+
1958
+
1959
+	/*******************************************************************
1960 1960
      ***********************  DEPRECATED METHODS  **********************
1961 1961
      *******************************************************************/
1962 1962
 
1963 1963
 
1964
-    /**
1965
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1966
-     * associated datetimes.
1967
-     *
1968
-     * @param int $qty
1969
-     * @return void
1970
-     * @throws EE_Error
1971
-     * @throws InvalidArgumentException
1972
-     * @throws InvalidDataTypeException
1973
-     * @throws InvalidInterfaceException
1974
-     * @throws ReflectionException
1975
-     * @deprecated 4.9.80.p
1976
-     */
1977
-    public function increase_sold($qty = 1)
1978
-    {
1979
-        EE_Error::doing_it_wrong(
1980
-            __FUNCTION__,
1981
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1982
-            '4.9.80.p',
1983
-            '5.0.0.p'
1984
-        );
1985
-        $this->increaseSold($qty);
1986
-    }
1987
-
1988
-
1989
-    /**
1990
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1991
-     *
1992
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1993
-     *                 Negative means to decreases old counts (and increase reserved counts).
1994
-     * @throws EE_Error
1995
-     * @throws InvalidArgumentException
1996
-     * @throws InvalidDataTypeException
1997
-     * @throws InvalidInterfaceException
1998
-     * @throws ReflectionException
1999
-     * @deprecated 4.9.80.p
2000
-     */
2001
-    protected function _increase_sold_for_datetimes($qty)
2002
-    {
2003
-        EE_Error::doing_it_wrong(
2004
-            __FUNCTION__,
2005
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
2006
-            '4.9.80.p',
2007
-            '5.0.0.p'
2008
-        );
2009
-        $this->increaseSoldForDatetimes($qty);
2010
-    }
2011
-
2012
-
2013
-    /**
2014
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
2015
-     * DB and then updates the model objects.
2016
-     * Does not affect the reserved counts.
2017
-     *
2018
-     * @param int $qty
2019
-     * @return void
2020
-     * @throws EE_Error
2021
-     * @throws InvalidArgumentException
2022
-     * @throws InvalidDataTypeException
2023
-     * @throws InvalidInterfaceException
2024
-     * @throws ReflectionException
2025
-     * @deprecated 4.9.80.p
2026
-     */
2027
-    public function decrease_sold($qty = 1)
2028
-    {
2029
-        EE_Error::doing_it_wrong(
2030
-            __FUNCTION__,
2031
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
2032
-            '4.9.80.p',
2033
-            '5.0.0.p'
2034
-        );
2035
-        $this->decreaseSold($qty);
2036
-    }
2037
-
2038
-
2039
-    /**
2040
-     * Decreases sold on related datetimes
2041
-     *
2042
-     * @param int $qty
2043
-     * @return void
2044
-     * @throws EE_Error
2045
-     * @throws InvalidArgumentException
2046
-     * @throws InvalidDataTypeException
2047
-     * @throws InvalidInterfaceException
2048
-     * @throws ReflectionException
2049
-     * @deprecated 4.9.80.p
2050
-     */
2051
-    protected function _decrease_sold_for_datetimes($qty = 1)
2052
-    {
2053
-        EE_Error::doing_it_wrong(
2054
-            __FUNCTION__,
2055
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
2056
-            '4.9.80.p',
2057
-            '5.0.0.p'
2058
-        );
2059
-        $this->decreaseSoldForDatetimes($qty);
2060
-    }
2061
-
2062
-
2063
-    /**
2064
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
2065
-     *
2066
-     * @param int    $qty
2067
-     * @param string $source
2068
-     * @return bool whether we successfully reserved the ticket or not.
2069
-     * @throws EE_Error
2070
-     * @throws InvalidArgumentException
2071
-     * @throws ReflectionException
2072
-     * @throws InvalidDataTypeException
2073
-     * @throws InvalidInterfaceException
2074
-     * @deprecated 4.9.80.p
2075
-     */
2076
-    public function increase_reserved($qty = 1, $source = 'unknown')
2077
-    {
2078
-        EE_Error::doing_it_wrong(
2079
-            __FUNCTION__,
2080
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
2081
-            '4.9.80.p',
2082
-            '5.0.0.p'
2083
-        );
2084
-        return $this->increaseReserved($qty);
2085
-    }
2086
-
2087
-
2088
-    /**
2089
-     * Increases sold on related datetimes
2090
-     *
2091
-     * @param int $qty
2092
-     * @return boolean indicating success
2093
-     * @throws EE_Error
2094
-     * @throws InvalidArgumentException
2095
-     * @throws InvalidDataTypeException
2096
-     * @throws InvalidInterfaceException
2097
-     * @throws ReflectionException
2098
-     * @deprecated 4.9.80.p
2099
-     */
2100
-    protected function _increase_reserved_for_datetimes($qty = 1)
2101
-    {
2102
-        EE_Error::doing_it_wrong(
2103
-            __FUNCTION__,
2104
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2105
-            '4.9.80.p',
2106
-            '5.0.0.p'
2107
-        );
2108
-        return $this->increaseReservedForDatetimes($qty);
2109
-    }
2110
-
2111
-
2112
-    /**
2113
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2114
-     *
2115
-     * @param int    $qty
2116
-     * @param bool   $adjust_datetimes
2117
-     * @param string $source
2118
-     * @return void
2119
-     * @throws EE_Error
2120
-     * @throws InvalidArgumentException
2121
-     * @throws ReflectionException
2122
-     * @throws InvalidDataTypeException
2123
-     * @throws InvalidInterfaceException
2124
-     * @deprecated 4.9.80.p
2125
-     */
2126
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2127
-    {
2128
-        EE_Error::doing_it_wrong(
2129
-            __FUNCTION__,
2130
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2131
-            '4.9.80.p',
2132
-            '5.0.0.p'
2133
-        );
2134
-        $this->decreaseReserved($qty);
2135
-    }
2136
-
2137
-
2138
-    /**
2139
-     * Decreases reserved on related datetimes
2140
-     *
2141
-     * @param int $qty
2142
-     * @return void
2143
-     * @throws EE_Error
2144
-     * @throws InvalidArgumentException
2145
-     * @throws ReflectionException
2146
-     * @throws InvalidDataTypeException
2147
-     * @throws InvalidInterfaceException
2148
-     * @deprecated 4.9.80.p
2149
-     */
2150
-    protected function _decrease_reserved_for_datetimes($qty = 1)
2151
-    {
2152
-        EE_Error::doing_it_wrong(
2153
-            __FUNCTION__,
2154
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2155
-            '4.9.80.p',
2156
-            '5.0.0.p'
2157
-        );
2158
-        $this->decreaseReservedForDatetimes($qty);
2159
-    }
1964
+	/**
1965
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1966
+	 * associated datetimes.
1967
+	 *
1968
+	 * @param int $qty
1969
+	 * @return void
1970
+	 * @throws EE_Error
1971
+	 * @throws InvalidArgumentException
1972
+	 * @throws InvalidDataTypeException
1973
+	 * @throws InvalidInterfaceException
1974
+	 * @throws ReflectionException
1975
+	 * @deprecated 4.9.80.p
1976
+	 */
1977
+	public function increase_sold($qty = 1)
1978
+	{
1979
+		EE_Error::doing_it_wrong(
1980
+			__FUNCTION__,
1981
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1982
+			'4.9.80.p',
1983
+			'5.0.0.p'
1984
+		);
1985
+		$this->increaseSold($qty);
1986
+	}
1987
+
1988
+
1989
+	/**
1990
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1991
+	 *
1992
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1993
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1994
+	 * @throws EE_Error
1995
+	 * @throws InvalidArgumentException
1996
+	 * @throws InvalidDataTypeException
1997
+	 * @throws InvalidInterfaceException
1998
+	 * @throws ReflectionException
1999
+	 * @deprecated 4.9.80.p
2000
+	 */
2001
+	protected function _increase_sold_for_datetimes($qty)
2002
+	{
2003
+		EE_Error::doing_it_wrong(
2004
+			__FUNCTION__,
2005
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
2006
+			'4.9.80.p',
2007
+			'5.0.0.p'
2008
+		);
2009
+		$this->increaseSoldForDatetimes($qty);
2010
+	}
2011
+
2012
+
2013
+	/**
2014
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
2015
+	 * DB and then updates the model objects.
2016
+	 * Does not affect the reserved counts.
2017
+	 *
2018
+	 * @param int $qty
2019
+	 * @return void
2020
+	 * @throws EE_Error
2021
+	 * @throws InvalidArgumentException
2022
+	 * @throws InvalidDataTypeException
2023
+	 * @throws InvalidInterfaceException
2024
+	 * @throws ReflectionException
2025
+	 * @deprecated 4.9.80.p
2026
+	 */
2027
+	public function decrease_sold($qty = 1)
2028
+	{
2029
+		EE_Error::doing_it_wrong(
2030
+			__FUNCTION__,
2031
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
2032
+			'4.9.80.p',
2033
+			'5.0.0.p'
2034
+		);
2035
+		$this->decreaseSold($qty);
2036
+	}
2037
+
2038
+
2039
+	/**
2040
+	 * Decreases sold on related datetimes
2041
+	 *
2042
+	 * @param int $qty
2043
+	 * @return void
2044
+	 * @throws EE_Error
2045
+	 * @throws InvalidArgumentException
2046
+	 * @throws InvalidDataTypeException
2047
+	 * @throws InvalidInterfaceException
2048
+	 * @throws ReflectionException
2049
+	 * @deprecated 4.9.80.p
2050
+	 */
2051
+	protected function _decrease_sold_for_datetimes($qty = 1)
2052
+	{
2053
+		EE_Error::doing_it_wrong(
2054
+			__FUNCTION__,
2055
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
2056
+			'4.9.80.p',
2057
+			'5.0.0.p'
2058
+		);
2059
+		$this->decreaseSoldForDatetimes($qty);
2060
+	}
2061
+
2062
+
2063
+	/**
2064
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
2065
+	 *
2066
+	 * @param int    $qty
2067
+	 * @param string $source
2068
+	 * @return bool whether we successfully reserved the ticket or not.
2069
+	 * @throws EE_Error
2070
+	 * @throws InvalidArgumentException
2071
+	 * @throws ReflectionException
2072
+	 * @throws InvalidDataTypeException
2073
+	 * @throws InvalidInterfaceException
2074
+	 * @deprecated 4.9.80.p
2075
+	 */
2076
+	public function increase_reserved($qty = 1, $source = 'unknown')
2077
+	{
2078
+		EE_Error::doing_it_wrong(
2079
+			__FUNCTION__,
2080
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
2081
+			'4.9.80.p',
2082
+			'5.0.0.p'
2083
+		);
2084
+		return $this->increaseReserved($qty);
2085
+	}
2086
+
2087
+
2088
+	/**
2089
+	 * Increases sold on related datetimes
2090
+	 *
2091
+	 * @param int $qty
2092
+	 * @return boolean indicating success
2093
+	 * @throws EE_Error
2094
+	 * @throws InvalidArgumentException
2095
+	 * @throws InvalidDataTypeException
2096
+	 * @throws InvalidInterfaceException
2097
+	 * @throws ReflectionException
2098
+	 * @deprecated 4.9.80.p
2099
+	 */
2100
+	protected function _increase_reserved_for_datetimes($qty = 1)
2101
+	{
2102
+		EE_Error::doing_it_wrong(
2103
+			__FUNCTION__,
2104
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
2105
+			'4.9.80.p',
2106
+			'5.0.0.p'
2107
+		);
2108
+		return $this->increaseReservedForDatetimes($qty);
2109
+	}
2110
+
2111
+
2112
+	/**
2113
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
2114
+	 *
2115
+	 * @param int    $qty
2116
+	 * @param bool   $adjust_datetimes
2117
+	 * @param string $source
2118
+	 * @return void
2119
+	 * @throws EE_Error
2120
+	 * @throws InvalidArgumentException
2121
+	 * @throws ReflectionException
2122
+	 * @throws InvalidDataTypeException
2123
+	 * @throws InvalidInterfaceException
2124
+	 * @deprecated 4.9.80.p
2125
+	 */
2126
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
2127
+	{
2128
+		EE_Error::doing_it_wrong(
2129
+			__FUNCTION__,
2130
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
2131
+			'4.9.80.p',
2132
+			'5.0.0.p'
2133
+		);
2134
+		$this->decreaseReserved($qty);
2135
+	}
2136
+
2137
+
2138
+	/**
2139
+	 * Decreases reserved on related datetimes
2140
+	 *
2141
+	 * @param int $qty
2142
+	 * @return void
2143
+	 * @throws EE_Error
2144
+	 * @throws InvalidArgumentException
2145
+	 * @throws ReflectionException
2146
+	 * @throws InvalidDataTypeException
2147
+	 * @throws InvalidInterfaceException
2148
+	 * @deprecated 4.9.80.p
2149
+	 */
2150
+	protected function _decrease_reserved_for_datetimes($qty = 1)
2151
+	{
2152
+		EE_Error::doing_it_wrong(
2153
+			__FUNCTION__,
2154
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
2155
+			'4.9.80.p',
2156
+			'5.0.0.p'
2157
+		);
2158
+		$this->decreaseReservedForDatetimes($qty);
2159
+	}
2160 2160
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
     public function ticket_status($display = false, $remaining = null)
172 172
     {
173 173
         $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
174
-        if (! $remaining) {
174
+        if ( ! $remaining) {
175 175
             return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
176 176
         }
177 177
         if ($this->get('TKT_deleted')) {
@@ -299,7 +299,7 @@  discard block
 block discarded – undo
299 299
             ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
300 300
             : '';
301 301
 
302
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
302
+        return $first_date && $last_date ? $first_date.$conjunction.$last_date : '';
303 303
     }
304 304
 
305 305
 
@@ -329,7 +329,7 @@  discard block
 block discarded – undo
329 329
      */
330 330
     public function datetimes($query_params = [])
331 331
     {
332
-        if (! isset($query_params['order_by'])) {
332
+        if ( ! isset($query_params['order_by'])) {
333 333
             $query_params['order_by']['DTT_order'] = 'ASC';
334 334
         }
335 335
         return $this->get_many_related('Datetime', $query_params);
@@ -376,7 +376,7 @@  discard block
 block discarded – undo
376 376
                 if (empty($tickets_sold['datetime'])) {
377 377
                     return $total;
378 378
                 }
379
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
379
+                if ( ! empty($dtt_id) && ! isset($tickets_sold['datetime'][$dtt_id])) {
380 380
                     EE_Error::add_error(
381 381
                         esc_html__(
382 382
                             'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
                     );
389 389
                     return $total;
390 390
                 }
391
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
391
+                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][$dtt_id];
392 392
 
393 393
             default:
394 394
                 return $total;
@@ -407,9 +407,9 @@  discard block
 block discarded – undo
407 407
     {
408 408
         $datetimes    = $this->get_many_related('Datetime');
409 409
         $tickets_sold = [];
410
-        if (! empty($datetimes)) {
410
+        if ( ! empty($datetimes)) {
411 411
             foreach ($datetimes as $datetime) {
412
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
412
+                $tickets_sold['datetime'][$datetime->ID()] = $datetime->get('DTT_sold');
413 413
             }
414 414
         }
415 415
         // Tickets sold
@@ -429,7 +429,7 @@  discard block
 block discarded – undo
429 429
     public function base_price(bool $return_array = false)
430 430
     {
431 431
         $base_price = $this->ticket_price_modifiers->getBasePrice();
432
-        if (! empty($base_price)) {
432
+        if ( ! empty($base_price)) {
433 433
             return $return_array ? $base_price : reset($base_price);
434 434
         }
435 435
         $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
@@ -451,7 +451,7 @@  discard block
 block discarded – undo
451 451
         $price_modifiers = $this->usesGlobalTaxes()
452 452
             ? $this->ticket_price_modifiers->getAllDiscountAndSurchargeModifiersForTicket()
453 453
             : $this->ticket_price_modifiers->getAllModifiersForTicket();
454
-        if (! empty($price_modifiers)) {
454
+        if ( ! empty($price_modifiers)) {
455 455
             return $price_modifiers;
456 456
         }
457 457
         return $this->prices(
@@ -477,7 +477,7 @@  discard block
 block discarded – undo
477 477
     public function tax_price_modifiers(): array
478 478
     {
479 479
         $tax_price_modifiers = $this->ticket_price_modifiers->getAllTaxesForTicket();
480
-        if (! empty($tax_price_modifiers)) {
480
+        if ( ! empty($tax_price_modifiers)) {
481 481
             return $tax_price_modifiers;
482 482
         }
483 483
         return $this->prices([['Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax]]);
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
      */
496 496
     public function prices(array $query_params = []): array
497 497
     {
498
-        if (! isset($query_params['order_by'])) {
498
+        if ( ! isset($query_params['order_by'])) {
499 499
             $query_params['order_by']['PRC_order'] = 'ASC';
500 500
         }
501 501
         return $this->get_many_related('Price', $query_params);
@@ -1066,7 +1066,7 @@  discard block
 block discarded – undo
1066 1066
                 'TKT_qty',
1067 1067
                 $qty
1068 1068
             );
1069
-            if (! $success) {
1069
+            if ( ! $success) {
1070 1070
                 // The datetimes were successfully bumped, but not the
1071 1071
                 // ticket. So we need to manually rollback the datetimes.
1072 1072
                 $this->decreaseReservedForDatetimes($qty);
@@ -1677,7 +1677,7 @@  discard block
 block discarded – undo
1677 1677
     {
1678 1678
         // get one datetime to use for getting the event
1679 1679
         $datetime = $this->first_datetime();
1680
-        if (! $datetime instanceof EE_Datetime) {
1680
+        if ( ! $datetime instanceof EE_Datetime) {
1681 1681
             throw new UnexpectedEntityException(
1682 1682
                 $datetime,
1683 1683
                 'EE_Datetime',
@@ -1688,7 +1688,7 @@  discard block
 block discarded – undo
1688 1688
             );
1689 1689
         }
1690 1690
         $event = $datetime->event();
1691
-        if (! $event instanceof EE_Event) {
1691
+        if ( ! $event instanceof EE_Event) {
1692 1692
             throw new UnexpectedEntityException(
1693 1693
                 $event,
1694 1694
                 'EE_Event',
Please login to merge, or discard this patch.
core/db_classes/EE_Message.class.php 2 patches
Indentation   +872 added lines, -872 removed lines patch added patch discarded remove patch
@@ -9,880 +9,880 @@
 block discarded – undo
9 9
  */
10 10
 class EE_Message extends EE_Base_Class implements EEI_Admin_Links
11 11
 {
12
-    /**
13
-     * @deprecated 4.9.0  Added for backward compat with add-on's
14
-     * @type null
15
-     */
16
-    public $template_pack;
17
-
18
-    /**
19
-     * @deprecated 4.9.0 Added for backward compat with add-on's
20
-     * @type null
21
-     */
22
-    public $template_variation;
23
-
24
-    /**
25
-     * @deprecated 4.9.0 Added for backward compat with add-on's
26
-     * @type string
27
-     */
28
-    public $content = '';
29
-
30
-
31
-    /**
32
-     * @type EE_messenger $_messenger
33
-     */
34
-    protected $_messenger = null;
35
-
36
-    /**
37
-     * @type EE_message_type $_message_type
38
-     */
39
-    protected $_message_type = null;
40
-
41
-
42
-    /**
43
-     * @param array  $props_n_values
44
-     * @param string $timezone
45
-     * @param array  $date_formats incoming date formats in an array.  First value is the date_format, second is time
46
-     *                             format.
47
-     * @return EE_Message
48
-     */
49
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
50
-    {
51
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__);
52
-        // if object doesn't exist, let's generate a unique token on instantiation so that its available even before saving to db.
53
-        if (! $has_object) {
54
-            EE_Registry::instance()->load_helper('URL');
55
-            $props_n_values['MSG_token'] = EEH_URL::generate_unique_token();
56
-        }
57
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
58
-    }
59
-
60
-
61
-    /**
62
-     * @param array  $props_n_values
63
-     * @param string $timezone
64
-     * @return EE_Message
65
-     */
66
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
67
-    {
68
-        return new self($props_n_values, true, $timezone);
69
-    }
70
-
71
-
72
-    /**
73
-     * Gets MSG_token
74
-     *
75
-     * @return int
76
-     */
77
-    public function MSG_token()
78
-    {
79
-        return $this->get('MSG_token');
80
-    }
81
-
82
-
83
-    /**
84
-     * Sets MSG_token
85
-     *
86
-     * @param int $MSG_token
87
-     */
88
-    public function set_MSG_token($MSG_token)
89
-    {
90
-        $this->set('MSG_token', $MSG_token);
91
-    }
92
-
93
-
94
-    /**
95
-     * Gets GRP_ID
96
-     *
97
-     * @return int
98
-     */
99
-    public function GRP_ID()
100
-    {
101
-        return $this->get('GRP_ID');
102
-    }
103
-
104
-
105
-    /**
106
-     * Sets GRP_ID
107
-     *
108
-     * @param int $GRP_ID
109
-     */
110
-    public function set_GRP_ID($GRP_ID)
111
-    {
112
-        $this->set('GRP_ID', $GRP_ID);
113
-    }
114
-
115
-
116
-    /**
117
-     * Gets TXN_ID
118
-     *
119
-     * @return int
120
-     */
121
-    public function TXN_ID()
122
-    {
123
-        return $this->get('TXN_ID');
124
-    }
125
-
126
-
127
-    /**
128
-     * Sets TXN_ID
129
-     *
130
-     * @param int $TXN_ID
131
-     */
132
-    public function set_TXN_ID($TXN_ID)
133
-    {
134
-        $this->set('TXN_ID', $TXN_ID);
135
-    }
136
-
137
-
138
-    /**
139
-     * Gets messenger
140
-     *
141
-     * @return string
142
-     */
143
-    public function messenger()
144
-    {
145
-        return $this->get('MSG_messenger');
146
-    }
147
-
148
-
149
-    /**
150
-     * Sets messenger
151
-     *
152
-     * @param string $messenger
153
-     */
154
-    public function set_messenger($messenger)
155
-    {
156
-        $this->set('MSG_messenger', $messenger);
157
-    }
158
-
159
-
160
-    /**
161
-     * Returns corresponding messenger object for the set messenger on this message
162
-     *
163
-     * @return EE_messenger | null
164
-     */
165
-    public function messenger_object()
166
-    {
167
-        return $this->_messenger;
168
-    }
169
-
170
-
171
-    /**
172
-     * Sets messenger
173
-     *
174
-     * @param EE_messenger $messenger
175
-     */
176
-    public function set_messenger_object(EE_messenger $messenger)
177
-    {
178
-        $this->_messenger = $messenger;
179
-    }
180
-
181
-
182
-    /**
183
-     * validates messenger
184
-     *
185
-     * @param bool $throw_exceptions
186
-     * @return bool
187
-     * @throws \EE_Error
188
-     */
189
-    public function valid_messenger($throw_exceptions = false)
190
-    {
191
-        if ($this->_messenger instanceof EE_messenger) {
192
-            return true;
193
-        }
194
-        if ($throw_exceptions) {
195
-            throw new EE_Error(
196
-                sprintf(
197
-                    esc_html__(
198
-                        'The "%1$s" messenger set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
199
-                        'event_espresso'
200
-                    ),
201
-                    $this->messenger()
202
-                )
203
-            );
204
-        }
205
-        return false;
206
-    }
207
-
208
-
209
-    /**
210
-     * This returns the set localized label for the messenger on this message.
211
-     * Note, if unable to retrieve the EE_messenger object then will just return the messenger slug saved
212
-     * with this message.
213
-     *
214
-     * @param bool $plural whether to return the plural label or not.
215
-     * @return string
216
-     */
217
-    public function messenger_label($plural = false)
218
-    {
219
-        $label_type = $plural ? 'plural' : 'singular';
220
-        $messenger  = $this->messenger_object();
221
-        return $messenger instanceof EE_messenger ? $messenger->label[ $label_type ] : $this->messenger();
222
-    }
223
-
224
-
225
-    /**
226
-     * Gets message_type
227
-     *
228
-     * @return string
229
-     */
230
-    public function message_type()
231
-    {
232
-        return $this->get('MSG_message_type');
233
-    }
234
-
235
-
236
-    /**
237
-     * Sets message_type
238
-     *
239
-     * @param string $message_type
240
-     */
241
-    public function set_message_type($message_type)
242
-    {
243
-        $this->set('MSG_message_type', $message_type);
244
-    }
245
-
246
-
247
-    /**
248
-     * Returns the message type object for the set message type on this message
249
-     *
250
-     * @return EE_message_type | null
251
-     */
252
-    public function message_type_object()
253
-    {
254
-        return $this->_message_type;
255
-    }
256
-
257
-
258
-    /**
259
-     * Sets message_type
260
-     *
261
-     * @param EE_message_type $message_type
262
-     * @param bool            $set_priority   This indicates whether to set the priority to whatever the priority is on
263
-     *                                        the message type or not.
264
-     */
265
-    public function set_message_type_object(EE_message_type $message_type, $set_priority = false)
266
-    {
267
-        $this->_message_type = $message_type;
268
-        if ($set_priority) {
269
-            $this->set_priority($this->_message_type->get_priority());
270
-        }
271
-    }
272
-
273
-
274
-    /**
275
-     * validates message_type
276
-     *
277
-     * @param bool $throw_exceptions
278
-     * @return bool
279
-     * @throws \EE_Error
280
-     */
281
-    public function valid_message_type($throw_exceptions = false)
282
-    {
283
-        if ($this->_message_type instanceof EE_message_type) {
284
-            return true;
285
-        }
286
-        if ($throw_exceptions) {
287
-            throw new EE_Error(
288
-                sprintf(
289
-                    esc_html__(
290
-                        'The %1$s message type set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
291
-                        'event_espresso'
292
-                    ),
293
-                    $this->message_type()
294
-                )
295
-            );
296
-        }
297
-        return false;
298
-    }
299
-
300
-
301
-    /**
302
-     * validates messenger and message_type (that they are valid EE_messenger and EE_message_type objects).
303
-     *
304
-     * @param bool $throw_exceptions
305
-     * @return bool
306
-     * @throws \EE_Error
307
-     */
308
-    public function is_valid($throw_exceptions = false)
309
-    {
310
-        if ($this->valid_messenger($throw_exceptions) && $this->valid_message_type($throw_exceptions)) {
311
-            return true;
312
-        }
313
-        return false;
314
-    }
315
-
316
-
317
-    /**
318
-     * This validates whether the internal messenger and message type objects are valid for sending.
319
-     * Three checks are done:
320
-     * 1. There is a valid messenger object.
321
-     * 2. There is a valid message type object.
322
-     * 3. The message type object is active for the messenger.
323
-     *
324
-     * @param bool $throw_exceptions
325
-     * @return bool
326
-     * @throws EE_Error  But only if $throw_exceptions is set to true.
327
-     */
328
-    public function is_valid_for_sending_or_generation($throw_exceptions = false)
329
-    {
330
-        $valid = false;
331
-        if ($this->is_valid($throw_exceptions)) {
332
-            /** @var EE_Message_Resource_Manager $message_resource_manager */
333
-            $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
334
-            $valid                    = $message_resource_manager->is_message_type_active_for_messenger(
335
-                $this->messenger(),
336
-                $this->message_type()
337
-            );
338
-            if (! $valid && $throw_exceptions) {
339
-                throw new EE_Error(
340
-                    sprintf(
341
-                        esc_html__(
342
-                            'The %1$s message type is not a valid message type for the %2$s messenger so it will not be sent.',
343
-                            'event_espresso'
344
-                        ),
345
-                        $this->message_type(),
346
-                        $this->messenger()
347
-                    )
348
-                );
349
-            }
350
-        }
351
-        return $valid;
352
-    }
353
-
354
-
355
-    /**
356
-     * This returns the set localized label for the message type on this message.
357
-     * Note, if unable to retrieve the EE_message_type object then will just return the message type slug saved
358
-     * with this message.
359
-     *
360
-     * @param bool $plural whether to return the plural label or not.
361
-     * @return string
362
-     */
363
-    public function message_type_label($plural = false)
364
-    {
365
-        $label_type   = $plural ? 'plural' : 'singular';
366
-        $message_type = $this->message_type_object();
367
-        return $message_type instanceof EE_message_type
368
-            ? $message_type->label[ $label_type ]
369
-            : str_replace(
370
-                '_',
371
-                ' ',
372
-                $this->message_type()
373
-            );
374
-    }
375
-
376
-
377
-    /**
378
-     * Gets context
379
-     *
380
-     * @return string
381
-     */
382
-    public function context()
383
-    {
384
-        return $this->get('MSG_context');
385
-    }
386
-
387
-
388
-    /**
389
-     * This returns the corresponding localized label for the given context slug, if possible from installed message
390
-     * types. Otherwise, this will just return the set context slug on this object.
391
-     *
392
-     * @return string
393
-     */
394
-    public function context_label()
395
-    {
396
-        /** @type EE_Message_Resource_Manager $message_resource_manager */
397
-        $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
398
-        $contexts                 = $message_resource_manager->get_all_contexts();
399
-        return isset($contexts[ $this->context() ]) ? $contexts[ $this->context() ] : $this->context();
400
-    }
401
-
402
-
403
-    /**
404
-     * Sets context
405
-     *
406
-     * @param string $context
407
-     */
408
-    public function set_context($context)
409
-    {
410
-        $this->set('MSG_context', $context);
411
-    }
412
-
413
-
414
-    /**
415
-     * Gets recipient_ID
416
-     *
417
-     * @return int
418
-     */
419
-    public function recipient_ID()
420
-    {
421
-        return $this->get('MSG_recipient_ID');
422
-    }
423
-
424
-
425
-    /**
426
-     * Sets recipient_ID
427
-     *
428
-     * @param string $recipient_ID
429
-     */
430
-    public function set_recipient_ID($recipient_ID)
431
-    {
432
-        $this->set('MSG_recipient_ID', $recipient_ID);
433
-    }
434
-
435
-
436
-    /**
437
-     * Gets recipient_type
438
-     *
439
-     * @return string
440
-     */
441
-    public function recipient_type()
442
-    {
443
-        return $this->get('MSG_recipient_type');
444
-    }
445
-
446
-
447
-    /**
448
-     * Return the related object matching the recipient type and ID.
449
-     *
450
-     * @return EE_Base_Class | null
451
-     */
452
-    public function recipient_object()
453
-    {
454
-        if (! $this->recipient_type() || ! $this->recipient_ID()) {
455
-            return null;
456
-        }
457
-
458
-        return $this->get_first_related($this->recipient_type());
459
-    }
460
-
461
-
462
-    /**
463
-     * Sets recipient_type
464
-     *
465
-     * @param string $recipient_type
466
-     */
467
-    public function set_recipient_type($recipient_type)
468
-    {
469
-        $this->set('MSG_recipient_type', $recipient_type);
470
-    }
471
-
472
-
473
-    /**
474
-     * Gets content
475
-     *
476
-     * @return string
477
-     */
478
-    public function content()
479
-    {
480
-        return $this->get('MSG_content');
481
-    }
482
-
483
-
484
-    /**
485
-     * Sets content
486
-     *
487
-     * @param string $content
488
-     */
489
-    public function set_content($content)
490
-    {
491
-        $this->set('MSG_content', $content);
492
-    }
493
-
494
-
495
-    /**
496
-     * Gets subject
497
-     *
498
-     * @return string
499
-     */
500
-    public function subject()
501
-    {
502
-        return $this->get('MSG_subject');
503
-    }
504
-
505
-
506
-    /**
507
-     * Sets subject
508
-     *
509
-     * @param string $subject
510
-     */
511
-    public function set_subject($subject)
512
-    {
513
-        $this->set('MSG_subject', $subject);
514
-    }
515
-
516
-
517
-    /**
518
-     * Gets to
519
-     *
520
-     * @return string
521
-     */
522
-    public function to()
523
-    {
524
-        $to = $this->get('MSG_to');
525
-        return empty($to) ? esc_html__('No recipient', 'event_espresso') : $to;
526
-    }
527
-
528
-
529
-    /**
530
-     * Sets to
531
-     *
532
-     * @param string $to
533
-     */
534
-    public function set_to($to)
535
-    {
536
-        $this->set('MSG_to', $to);
537
-    }
538
-
539
-
540
-    /**
541
-     * Gets from
542
-     *
543
-     * @return string
544
-     */
545
-    public function from()
546
-    {
547
-        return $this->get('MSG_from');
548
-    }
549
-
550
-
551
-    /**
552
-     * Sets from
553
-     *
554
-     * @param string $from
555
-     */
556
-    public function set_from($from)
557
-    {
558
-        $this->set('MSG_from', $from);
559
-    }
560
-
561
-
562
-    /**
563
-     * Gets priority
564
-     *
565
-     * @return int
566
-     */
567
-    public function priority()
568
-    {
569
-        return $this->get('MSG_priority');
570
-    }
571
-
572
-
573
-    /**
574
-     * Sets priority
575
-     * Note.  Send Now Messengers always override any priority that may be set on a Message.  So
576
-     * this method calls the send_now method to verify that.
577
-     *
578
-     * @param int $priority
579
-     */
580
-    public function set_priority($priority)
581
-    {
582
-        $priority = $this->send_now() ? EEM_Message::priority_high : $priority;
583
-        parent::set('MSG_priority', $priority);
584
-    }
585
-
586
-
587
-    /**
588
-     * Overrides parent::set method so we can capture any sets for priority.
589
-     *
590
-     * @param string $field_name
591
-     * @param mixed  $field_value
592
-     * @param bool   $use_default
593
-     * @throws EE_Error
594
-     * @see parent::set() for phpdocs
595
-     */
596
-    public function set($field_name, $field_value, $use_default = false)
597
-    {
598
-        if ($field_name === 'MSG_priority') {
599
-            $this->set_priority($field_value);
600
-        }
601
-        parent::set($field_name, $field_value, $use_default);
602
-    }
603
-
604
-
605
-    /**
606
-     * @return bool
607
-     * @throws \EE_Error
608
-     */
609
-    public function send_now()
610
-    {
611
-        $send_now = $this->valid_messenger() && $this->messenger_object()->send_now() ? EEM_Message::priority_high
612
-            : $this->priority();
613
-        return $send_now === EEM_Message::priority_high ? true : false;
614
-    }
615
-
616
-
617
-    /**
618
-     * Gets STS_ID
619
-     *
620
-     * @return string
621
-     */
622
-    public function STS_ID()
623
-    {
624
-        return $this->get('STS_ID');
625
-    }
626
-
627
-
628
-    /**
629
-     * Sets STS_ID
630
-     *
631
-     * @param string $STS_ID
632
-     */
633
-    public function set_STS_ID($STS_ID)
634
-    {
635
-        $this->set('STS_ID', $STS_ID);
636
-    }
637
-
638
-
639
-    /**
640
-     * Gets created
641
-     *
642
-     * @return string
643
-     */
644
-    public function created()
645
-    {
646
-        return $this->get('MSG_created');
647
-    }
648
-
649
-
650
-    /**
651
-     * Sets created
652
-     *
653
-     * @param string $created
654
-     */
655
-    public function set_created($created)
656
-    {
657
-        $this->set('MSG_created', $created);
658
-    }
659
-
660
-
661
-    /**
662
-     * Gets modified
663
-     *
664
-     * @return string
665
-     */
666
-    public function modified()
667
-    {
668
-        return $this->get('MSG_modified');
669
-    }
670
-
671
-
672
-    /**
673
-     * Sets modified
674
-     *
675
-     * @param string $modified
676
-     */
677
-    public function set_modified($modified)
678
-    {
679
-        $this->set('MSG_modified', $modified);
680
-    }
681
-
682
-
683
-    /**
684
-     * Sets generation data for this message.
685
-     *
686
-     * @param mixed $data
687
-     */
688
-    public function set_generation_data($data)
689
-    {
690
-        $this->set_field_or_extra_meta('MSG_generation_data', $data);
691
-    }
692
-
693
-
694
-    /**
695
-     * Returns any set generation data for this message.
696
-     *
697
-     * @return mixed|null
698
-     */
699
-    public function get_generation_data()
700
-    {
701
-        return $this->get_field_or_extra_meta('MSG_generation_data');
702
-    }
703
-
704
-
705
-    /**
706
-     * Gets any error message.
707
-     *
708
-     * @return mixed|null
709
-     */
710
-    public function error_message()
711
-    {
712
-        return $this->get_field_or_extra_meta('MSG_error');
713
-    }
714
-
715
-
716
-    /**
717
-     * Sets an error message.
718
-     *
719
-     * @param $message
720
-     * @return bool|int
721
-     */
722
-    public function set_error_message($message)
723
-    {
724
-        return $this->set_field_or_extra_meta('MSG_error', $message);
725
-    }
726
-
727
-
728
-    /**
729
-     * This retrieves the associated template pack with this message.
730
-     *
731
-     * @return EE_Messages_Template_Pack | null
732
-     */
733
-    public function get_template_pack()
734
-    {
735
-        /**
736
-         * This is deprecated functionality that will be removed eventually but included here now for backward compat.
737
-         */
738
-        if (! empty($this->template_pack)) {
739
-            return $this->template_pack;
740
-        }
741
-        /** @type EE_Message_Template_Group $grp */
742
-        $grp = $this->get_first_related('Message_Template_Group');
743
-        // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
744
-        if (! $grp instanceof EE_Message_Template_Group) {
745
-            $grp = EEM_Message_Template_Group::instance()->get_one(
746
-                [
747
-                    [
748
-                        'MTP_messenger'    => $this->messenger(),
749
-                        'MTP_message_type' => $this->message_type(),
750
-                        'MTP_is_global'    => true,
751
-                    ],
752
-                ]
753
-            );
754
-        }
755
-
756
-        return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack() : null;
757
-    }
758
-
759
-
760
-    /**
761
-     * Retrieves the variation used for generating this message.
762
-     *
763
-     * @return string
764
-     */
765
-    public function get_template_pack_variation()
766
-    {
767
-        /**
768
-         * This is deprecated functionality that will be removed eventually but included here now for backward compat.
769
-         */
770
-        if (! empty($this->template_variation)) {
771
-            return $this->template_variation;
772
-        }
773
-
774
-        /** @type EE_Message_Template_Group $grp */
775
-        $grp = $this->get_first_related('Message_Template_Group');
776
-
777
-        // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
778
-        if (! $grp instanceof EE_Message_Template_Group) {
779
-            $grp = EEM_Message_Template_Group::instance()->get_one(
780
-                [
781
-                    [
782
-                        'MTP_messenger'    => $this->messenger(),
783
-                        'MTP_message_type' => $this->message_type(),
784
-                        'MTP_is_global'    => true,
785
-                    ],
786
-                ]
787
-            );
788
-        }
789
-
790
-        return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack_variation() : '';
791
-    }
792
-
793
-
794
-    /**
795
-     * Return the link to the admin details for the object.
796
-     *
797
-     * @return string
798
-     */
799
-    public function get_admin_details_link()
800
-    {
801
-        EE_Registry::instance()->load_helper('URL');
802
-        EE_Registry::instance()->load_helper('MSG_Template');
803
-        switch ($this->STS_ID()) {
804
-            case EEM_Message::status_failed:
805
-            case EEM_Message::status_debug_only:
806
-                return EEH_MSG_Template::generate_error_display_trigger($this);
807
-                break;
808
-
809
-            case EEM_Message::status_sent:
810
-                return EEH_MSG_Template::generate_browser_trigger($this);
811
-                break;
812
-
813
-            default:
814
-                return '';
815
-        }
816
-    }
817
-
818
-
819
-    /**
820
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
821
-     *
822
-     * @return string
823
-     */
824
-    public function get_admin_edit_link()
825
-    {
826
-        return $this->get_admin_details_link();
827
-    }
828
-
829
-
830
-    /**
831
-     * Returns the link to a settings page for the object.
832
-     *
833
-     * @return string
834
-     */
835
-    public function get_admin_settings_link()
836
-    {
837
-        EE_Registry::instance()->load_helper('URL');
838
-        return EEH_URL::add_query_args_and_nonce(
839
-            [
840
-                'page'   => 'espresso_messages',
841
-                'action' => 'settings',
842
-            ],
843
-            admin_url('admin.php')
844
-        );
845
-    }
846
-
847
-
848
-    /**
849
-     * Returns the link to the "overview" for the object (typically the "list table" view).
850
-     *
851
-     * @return string
852
-     */
853
-    public function get_admin_overview_link()
854
-    {
855
-        EE_Registry::instance()->load_helper('URL');
856
-        return EEH_URL::add_query_args_and_nonce(
857
-            [
858
-                'page'   => 'espresso_messages',
859
-                'action' => 'default',
860
-            ],
861
-            admin_url('admin.php')
862
-        );
863
-    }
864
-
865
-
866
-    /**
867
-     * This sets the EEM_Message::status_messenger_executing class on the message and the appropriate error message for
868
-     * it.
869
-     * Note this also SAVES the current message object to the db because it adds an error message to accompany the
870
-     * status.
871
-     */
872
-    public function set_messenger_is_executing()
873
-    {
874
-        $this->set_STS_ID(EEM_Message::status_messenger_executing);
875
-        if (EEM_Message::debug()) {
876
-            $this->set_error_message(
877
-                esc_html__(
878
-                    'A message with this status indicates that there was a problem that occurred while the message was being
12
+	/**
13
+	 * @deprecated 4.9.0  Added for backward compat with add-on's
14
+	 * @type null
15
+	 */
16
+	public $template_pack;
17
+
18
+	/**
19
+	 * @deprecated 4.9.0 Added for backward compat with add-on's
20
+	 * @type null
21
+	 */
22
+	public $template_variation;
23
+
24
+	/**
25
+	 * @deprecated 4.9.0 Added for backward compat with add-on's
26
+	 * @type string
27
+	 */
28
+	public $content = '';
29
+
30
+
31
+	/**
32
+	 * @type EE_messenger $_messenger
33
+	 */
34
+	protected $_messenger = null;
35
+
36
+	/**
37
+	 * @type EE_message_type $_message_type
38
+	 */
39
+	protected $_message_type = null;
40
+
41
+
42
+	/**
43
+	 * @param array  $props_n_values
44
+	 * @param string $timezone
45
+	 * @param array  $date_formats incoming date formats in an array.  First value is the date_format, second is time
46
+	 *                             format.
47
+	 * @return EE_Message
48
+	 */
49
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
50
+	{
51
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__);
52
+		// if object doesn't exist, let's generate a unique token on instantiation so that its available even before saving to db.
53
+		if (! $has_object) {
54
+			EE_Registry::instance()->load_helper('URL');
55
+			$props_n_values['MSG_token'] = EEH_URL::generate_unique_token();
56
+		}
57
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
58
+	}
59
+
60
+
61
+	/**
62
+	 * @param array  $props_n_values
63
+	 * @param string $timezone
64
+	 * @return EE_Message
65
+	 */
66
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
67
+	{
68
+		return new self($props_n_values, true, $timezone);
69
+	}
70
+
71
+
72
+	/**
73
+	 * Gets MSG_token
74
+	 *
75
+	 * @return int
76
+	 */
77
+	public function MSG_token()
78
+	{
79
+		return $this->get('MSG_token');
80
+	}
81
+
82
+
83
+	/**
84
+	 * Sets MSG_token
85
+	 *
86
+	 * @param int $MSG_token
87
+	 */
88
+	public function set_MSG_token($MSG_token)
89
+	{
90
+		$this->set('MSG_token', $MSG_token);
91
+	}
92
+
93
+
94
+	/**
95
+	 * Gets GRP_ID
96
+	 *
97
+	 * @return int
98
+	 */
99
+	public function GRP_ID()
100
+	{
101
+		return $this->get('GRP_ID');
102
+	}
103
+
104
+
105
+	/**
106
+	 * Sets GRP_ID
107
+	 *
108
+	 * @param int $GRP_ID
109
+	 */
110
+	public function set_GRP_ID($GRP_ID)
111
+	{
112
+		$this->set('GRP_ID', $GRP_ID);
113
+	}
114
+
115
+
116
+	/**
117
+	 * Gets TXN_ID
118
+	 *
119
+	 * @return int
120
+	 */
121
+	public function TXN_ID()
122
+	{
123
+		return $this->get('TXN_ID');
124
+	}
125
+
126
+
127
+	/**
128
+	 * Sets TXN_ID
129
+	 *
130
+	 * @param int $TXN_ID
131
+	 */
132
+	public function set_TXN_ID($TXN_ID)
133
+	{
134
+		$this->set('TXN_ID', $TXN_ID);
135
+	}
136
+
137
+
138
+	/**
139
+	 * Gets messenger
140
+	 *
141
+	 * @return string
142
+	 */
143
+	public function messenger()
144
+	{
145
+		return $this->get('MSG_messenger');
146
+	}
147
+
148
+
149
+	/**
150
+	 * Sets messenger
151
+	 *
152
+	 * @param string $messenger
153
+	 */
154
+	public function set_messenger($messenger)
155
+	{
156
+		$this->set('MSG_messenger', $messenger);
157
+	}
158
+
159
+
160
+	/**
161
+	 * Returns corresponding messenger object for the set messenger on this message
162
+	 *
163
+	 * @return EE_messenger | null
164
+	 */
165
+	public function messenger_object()
166
+	{
167
+		return $this->_messenger;
168
+	}
169
+
170
+
171
+	/**
172
+	 * Sets messenger
173
+	 *
174
+	 * @param EE_messenger $messenger
175
+	 */
176
+	public function set_messenger_object(EE_messenger $messenger)
177
+	{
178
+		$this->_messenger = $messenger;
179
+	}
180
+
181
+
182
+	/**
183
+	 * validates messenger
184
+	 *
185
+	 * @param bool $throw_exceptions
186
+	 * @return bool
187
+	 * @throws \EE_Error
188
+	 */
189
+	public function valid_messenger($throw_exceptions = false)
190
+	{
191
+		if ($this->_messenger instanceof EE_messenger) {
192
+			return true;
193
+		}
194
+		if ($throw_exceptions) {
195
+			throw new EE_Error(
196
+				sprintf(
197
+					esc_html__(
198
+						'The "%1$s" messenger set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
199
+						'event_espresso'
200
+					),
201
+					$this->messenger()
202
+				)
203
+			);
204
+		}
205
+		return false;
206
+	}
207
+
208
+
209
+	/**
210
+	 * This returns the set localized label for the messenger on this message.
211
+	 * Note, if unable to retrieve the EE_messenger object then will just return the messenger slug saved
212
+	 * with this message.
213
+	 *
214
+	 * @param bool $plural whether to return the plural label or not.
215
+	 * @return string
216
+	 */
217
+	public function messenger_label($plural = false)
218
+	{
219
+		$label_type = $plural ? 'plural' : 'singular';
220
+		$messenger  = $this->messenger_object();
221
+		return $messenger instanceof EE_messenger ? $messenger->label[ $label_type ] : $this->messenger();
222
+	}
223
+
224
+
225
+	/**
226
+	 * Gets message_type
227
+	 *
228
+	 * @return string
229
+	 */
230
+	public function message_type()
231
+	{
232
+		return $this->get('MSG_message_type');
233
+	}
234
+
235
+
236
+	/**
237
+	 * Sets message_type
238
+	 *
239
+	 * @param string $message_type
240
+	 */
241
+	public function set_message_type($message_type)
242
+	{
243
+		$this->set('MSG_message_type', $message_type);
244
+	}
245
+
246
+
247
+	/**
248
+	 * Returns the message type object for the set message type on this message
249
+	 *
250
+	 * @return EE_message_type | null
251
+	 */
252
+	public function message_type_object()
253
+	{
254
+		return $this->_message_type;
255
+	}
256
+
257
+
258
+	/**
259
+	 * Sets message_type
260
+	 *
261
+	 * @param EE_message_type $message_type
262
+	 * @param bool            $set_priority   This indicates whether to set the priority to whatever the priority is on
263
+	 *                                        the message type or not.
264
+	 */
265
+	public function set_message_type_object(EE_message_type $message_type, $set_priority = false)
266
+	{
267
+		$this->_message_type = $message_type;
268
+		if ($set_priority) {
269
+			$this->set_priority($this->_message_type->get_priority());
270
+		}
271
+	}
272
+
273
+
274
+	/**
275
+	 * validates message_type
276
+	 *
277
+	 * @param bool $throw_exceptions
278
+	 * @return bool
279
+	 * @throws \EE_Error
280
+	 */
281
+	public function valid_message_type($throw_exceptions = false)
282
+	{
283
+		if ($this->_message_type instanceof EE_message_type) {
284
+			return true;
285
+		}
286
+		if ($throw_exceptions) {
287
+			throw new EE_Error(
288
+				sprintf(
289
+					esc_html__(
290
+						'The %1$s message type set for this message is missing or invalid. Please double-check the spelling and verify that the correct files exist.',
291
+						'event_espresso'
292
+					),
293
+					$this->message_type()
294
+				)
295
+			);
296
+		}
297
+		return false;
298
+	}
299
+
300
+
301
+	/**
302
+	 * validates messenger and message_type (that they are valid EE_messenger and EE_message_type objects).
303
+	 *
304
+	 * @param bool $throw_exceptions
305
+	 * @return bool
306
+	 * @throws \EE_Error
307
+	 */
308
+	public function is_valid($throw_exceptions = false)
309
+	{
310
+		if ($this->valid_messenger($throw_exceptions) && $this->valid_message_type($throw_exceptions)) {
311
+			return true;
312
+		}
313
+		return false;
314
+	}
315
+
316
+
317
+	/**
318
+	 * This validates whether the internal messenger and message type objects are valid for sending.
319
+	 * Three checks are done:
320
+	 * 1. There is a valid messenger object.
321
+	 * 2. There is a valid message type object.
322
+	 * 3. The message type object is active for the messenger.
323
+	 *
324
+	 * @param bool $throw_exceptions
325
+	 * @return bool
326
+	 * @throws EE_Error  But only if $throw_exceptions is set to true.
327
+	 */
328
+	public function is_valid_for_sending_or_generation($throw_exceptions = false)
329
+	{
330
+		$valid = false;
331
+		if ($this->is_valid($throw_exceptions)) {
332
+			/** @var EE_Message_Resource_Manager $message_resource_manager */
333
+			$message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
334
+			$valid                    = $message_resource_manager->is_message_type_active_for_messenger(
335
+				$this->messenger(),
336
+				$this->message_type()
337
+			);
338
+			if (! $valid && $throw_exceptions) {
339
+				throw new EE_Error(
340
+					sprintf(
341
+						esc_html__(
342
+							'The %1$s message type is not a valid message type for the %2$s messenger so it will not be sent.',
343
+							'event_espresso'
344
+						),
345
+						$this->message_type(),
346
+						$this->messenger()
347
+					)
348
+				);
349
+			}
350
+		}
351
+		return $valid;
352
+	}
353
+
354
+
355
+	/**
356
+	 * This returns the set localized label for the message type on this message.
357
+	 * Note, if unable to retrieve the EE_message_type object then will just return the message type slug saved
358
+	 * with this message.
359
+	 *
360
+	 * @param bool $plural whether to return the plural label or not.
361
+	 * @return string
362
+	 */
363
+	public function message_type_label($plural = false)
364
+	{
365
+		$label_type   = $plural ? 'plural' : 'singular';
366
+		$message_type = $this->message_type_object();
367
+		return $message_type instanceof EE_message_type
368
+			? $message_type->label[ $label_type ]
369
+			: str_replace(
370
+				'_',
371
+				' ',
372
+				$this->message_type()
373
+			);
374
+	}
375
+
376
+
377
+	/**
378
+	 * Gets context
379
+	 *
380
+	 * @return string
381
+	 */
382
+	public function context()
383
+	{
384
+		return $this->get('MSG_context');
385
+	}
386
+
387
+
388
+	/**
389
+	 * This returns the corresponding localized label for the given context slug, if possible from installed message
390
+	 * types. Otherwise, this will just return the set context slug on this object.
391
+	 *
392
+	 * @return string
393
+	 */
394
+	public function context_label()
395
+	{
396
+		/** @type EE_Message_Resource_Manager $message_resource_manager */
397
+		$message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
398
+		$contexts                 = $message_resource_manager->get_all_contexts();
399
+		return isset($contexts[ $this->context() ]) ? $contexts[ $this->context() ] : $this->context();
400
+	}
401
+
402
+
403
+	/**
404
+	 * Sets context
405
+	 *
406
+	 * @param string $context
407
+	 */
408
+	public function set_context($context)
409
+	{
410
+		$this->set('MSG_context', $context);
411
+	}
412
+
413
+
414
+	/**
415
+	 * Gets recipient_ID
416
+	 *
417
+	 * @return int
418
+	 */
419
+	public function recipient_ID()
420
+	{
421
+		return $this->get('MSG_recipient_ID');
422
+	}
423
+
424
+
425
+	/**
426
+	 * Sets recipient_ID
427
+	 *
428
+	 * @param string $recipient_ID
429
+	 */
430
+	public function set_recipient_ID($recipient_ID)
431
+	{
432
+		$this->set('MSG_recipient_ID', $recipient_ID);
433
+	}
434
+
435
+
436
+	/**
437
+	 * Gets recipient_type
438
+	 *
439
+	 * @return string
440
+	 */
441
+	public function recipient_type()
442
+	{
443
+		return $this->get('MSG_recipient_type');
444
+	}
445
+
446
+
447
+	/**
448
+	 * Return the related object matching the recipient type and ID.
449
+	 *
450
+	 * @return EE_Base_Class | null
451
+	 */
452
+	public function recipient_object()
453
+	{
454
+		if (! $this->recipient_type() || ! $this->recipient_ID()) {
455
+			return null;
456
+		}
457
+
458
+		return $this->get_first_related($this->recipient_type());
459
+	}
460
+
461
+
462
+	/**
463
+	 * Sets recipient_type
464
+	 *
465
+	 * @param string $recipient_type
466
+	 */
467
+	public function set_recipient_type($recipient_type)
468
+	{
469
+		$this->set('MSG_recipient_type', $recipient_type);
470
+	}
471
+
472
+
473
+	/**
474
+	 * Gets content
475
+	 *
476
+	 * @return string
477
+	 */
478
+	public function content()
479
+	{
480
+		return $this->get('MSG_content');
481
+	}
482
+
483
+
484
+	/**
485
+	 * Sets content
486
+	 *
487
+	 * @param string $content
488
+	 */
489
+	public function set_content($content)
490
+	{
491
+		$this->set('MSG_content', $content);
492
+	}
493
+
494
+
495
+	/**
496
+	 * Gets subject
497
+	 *
498
+	 * @return string
499
+	 */
500
+	public function subject()
501
+	{
502
+		return $this->get('MSG_subject');
503
+	}
504
+
505
+
506
+	/**
507
+	 * Sets subject
508
+	 *
509
+	 * @param string $subject
510
+	 */
511
+	public function set_subject($subject)
512
+	{
513
+		$this->set('MSG_subject', $subject);
514
+	}
515
+
516
+
517
+	/**
518
+	 * Gets to
519
+	 *
520
+	 * @return string
521
+	 */
522
+	public function to()
523
+	{
524
+		$to = $this->get('MSG_to');
525
+		return empty($to) ? esc_html__('No recipient', 'event_espresso') : $to;
526
+	}
527
+
528
+
529
+	/**
530
+	 * Sets to
531
+	 *
532
+	 * @param string $to
533
+	 */
534
+	public function set_to($to)
535
+	{
536
+		$this->set('MSG_to', $to);
537
+	}
538
+
539
+
540
+	/**
541
+	 * Gets from
542
+	 *
543
+	 * @return string
544
+	 */
545
+	public function from()
546
+	{
547
+		return $this->get('MSG_from');
548
+	}
549
+
550
+
551
+	/**
552
+	 * Sets from
553
+	 *
554
+	 * @param string $from
555
+	 */
556
+	public function set_from($from)
557
+	{
558
+		$this->set('MSG_from', $from);
559
+	}
560
+
561
+
562
+	/**
563
+	 * Gets priority
564
+	 *
565
+	 * @return int
566
+	 */
567
+	public function priority()
568
+	{
569
+		return $this->get('MSG_priority');
570
+	}
571
+
572
+
573
+	/**
574
+	 * Sets priority
575
+	 * Note.  Send Now Messengers always override any priority that may be set on a Message.  So
576
+	 * this method calls the send_now method to verify that.
577
+	 *
578
+	 * @param int $priority
579
+	 */
580
+	public function set_priority($priority)
581
+	{
582
+		$priority = $this->send_now() ? EEM_Message::priority_high : $priority;
583
+		parent::set('MSG_priority', $priority);
584
+	}
585
+
586
+
587
+	/**
588
+	 * Overrides parent::set method so we can capture any sets for priority.
589
+	 *
590
+	 * @param string $field_name
591
+	 * @param mixed  $field_value
592
+	 * @param bool   $use_default
593
+	 * @throws EE_Error
594
+	 * @see parent::set() for phpdocs
595
+	 */
596
+	public function set($field_name, $field_value, $use_default = false)
597
+	{
598
+		if ($field_name === 'MSG_priority') {
599
+			$this->set_priority($field_value);
600
+		}
601
+		parent::set($field_name, $field_value, $use_default);
602
+	}
603
+
604
+
605
+	/**
606
+	 * @return bool
607
+	 * @throws \EE_Error
608
+	 */
609
+	public function send_now()
610
+	{
611
+		$send_now = $this->valid_messenger() && $this->messenger_object()->send_now() ? EEM_Message::priority_high
612
+			: $this->priority();
613
+		return $send_now === EEM_Message::priority_high ? true : false;
614
+	}
615
+
616
+
617
+	/**
618
+	 * Gets STS_ID
619
+	 *
620
+	 * @return string
621
+	 */
622
+	public function STS_ID()
623
+	{
624
+		return $this->get('STS_ID');
625
+	}
626
+
627
+
628
+	/**
629
+	 * Sets STS_ID
630
+	 *
631
+	 * @param string $STS_ID
632
+	 */
633
+	public function set_STS_ID($STS_ID)
634
+	{
635
+		$this->set('STS_ID', $STS_ID);
636
+	}
637
+
638
+
639
+	/**
640
+	 * Gets created
641
+	 *
642
+	 * @return string
643
+	 */
644
+	public function created()
645
+	{
646
+		return $this->get('MSG_created');
647
+	}
648
+
649
+
650
+	/**
651
+	 * Sets created
652
+	 *
653
+	 * @param string $created
654
+	 */
655
+	public function set_created($created)
656
+	{
657
+		$this->set('MSG_created', $created);
658
+	}
659
+
660
+
661
+	/**
662
+	 * Gets modified
663
+	 *
664
+	 * @return string
665
+	 */
666
+	public function modified()
667
+	{
668
+		return $this->get('MSG_modified');
669
+	}
670
+
671
+
672
+	/**
673
+	 * Sets modified
674
+	 *
675
+	 * @param string $modified
676
+	 */
677
+	public function set_modified($modified)
678
+	{
679
+		$this->set('MSG_modified', $modified);
680
+	}
681
+
682
+
683
+	/**
684
+	 * Sets generation data for this message.
685
+	 *
686
+	 * @param mixed $data
687
+	 */
688
+	public function set_generation_data($data)
689
+	{
690
+		$this->set_field_or_extra_meta('MSG_generation_data', $data);
691
+	}
692
+
693
+
694
+	/**
695
+	 * Returns any set generation data for this message.
696
+	 *
697
+	 * @return mixed|null
698
+	 */
699
+	public function get_generation_data()
700
+	{
701
+		return $this->get_field_or_extra_meta('MSG_generation_data');
702
+	}
703
+
704
+
705
+	/**
706
+	 * Gets any error message.
707
+	 *
708
+	 * @return mixed|null
709
+	 */
710
+	public function error_message()
711
+	{
712
+		return $this->get_field_or_extra_meta('MSG_error');
713
+	}
714
+
715
+
716
+	/**
717
+	 * Sets an error message.
718
+	 *
719
+	 * @param $message
720
+	 * @return bool|int
721
+	 */
722
+	public function set_error_message($message)
723
+	{
724
+		return $this->set_field_or_extra_meta('MSG_error', $message);
725
+	}
726
+
727
+
728
+	/**
729
+	 * This retrieves the associated template pack with this message.
730
+	 *
731
+	 * @return EE_Messages_Template_Pack | null
732
+	 */
733
+	public function get_template_pack()
734
+	{
735
+		/**
736
+		 * This is deprecated functionality that will be removed eventually but included here now for backward compat.
737
+		 */
738
+		if (! empty($this->template_pack)) {
739
+			return $this->template_pack;
740
+		}
741
+		/** @type EE_Message_Template_Group $grp */
742
+		$grp = $this->get_first_related('Message_Template_Group');
743
+		// if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
744
+		if (! $grp instanceof EE_Message_Template_Group) {
745
+			$grp = EEM_Message_Template_Group::instance()->get_one(
746
+				[
747
+					[
748
+						'MTP_messenger'    => $this->messenger(),
749
+						'MTP_message_type' => $this->message_type(),
750
+						'MTP_is_global'    => true,
751
+					],
752
+				]
753
+			);
754
+		}
755
+
756
+		return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack() : null;
757
+	}
758
+
759
+
760
+	/**
761
+	 * Retrieves the variation used for generating this message.
762
+	 *
763
+	 * @return string
764
+	 */
765
+	public function get_template_pack_variation()
766
+	{
767
+		/**
768
+		 * This is deprecated functionality that will be removed eventually but included here now for backward compat.
769
+		 */
770
+		if (! empty($this->template_variation)) {
771
+			return $this->template_variation;
772
+		}
773
+
774
+		/** @type EE_Message_Template_Group $grp */
775
+		$grp = $this->get_first_related('Message_Template_Group');
776
+
777
+		// if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
778
+		if (! $grp instanceof EE_Message_Template_Group) {
779
+			$grp = EEM_Message_Template_Group::instance()->get_one(
780
+				[
781
+					[
782
+						'MTP_messenger'    => $this->messenger(),
783
+						'MTP_message_type' => $this->message_type(),
784
+						'MTP_is_global'    => true,
785
+					],
786
+				]
787
+			);
788
+		}
789
+
790
+		return $grp instanceof EE_Message_Template_Group ? $grp->get_template_pack_variation() : '';
791
+	}
792
+
793
+
794
+	/**
795
+	 * Return the link to the admin details for the object.
796
+	 *
797
+	 * @return string
798
+	 */
799
+	public function get_admin_details_link()
800
+	{
801
+		EE_Registry::instance()->load_helper('URL');
802
+		EE_Registry::instance()->load_helper('MSG_Template');
803
+		switch ($this->STS_ID()) {
804
+			case EEM_Message::status_failed:
805
+			case EEM_Message::status_debug_only:
806
+				return EEH_MSG_Template::generate_error_display_trigger($this);
807
+				break;
808
+
809
+			case EEM_Message::status_sent:
810
+				return EEH_MSG_Template::generate_browser_trigger($this);
811
+				break;
812
+
813
+			default:
814
+				return '';
815
+		}
816
+	}
817
+
818
+
819
+	/**
820
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
821
+	 *
822
+	 * @return string
823
+	 */
824
+	public function get_admin_edit_link()
825
+	{
826
+		return $this->get_admin_details_link();
827
+	}
828
+
829
+
830
+	/**
831
+	 * Returns the link to a settings page for the object.
832
+	 *
833
+	 * @return string
834
+	 */
835
+	public function get_admin_settings_link()
836
+	{
837
+		EE_Registry::instance()->load_helper('URL');
838
+		return EEH_URL::add_query_args_and_nonce(
839
+			[
840
+				'page'   => 'espresso_messages',
841
+				'action' => 'settings',
842
+			],
843
+			admin_url('admin.php')
844
+		);
845
+	}
846
+
847
+
848
+	/**
849
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
850
+	 *
851
+	 * @return string
852
+	 */
853
+	public function get_admin_overview_link()
854
+	{
855
+		EE_Registry::instance()->load_helper('URL');
856
+		return EEH_URL::add_query_args_and_nonce(
857
+			[
858
+				'page'   => 'espresso_messages',
859
+				'action' => 'default',
860
+			],
861
+			admin_url('admin.php')
862
+		);
863
+	}
864
+
865
+
866
+	/**
867
+	 * This sets the EEM_Message::status_messenger_executing class on the message and the appropriate error message for
868
+	 * it.
869
+	 * Note this also SAVES the current message object to the db because it adds an error message to accompany the
870
+	 * status.
871
+	 */
872
+	public function set_messenger_is_executing()
873
+	{
874
+		$this->set_STS_ID(EEM_Message::status_messenger_executing);
875
+		if (EEM_Message::debug()) {
876
+			$this->set_error_message(
877
+				esc_html__(
878
+					'A message with this status indicates that there was a problem that occurred while the message was being
879 879
                     processed by the messenger.  It is still possible that the message was sent successfully, but at some
880 880
                     point during the processing there was a failure.  This usually is indicative of a timeout issue with PHP 
881 881
                     or memory limits being reached.  If you see this repeatedly you may want to consider upgrading the memory 
882 882
                     available to PHP on your server.',
883
-                    'event_espresso'
884
-                )
885
-            );
886
-        }
887
-    }
883
+					'event_espresso'
884
+				)
885
+			);
886
+		}
887
+	}
888 888
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -50,7 +50,7 @@  discard block
 block discarded – undo
50 50
     {
51 51
         $has_object = parent::_check_for_object($props_n_values, __CLASS__);
52 52
         // if object doesn't exist, let's generate a unique token on instantiation so that its available even before saving to db.
53
-        if (! $has_object) {
53
+        if ( ! $has_object) {
54 54
             EE_Registry::instance()->load_helper('URL');
55 55
             $props_n_values['MSG_token'] = EEH_URL::generate_unique_token();
56 56
         }
@@ -218,7 +218,7 @@  discard block
 block discarded – undo
218 218
     {
219 219
         $label_type = $plural ? 'plural' : 'singular';
220 220
         $messenger  = $this->messenger_object();
221
-        return $messenger instanceof EE_messenger ? $messenger->label[ $label_type ] : $this->messenger();
221
+        return $messenger instanceof EE_messenger ? $messenger->label[$label_type] : $this->messenger();
222 222
     }
223 223
 
224 224
 
@@ -335,7 +335,7 @@  discard block
 block discarded – undo
335 335
                 $this->messenger(),
336 336
                 $this->message_type()
337 337
             );
338
-            if (! $valid && $throw_exceptions) {
338
+            if ( ! $valid && $throw_exceptions) {
339 339
                 throw new EE_Error(
340 340
                     sprintf(
341 341
                         esc_html__(
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
         $label_type   = $plural ? 'plural' : 'singular';
366 366
         $message_type = $this->message_type_object();
367 367
         return $message_type instanceof EE_message_type
368
-            ? $message_type->label[ $label_type ]
368
+            ? $message_type->label[$label_type]
369 369
             : str_replace(
370 370
                 '_',
371 371
                 ' ',
@@ -396,7 +396,7 @@  discard block
 block discarded – undo
396 396
         /** @type EE_Message_Resource_Manager $message_resource_manager */
397 397
         $message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
398 398
         $contexts                 = $message_resource_manager->get_all_contexts();
399
-        return isset($contexts[ $this->context() ]) ? $contexts[ $this->context() ] : $this->context();
399
+        return isset($contexts[$this->context()]) ? $contexts[$this->context()] : $this->context();
400 400
     }
401 401
 
402 402
 
@@ -451,7 +451,7 @@  discard block
 block discarded – undo
451 451
      */
452 452
     public function recipient_object()
453 453
     {
454
-        if (! $this->recipient_type() || ! $this->recipient_ID()) {
454
+        if ( ! $this->recipient_type() || ! $this->recipient_ID()) {
455 455
             return null;
456 456
         }
457 457
 
@@ -735,13 +735,13 @@  discard block
 block discarded – undo
735 735
         /**
736 736
          * This is deprecated functionality that will be removed eventually but included here now for backward compat.
737 737
          */
738
-        if (! empty($this->template_pack)) {
738
+        if ( ! empty($this->template_pack)) {
739 739
             return $this->template_pack;
740 740
         }
741 741
         /** @type EE_Message_Template_Group $grp */
742 742
         $grp = $this->get_first_related('Message_Template_Group');
743 743
         // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
744
-        if (! $grp instanceof EE_Message_Template_Group) {
744
+        if ( ! $grp instanceof EE_Message_Template_Group) {
745 745
             $grp = EEM_Message_Template_Group::instance()->get_one(
746 746
                 [
747 747
                     [
@@ -767,7 +767,7 @@  discard block
 block discarded – undo
767 767
         /**
768 768
          * This is deprecated functionality that will be removed eventually but included here now for backward compat.
769 769
          */
770
-        if (! empty($this->template_variation)) {
770
+        if ( ! empty($this->template_variation)) {
771 771
             return $this->template_variation;
772 772
         }
773 773
 
@@ -775,7 +775,7 @@  discard block
 block discarded – undo
775 775
         $grp = $this->get_first_related('Message_Template_Group');
776 776
 
777 777
         // if no group then let's try to get the first related group by internal messenger and message type (will use global grp).
778
-        if (! $grp instanceof EE_Message_Template_Group) {
778
+        if ( ! $grp instanceof EE_Message_Template_Group) {
779 779
             $grp = EEM_Message_Template_Group::instance()->get_one(
780 780
                 [
781 781
                     [
Please login to merge, or discard this patch.
core/db_classes/EE_Currency.class.php 1 patch
Indentation   +195 added lines, -195 removed lines patch added patch discarded remove patch
@@ -10,199 +10,199 @@
 block discarded – undo
10 10
  */
11 11
 class EE_Currency extends EE_Base_Class
12 12
 {
13
-    /** Currency COde @var CUR_code */
14
-    protected $_CUR_code = null;
15
-
16
-    /** Currency Name Singular @var CUR_single */
17
-    protected $_CUR_single = null;
18
-
19
-    /** Currency Name Plural @var CUR_plural */
20
-    protected $_CUR_plural = null;
21
-
22
-    /** Currency Sign @var CUR_sign */
23
-    protected $_CUR_sign = null;
24
-
25
-    /** Currency Decimal Places @var CUR_dec_plc */
26
-    protected $_CUR_dec_plc = null;
27
-
28
-    /** Active? @var CUR_active */
29
-    protected $_CUR_active = null;
30
-
31
-    protected $_Payment_Method;
32
-
33
-
34
-    /**
35
-     * @param array  $props_n_values          incoming values
36
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
37
-     *                                        used.)
38
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
39
-     *                                        date_format and the second value is the time format
40
-     * @return EE_Attendee
41
-     */
42
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
43
-    {
44
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
45
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
46
-    }
47
-
48
-
49
-    /**
50
-     * @param array  $props_n_values  incoming values from the database
51
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
52
-     *                                the website will be used.
53
-     * @return EE_Attendee
54
-     */
55
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
56
-    {
57
-        return new self($props_n_values, true, $timezone);
58
-    }
59
-
60
-
61
-    /**
62
-     * Gets code
63
-     *
64
-     * @return string
65
-     */
66
-    public function code()
67
-    {
68
-        return $this->get('CUR_code');
69
-    }
70
-
71
-
72
-    /**
73
-     * Sets code
74
-     *
75
-     * @param string $code
76
-     * @return boolean
77
-     */
78
-    public function set_code($code)
79
-    {
80
-        return $this->set('CUR_code', $code);
81
-    }
82
-
83
-
84
-    /**
85
-     * Gets active
86
-     *
87
-     * @return boolean
88
-     */
89
-    public function active()
90
-    {
91
-        return $this->get('CUR_active');
92
-    }
93
-
94
-
95
-    /**
96
-     * Sets active
97
-     *
98
-     * @param boolean $active
99
-     * @return boolean
100
-     */
101
-    public function set_active($active)
102
-    {
103
-        return $this->set('CUR_active', $active);
104
-    }
105
-
106
-
107
-    /**
108
-     * Gets dec_plc
109
-     *
110
-     * @return int
111
-     */
112
-    public function dec_plc()
113
-    {
114
-        return $this->get('CUR_dec_plc');
115
-    }
116
-
117
-
118
-    /**
119
-     * Sets dec_plc
120
-     *
121
-     * @param int $dec_plc
122
-     * @return boolean
123
-     */
124
-    public function set_dec_plc($dec_plc)
125
-    {
126
-        return $this->set('CUR_dec_plc', $dec_plc);
127
-    }
128
-
129
-
130
-    /**
131
-     * Gets plural
132
-     *
133
-     * @return string
134
-     */
135
-    public function plural_name()
136
-    {
137
-        return $this->get('CUR_plural');
138
-    }
139
-
140
-
141
-    /**
142
-     * Sets plural
143
-     *
144
-     * @param string $plural
145
-     * @return boolean
146
-     */
147
-    public function set_plural_name($plural)
148
-    {
149
-        return $this->set('CUR_plural', $plural);
150
-    }
151
-
152
-
153
-    /**
154
-     * Gets sign
155
-     *
156
-     * @return string
157
-     */
158
-    public function sign()
159
-    {
160
-        return $this->get('CUR_sign');
161
-    }
162
-
163
-
164
-    /**
165
-     * Sets sign
166
-     *
167
-     * @param string $sign
168
-     * @return boolean
169
-     */
170
-    public function set_sign($sign)
171
-    {
172
-        return $this->set('CUR_sign', $sign);
173
-    }
174
-
175
-
176
-    /**
177
-     * Gets single
178
-     *
179
-     * @return string
180
-     */
181
-    public function singular_name()
182
-    {
183
-        return $this->get('CUR_single');
184
-    }
185
-
186
-
187
-    /**
188
-     * Sets single
189
-     *
190
-     * @param string $single
191
-     * @return boolean
192
-     */
193
-    public function set_singular_name($single)
194
-    {
195
-        return $this->set('CUR_single', $single);
196
-    }
197
-
198
-
199
-    /**
200
-     * Gets a prettier name
201
-     *
202
-     * @return string
203
-     */
204
-    public function name()
205
-    {
206
-        return sprintf(esc_html__("%s (%s)", "event_espresso"), $this->code(), $this->plural_name());
207
-    }
13
+	/** Currency COde @var CUR_code */
14
+	protected $_CUR_code = null;
15
+
16
+	/** Currency Name Singular @var CUR_single */
17
+	protected $_CUR_single = null;
18
+
19
+	/** Currency Name Plural @var CUR_plural */
20
+	protected $_CUR_plural = null;
21
+
22
+	/** Currency Sign @var CUR_sign */
23
+	protected $_CUR_sign = null;
24
+
25
+	/** Currency Decimal Places @var CUR_dec_plc */
26
+	protected $_CUR_dec_plc = null;
27
+
28
+	/** Active? @var CUR_active */
29
+	protected $_CUR_active = null;
30
+
31
+	protected $_Payment_Method;
32
+
33
+
34
+	/**
35
+	 * @param array  $props_n_values          incoming values
36
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
37
+	 *                                        used.)
38
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
39
+	 *                                        date_format and the second value is the time format
40
+	 * @return EE_Attendee
41
+	 */
42
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
43
+	{
44
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
45
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
46
+	}
47
+
48
+
49
+	/**
50
+	 * @param array  $props_n_values  incoming values from the database
51
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
52
+	 *                                the website will be used.
53
+	 * @return EE_Attendee
54
+	 */
55
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
56
+	{
57
+		return new self($props_n_values, true, $timezone);
58
+	}
59
+
60
+
61
+	/**
62
+	 * Gets code
63
+	 *
64
+	 * @return string
65
+	 */
66
+	public function code()
67
+	{
68
+		return $this->get('CUR_code');
69
+	}
70
+
71
+
72
+	/**
73
+	 * Sets code
74
+	 *
75
+	 * @param string $code
76
+	 * @return boolean
77
+	 */
78
+	public function set_code($code)
79
+	{
80
+		return $this->set('CUR_code', $code);
81
+	}
82
+
83
+
84
+	/**
85
+	 * Gets active
86
+	 *
87
+	 * @return boolean
88
+	 */
89
+	public function active()
90
+	{
91
+		return $this->get('CUR_active');
92
+	}
93
+
94
+
95
+	/**
96
+	 * Sets active
97
+	 *
98
+	 * @param boolean $active
99
+	 * @return boolean
100
+	 */
101
+	public function set_active($active)
102
+	{
103
+		return $this->set('CUR_active', $active);
104
+	}
105
+
106
+
107
+	/**
108
+	 * Gets dec_plc
109
+	 *
110
+	 * @return int
111
+	 */
112
+	public function dec_plc()
113
+	{
114
+		return $this->get('CUR_dec_plc');
115
+	}
116
+
117
+
118
+	/**
119
+	 * Sets dec_plc
120
+	 *
121
+	 * @param int $dec_plc
122
+	 * @return boolean
123
+	 */
124
+	public function set_dec_plc($dec_plc)
125
+	{
126
+		return $this->set('CUR_dec_plc', $dec_plc);
127
+	}
128
+
129
+
130
+	/**
131
+	 * Gets plural
132
+	 *
133
+	 * @return string
134
+	 */
135
+	public function plural_name()
136
+	{
137
+		return $this->get('CUR_plural');
138
+	}
139
+
140
+
141
+	/**
142
+	 * Sets plural
143
+	 *
144
+	 * @param string $plural
145
+	 * @return boolean
146
+	 */
147
+	public function set_plural_name($plural)
148
+	{
149
+		return $this->set('CUR_plural', $plural);
150
+	}
151
+
152
+
153
+	/**
154
+	 * Gets sign
155
+	 *
156
+	 * @return string
157
+	 */
158
+	public function sign()
159
+	{
160
+		return $this->get('CUR_sign');
161
+	}
162
+
163
+
164
+	/**
165
+	 * Sets sign
166
+	 *
167
+	 * @param string $sign
168
+	 * @return boolean
169
+	 */
170
+	public function set_sign($sign)
171
+	{
172
+		return $this->set('CUR_sign', $sign);
173
+	}
174
+
175
+
176
+	/**
177
+	 * Gets single
178
+	 *
179
+	 * @return string
180
+	 */
181
+	public function singular_name()
182
+	{
183
+		return $this->get('CUR_single');
184
+	}
185
+
186
+
187
+	/**
188
+	 * Sets single
189
+	 *
190
+	 * @param string $single
191
+	 * @return boolean
192
+	 */
193
+	public function set_singular_name($single)
194
+	{
195
+		return $this->set('CUR_single', $single);
196
+	}
197
+
198
+
199
+	/**
200
+	 * Gets a prettier name
201
+	 *
202
+	 * @return string
203
+	 */
204
+	public function name()
205
+	{
206
+		return sprintf(esc_html__("%s (%s)", "event_espresso"), $this->code(), $this->plural_name());
207
+	}
208 208
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Event_Message_Template.class.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -11,25 +11,25 @@
 block discarded – undo
11 11
  */
12 12
 class EE_Event_Message_Template extends EE_Base_Class
13 13
 {
14
-    /**
15
-     * @param array $props_n_values
16
-     * @param null  $timezone
17
-     * @return EE_Event_Message_Template|mixed
18
-     */
19
-    public static function new_instance($props_n_values = [], $timezone = '')
20
-    {
21
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
22
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone);
23
-    }
14
+	/**
15
+	 * @param array $props_n_values
16
+	 * @param null  $timezone
17
+	 * @return EE_Event_Message_Template|mixed
18
+	 */
19
+	public static function new_instance($props_n_values = [], $timezone = '')
20
+	{
21
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
22
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone);
23
+	}
24 24
 
25 25
 
26
-    /**
27
-     * @param array $props_n_values
28
-     * @param null  $timezone
29
-     * @return EE_Event_Message_Template
30
-     */
31
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
32
-    {
33
-        return new self($props_n_values, true, $timezone);
34
-    }
26
+	/**
27
+	 * @param array $props_n_values
28
+	 * @param null  $timezone
29
+	 * @return EE_Event_Message_Template
30
+	 */
31
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
32
+	{
33
+		return new self($props_n_values, true, $timezone);
34
+	}
35 35
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 2 patches
Indentation   +1728 added lines, -1728 removed lines patch added patch discarded remove patch
@@ -13,1732 +13,1732 @@
 block discarded – undo
13 13
  */
14 14
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
15 15
 {
16
-    /**
17
-     * The length of time in seconds that a lock is applied before being considered expired.
18
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
19
-     */
20
-    const LOCK_EXPIRATION = 2;
21
-
22
-    /**
23
-     * txn status upon initial construction.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $_old_txn_status;
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values          incoming values
32
-     * @param string $timezone                incoming timezone
33
-     *                                        (if not set the timezone set for the website will be used.)
34
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
-     *                                        date_format and the second value is the time format
36
-     * @return EE_Transaction
37
-     * @throws EE_Error
38
-     * @throws InvalidArgumentException
39
-     * @throws InvalidDataTypeException
40
-     * @throws InvalidInterfaceException
41
-     * @throws ReflectionException
42
-     */
43
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
44
-    {
45
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
-        $txn        = $has_object
47
-            ? $has_object
48
-            : new self($props_n_values, false, $timezone, $date_formats);
49
-        if (! $has_object) {
50
-            $txn->set_old_txn_status($txn->status_ID());
51
-        }
52
-        return $txn;
53
-    }
54
-
55
-
56
-    /**
57
-     * @param array  $props_n_values  incoming values from the database
58
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
-     *                                the website will be used.
60
-     * @return EE_Transaction
61
-     * @throws EE_Error
62
-     * @throws InvalidArgumentException
63
-     * @throws InvalidDataTypeException
64
-     * @throws InvalidInterfaceException
65
-     * @throws ReflectionException
66
-     */
67
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
68
-    {
69
-        $txn = new self($props_n_values, true, $timezone);
70
-        $txn->set_old_txn_status($txn->status_ID());
71
-        return $txn;
72
-    }
73
-
74
-
75
-    /**
76
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
-     * If that also fails, then an exception is thrown.
79
-     *
80
-     * @throws EE_Error
81
-     * @throws InvalidArgumentException
82
-     * @throws InvalidDataTypeException
83
-     * @throws InvalidInterfaceException
84
-     * @throws ReflectionException
85
-     */
86
-    public function lock()
87
-    {
88
-        // attempt to set lock, but if that fails...
89
-        if (! $this->add_extra_meta('lock', time(), true)) {
90
-            // then attempt to remove the lock in case it is expired
91
-            if ($this->_remove_expired_lock()) {
92
-                // if removal was successful, then try setting lock again
93
-                $this->lock();
94
-            } else {
95
-                // but if the lock can not be removed, then throw an exception
96
-                throw new EE_Error(
97
-                    sprintf(
98
-                        esc_html__(
99
-                            '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.',
100
-                            'event_espresso'
101
-                        ),
102
-                        $this->ID()
103
-                    )
104
-                );
105
-            }
106
-        }
107
-    }
108
-
109
-
110
-    /**
111
-     * removes transaction lock applied in EE_Transaction::lock()
112
-     *
113
-     * @return int
114
-     * @throws EE_Error
115
-     * @throws InvalidArgumentException
116
-     * @throws InvalidDataTypeException
117
-     * @throws InvalidInterfaceException
118
-     * @throws ReflectionException
119
-     */
120
-    public function unlock()
121
-    {
122
-        return $this->delete_extra_meta('lock');
123
-    }
124
-
125
-
126
-    /**
127
-     * Decides whether or not now is the right time to update the transaction.
128
-     * This is useful because we don't always know if it is safe to update the transaction
129
-     * and its related data. why?
130
-     * because it's possible that the transaction is being used in another
131
-     * request and could overwrite anything we save.
132
-     * So we want to only update the txn once we know that won't happen.
133
-     * We also check that the lock isn't expired, and remove it if it is
134
-     *
135
-     * @return boolean
136
-     * @throws EE_Error
137
-     * @throws InvalidArgumentException
138
-     * @throws InvalidDataTypeException
139
-     * @throws InvalidInterfaceException
140
-     * @throws ReflectionException
141
-     */
142
-    public function is_locked()
143
-    {
144
-        // if TXN is not locked, then return false immediately
145
-        if (! $this->_get_lock()) {
146
-            return false;
147
-        }
148
-        // if not, then let's try and remove the lock in case it's expired...
149
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
-        // and a positive number if the lock was removed (ie: number of locks deleted),
151
-        // so we need to return the opposite
152
-        return ! $this->_remove_expired_lock();
153
-    }
154
-
155
-
156
-    /**
157
-     * Gets the meta field indicating that this TXN is locked
158
-     *
159
-     * @return int
160
-     * @throws EE_Error
161
-     * @throws InvalidArgumentException
162
-     * @throws InvalidDataTypeException
163
-     * @throws InvalidInterfaceException
164
-     * @throws ReflectionException
165
-     */
166
-    protected function _get_lock()
167
-    {
168
-        return (int) $this->get_extra_meta('lock', true, 0);
169
-    }
170
-
171
-
172
-    /**
173
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
-     *
175
-     * @return int
176
-     * @throws EE_Error
177
-     * @throws InvalidArgumentException
178
-     * @throws InvalidDataTypeException
179
-     * @throws InvalidInterfaceException
180
-     * @throws ReflectionException
181
-     */
182
-    protected function _remove_expired_lock()
183
-    {
184
-        $locked = $this->_get_lock();
185
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
-            return $this->unlock();
187
-        }
188
-        return 0;
189
-    }
190
-
191
-
192
-    /**
193
-     * Set transaction total
194
-     *
195
-     * @param float $total total value of transaction
196
-     * @throws EE_Error
197
-     * @throws InvalidArgumentException
198
-     * @throws InvalidDataTypeException
199
-     * @throws InvalidInterfaceException
200
-     * @throws ReflectionException
201
-     */
202
-    public function set_total($total = 0.00)
203
-    {
204
-        $this->set('TXN_total', (float) $total);
205
-    }
206
-
207
-
208
-    /**
209
-     * Set Total Amount Paid to Date
210
-     *
211
-     * @param float $total_paid total amount paid to date (sum of all payments)
212
-     * @throws EE_Error
213
-     * @throws InvalidArgumentException
214
-     * @throws InvalidDataTypeException
215
-     * @throws InvalidInterfaceException
216
-     * @throws ReflectionException
217
-     */
218
-    public function set_paid($total_paid = 0.00)
219
-    {
220
-        $this->set('TXN_paid', (float) $total_paid);
221
-    }
222
-
223
-
224
-    /**
225
-     * Set transaction status
226
-     *
227
-     * @param string $status        whether the transaction is open, declined, accepted,
228
-     *                              or any number of custom values that can be set
229
-     * @throws EE_Error
230
-     * @throws InvalidArgumentException
231
-     * @throws InvalidDataTypeException
232
-     * @throws InvalidInterfaceException
233
-     * @throws ReflectionException
234
-     */
235
-    public function set_status($status = '')
236
-    {
237
-        $this->set('STS_ID', $status);
238
-    }
239
-
240
-
241
-    /**
242
-     * Set hash salt
243
-     *
244
-     * @param string $hash_salt required for some payment gateways
245
-     * @throws EE_Error
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidDataTypeException
248
-     * @throws InvalidInterfaceException
249
-     * @throws ReflectionException
250
-     */
251
-    public function set_hash_salt($hash_salt = '')
252
-    {
253
-        $this->set('TXN_hash_salt', $hash_salt);
254
-    }
255
-
256
-
257
-    /**
258
-     * Sets TXN_reg_steps array
259
-     *
260
-     * @param array $txn_reg_steps
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public function set_reg_steps(array $txn_reg_steps)
268
-    {
269
-        $this->set('TXN_reg_steps', $txn_reg_steps);
270
-    }
271
-
272
-
273
-    /**
274
-     * Gets TXN_reg_steps
275
-     *
276
-     * @return array
277
-     * @throws EE_Error
278
-     * @throws InvalidArgumentException
279
-     * @throws InvalidDataTypeException
280
-     * @throws InvalidInterfaceException
281
-     * @throws ReflectionException
282
-     */
283
-    public function reg_steps()
284
-    {
285
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
286
-        return is_array($TXN_reg_steps)
287
-            ? $TXN_reg_steps
288
-            : [];
289
-    }
290
-
291
-
292
-    /**
293
-     * @return string of transaction's total cost, with currency symbol and decimal
294
-     * @throws EE_Error
295
-     * @throws InvalidArgumentException
296
-     * @throws InvalidDataTypeException
297
-     * @throws InvalidInterfaceException
298
-     * @throws ReflectionException
299
-     */
300
-    public function pretty_total()
301
-    {
302
-        return $this->get_pretty('TXN_total');
303
-    }
304
-
305
-
306
-    /**
307
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
308
-     *
309
-     * @return string
310
-     * @throws EE_Error
311
-     * @throws InvalidArgumentException
312
-     * @throws InvalidDataTypeException
313
-     * @throws InvalidInterfaceException
314
-     * @throws ReflectionException
315
-     */
316
-    public function pretty_paid()
317
-    {
318
-        return $this->get_pretty('TXN_paid');
319
-    }
320
-
321
-
322
-    /**
323
-     * calculate the amount remaining for this transaction and return;
324
-     *
325
-     * @return float amount remaining
326
-     * @throws EE_Error
327
-     * @throws InvalidArgumentException
328
-     * @throws InvalidDataTypeException
329
-     * @throws InvalidInterfaceException
330
-     * @throws ReflectionException
331
-     */
332
-    public function remaining()
333
-    {
334
-        return $this->total() - $this->paid();
335
-    }
336
-
337
-
338
-    /**
339
-     * get Transaction Total
340
-     *
341
-     * @return float
342
-     * @throws EE_Error
343
-     * @throws InvalidArgumentException
344
-     * @throws InvalidDataTypeException
345
-     * @throws InvalidInterfaceException
346
-     * @throws ReflectionException
347
-     */
348
-    public function total()
349
-    {
350
-        return (float) $this->get('TXN_total');
351
-    }
352
-
353
-
354
-    /**
355
-     * get Total Amount Paid to Date
356
-     *
357
-     * @return float
358
-     * @throws EE_Error
359
-     * @throws InvalidArgumentException
360
-     * @throws InvalidDataTypeException
361
-     * @throws InvalidInterfaceException
362
-     * @throws ReflectionException
363
-     */
364
-    public function paid()
365
-    {
366
-        return (float) $this->get('TXN_paid');
367
-    }
368
-
369
-
370
-    /**
371
-     * @return mixed|null
372
-     * @throws EE_Error
373
-     * @throws InvalidArgumentException
374
-     * @throws InvalidDataTypeException
375
-     * @throws InvalidInterfaceException
376
-     * @throws ReflectionException
377
-     */
378
-    public function get_cart_session()
379
-    {
380
-        $session_data = (array) $this->get('TXN_session_data');
381
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
382
-            ? $session_data['cart']
383
-            : null;
384
-    }
385
-
386
-
387
-    /**
388
-     * get Transaction session data
389
-     *
390
-     * @return array|mixed
391
-     * @throws EE_Error
392
-     * @throws InvalidArgumentException
393
-     * @throws InvalidDataTypeException
394
-     * @throws InvalidInterfaceException
395
-     * @throws ReflectionException
396
-     */
397
-    public function session_data()
398
-    {
399
-        $session_data = $this->get('TXN_session_data');
400
-        if (empty($session_data)) {
401
-            $session_data = [
402
-                'id'            => null,
403
-                'user_id'       => null,
404
-                'ip_address'    => null,
405
-                'user_agent'    => null,
406
-                'init_access'   => null,
407
-                'last_access'   => null,
408
-                'pages_visited' => [],
409
-            ];
410
-        }
411
-        return $session_data;
412
-    }
413
-
414
-
415
-    /**
416
-     * Set session data within the TXN object
417
-     *
418
-     * @param EE_Session|array|null $session_data
419
-     * @throws EE_Error
420
-     * @throws InvalidArgumentException
421
-     * @throws InvalidDataTypeException
422
-     * @throws InvalidInterfaceException
423
-     * @throws ReflectionException
424
-     */
425
-    public function set_txn_session_data($session_data)
426
-    {
427
-        if ($session_data instanceof EE_Session) {
428
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
429
-        } else {
430
-            $this->set('TXN_session_data', $session_data);
431
-        }
432
-    }
433
-
434
-
435
-    /**
436
-     * get Transaction hash salt
437
-     *
438
-     * @return mixed
439
-     * @throws EE_Error
440
-     * @throws InvalidArgumentException
441
-     * @throws InvalidDataTypeException
442
-     * @throws InvalidInterfaceException
443
-     * @throws ReflectionException
444
-     */
445
-    public function hash_salt_()
446
-    {
447
-        return $this->get('TXN_hash_salt');
448
-    }
449
-
450
-
451
-    /**
452
-     * Returns the transaction datetime as either:
453
-     *            - unix timestamp format ($format = false, $gmt = true)
454
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
455
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
456
-     *              set timezone in this class differs from what the timezone is on the blog.
457
-     *            - formatted date string including the UTC (timezone) offset (default).
458
-     *
459
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
460
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
461
-     *                          or no UTC offset applied
462
-     * @return string | int
463
-     * @throws EE_Error
464
-     * @throws InvalidArgumentException
465
-     * @throws InvalidDataTypeException
466
-     * @throws InvalidInterfaceException
467
-     * @throws ReflectionException
468
-     */
469
-    public function datetime($format = false, $gmt = false)
470
-    {
471
-        if ($format) {
472
-            return $this->get_pretty('TXN_timestamp');
473
-        }
474
-        if ($gmt) {
475
-            return $this->get_raw('TXN_timestamp');
476
-        }
477
-        return $this->get('TXN_timestamp');
478
-    }
479
-
480
-
481
-    /**
482
-     * Gets registrations on this transaction
483
-     *
484
-     * @param array   $query_params array of query parameters
485
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
486
-     * @return EE_Base_Class[]|EE_Registration[]
487
-     * @throws EE_Error
488
-     * @throws InvalidArgumentException
489
-     * @throws InvalidDataTypeException
490
-     * @throws InvalidInterfaceException
491
-     * @throws ReflectionException
492
-     */
493
-    public function registrations($query_params = [], $get_cached = false)
494
-    {
495
-        $query_params = (empty($query_params) || ! is_array($query_params))
496
-            ? [
497
-                'order_by' => [
498
-                    'Event.EVT_name'     => 'ASC',
499
-                    'Attendee.ATT_lname' => 'ASC',
500
-                    'Attendee.ATT_fname' => 'ASC',
501
-                    'REG_ID'             => 'ASC',
502
-                ],
503
-            ]
504
-            : $query_params;
505
-        $query_params = $get_cached
506
-            ? []
507
-            : $query_params;
508
-        return $this->get_many_related('Registration', $query_params);
509
-    }
510
-
511
-
512
-    /**
513
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
514
-     * function for getting attendees and how many registrations they each have for an event)
515
-     *
516
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
517
-     * @throws EE_Error
518
-     * @throws InvalidArgumentException
519
-     * @throws InvalidDataTypeException
520
-     * @throws InvalidInterfaceException
521
-     * @throws ReflectionException
522
-     */
523
-    public function attendees()
524
-    {
525
-        return $this->get_many_related('Attendee', [['Registration.Transaction.TXN_ID' => $this->ID()]]);
526
-    }
527
-
528
-
529
-    /**
530
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
531
-     *
532
-     * @param array $query_params @see
533
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
534
-     * @return EE_Base_Class[]|EE_Payment[]
535
-     * @throws EE_Error
536
-     * @throws InvalidArgumentException
537
-     * @throws InvalidDataTypeException
538
-     * @throws InvalidInterfaceException
539
-     * @throws ReflectionException
540
-     */
541
-    public function payments($query_params = [])
542
-    {
543
-        return $this->get_many_related('Payment', $query_params);
544
-    }
545
-
546
-
547
-    /**
548
-     * gets only approved payments for this transaction
549
-     *
550
-     * @return EE_Base_Class[]|EE_Payment[]
551
-     * @throws EE_Error
552
-     * @throws InvalidArgumentException
553
-     * @throws ReflectionException
554
-     * @throws InvalidDataTypeException
555
-     * @throws InvalidInterfaceException
556
-     */
557
-    public function approved_payments()
558
-    {
559
-        EE_Registry::instance()->load_model('Payment');
560
-        return $this->get_many_related(
561
-            'Payment',
562
-            [
563
-                ['STS_ID' => EEM_Payment::status_id_approved],
564
-                'order_by' => ['PAY_timestamp' => 'DESC'],
565
-            ]
566
-        );
567
-    }
568
-
569
-
570
-    /**
571
-     * Gets all payments which have not been approved
572
-     *
573
-     * @return EE_Base_Class[]|EEI_Payment[]
574
-     * @throws EE_Error if a model is misconfigured somehow
575
-     * @throws InvalidArgumentException
576
-     * @throws InvalidDataTypeException
577
-     * @throws InvalidInterfaceException
578
-     * @throws ReflectionException
579
-     */
580
-    public function pending_payments()
581
-    {
582
-        return $this->get_many_related(
583
-            'Payment',
584
-            [
585
-                [
586
-                    'STS_ID' => EEM_Payment::status_id_pending,
587
-                ],
588
-                'order_by' => [
589
-                    'PAY_timestamp' => 'DESC',
590
-                ],
591
-            ]
592
-        );
593
-    }
594
-
595
-
596
-    /**
597
-     * echoes $this->pretty_status()
598
-     *
599
-     * @param bool $show_icons
600
-     * @throws EE_Error
601
-     * @throws InvalidArgumentException
602
-     * @throws InvalidDataTypeException
603
-     * @throws InvalidInterfaceException
604
-     * @throws ReflectionException
605
-     */
606
-    public function e_pretty_status($show_icons = false)
607
-    {
608
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
609
-    }
610
-
611
-
612
-    /**
613
-     * returns a pretty version of the status, good for displaying to users
614
-     *
615
-     * @param bool $show_icons
616
-     * @return string
617
-     * @throws EE_Error
618
-     * @throws InvalidArgumentException
619
-     * @throws InvalidDataTypeException
620
-     * @throws InvalidInterfaceException
621
-     * @throws ReflectionException
622
-     */
623
-    public function pretty_status($show_icons = false)
624
-    {
625
-        $status = EEM_Status::instance()->localized_status(
626
-            [$this->status_ID() => esc_html__('unknown', 'event_espresso')],
627
-            false,
628
-            'sentence'
629
-        );
630
-        $icon   = '';
631
-        switch ($this->status_ID()) {
632
-            case EEM_Transaction::complete_status_code:
633
-                $icon = $show_icons
634
-                    ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
635
-                    : '';
636
-                break;
637
-            case EEM_Transaction::incomplete_status_code:
638
-                $icon = $show_icons
639
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
640
-                    : '';
641
-                break;
642
-            case EEM_Transaction::abandoned_status_code:
643
-                $icon = $show_icons
644
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>'
645
-                    : '';
646
-                break;
647
-            case EEM_Transaction::failed_status_code:
648
-                $icon = $show_icons
649
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
650
-                    : '';
651
-                break;
652
-            case EEM_Transaction::overpaid_status_code:
653
-                $icon = $show_icons
654
-                    ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>'
655
-                    : '';
656
-                break;
657
-        }
658
-        return $icon . $status[ $this->status_ID() ];
659
-    }
660
-
661
-
662
-    /**
663
-     * get Transaction Status
664
-     *
665
-     * @return mixed
666
-     * @throws EE_Error
667
-     * @throws InvalidArgumentException
668
-     * @throws InvalidDataTypeException
669
-     * @throws InvalidInterfaceException
670
-     * @throws ReflectionException
671
-     */
672
-    public function status_ID()
673
-    {
674
-        return $this->get('STS_ID');
675
-    }
676
-
677
-
678
-    /**
679
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
680
-     *
681
-     * @return boolean
682
-     * @throws EE_Error
683
-     * @throws InvalidArgumentException
684
-     * @throws InvalidDataTypeException
685
-     * @throws InvalidInterfaceException
686
-     * @throws ReflectionException
687
-     */
688
-    public function is_free()
689
-    {
690
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
691
-    }
692
-
693
-
694
-    /**
695
-     * Returns whether this transaction is complete
696
-     * Useful in templates and other logic for deciding if we should ask for another payment...
697
-     *
698
-     * @return boolean
699
-     * @throws EE_Error
700
-     * @throws InvalidArgumentException
701
-     * @throws InvalidDataTypeException
702
-     * @throws InvalidInterfaceException
703
-     * @throws ReflectionException
704
-     */
705
-    public function is_completed()
706
-    {
707
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
708
-    }
709
-
710
-
711
-    /**
712
-     * Returns whether this transaction is incomplete
713
-     * Useful in templates and other logic for deciding if we should ask for another payment...
714
-     *
715
-     * @return boolean
716
-     * @throws EE_Error
717
-     * @throws InvalidArgumentException
718
-     * @throws InvalidDataTypeException
719
-     * @throws InvalidInterfaceException
720
-     * @throws ReflectionException
721
-     */
722
-    public function is_incomplete()
723
-    {
724
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
725
-    }
726
-
727
-
728
-    /**
729
-     * Returns whether this transaction is overpaid
730
-     * Useful in templates and other logic for deciding if monies need to be refunded
731
-     *
732
-     * @return boolean
733
-     * @throws EE_Error
734
-     * @throws InvalidArgumentException
735
-     * @throws InvalidDataTypeException
736
-     * @throws InvalidInterfaceException
737
-     * @throws ReflectionException
738
-     */
739
-    public function is_overpaid()
740
-    {
741
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
742
-    }
743
-
744
-
745
-    /**
746
-     * Returns whether this transaction was abandoned
747
-     * meaning that the transaction/registration process was somehow interrupted and never completed
748
-     * but that contact information exists for at least one registrant
749
-     *
750
-     * @return boolean
751
-     * @throws EE_Error
752
-     * @throws InvalidArgumentException
753
-     * @throws InvalidDataTypeException
754
-     * @throws InvalidInterfaceException
755
-     * @throws ReflectionException
756
-     */
757
-    public function is_abandoned()
758
-    {
759
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
760
-    }
761
-
762
-
763
-    /**
764
-     * Returns whether this transaction failed
765
-     * meaning that the transaction/registration process was somehow interrupted and never completed
766
-     * and that NO contact information exists for any registrants
767
-     *
768
-     * @return boolean
769
-     * @throws EE_Error
770
-     * @throws InvalidArgumentException
771
-     * @throws InvalidDataTypeException
772
-     * @throws InvalidInterfaceException
773
-     * @throws ReflectionException
774
-     */
775
-    public function failed()
776
-    {
777
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
778
-    }
779
-
780
-
781
-    /**
782
-     * This returns the url for the invoice of this transaction
783
-     *
784
-     * @param string $type 'html' or 'pdf' (default is pdf)
785
-     * @return string
786
-     * @throws DomainException
787
-     * @throws EE_Error
788
-     * @throws InvalidArgumentException
789
-     * @throws InvalidDataTypeException
790
-     * @throws InvalidInterfaceException
791
-     * @throws ReflectionException
792
-     */
793
-    public function invoice_url($type = 'html')
794
-    {
795
-        $REG = $this->primary_registration();
796
-        if (! $REG instanceof EE_Registration) {
797
-            return '';
798
-        }
799
-        return $REG->invoice_url($type);
800
-    }
801
-
802
-
803
-    /**
804
-     * Gets the primary registration only
805
-     *
806
-     * @return EE_Base_Class|EE_Registration
807
-     * @throws EE_Error
808
-     * @throws InvalidArgumentException
809
-     * @throws InvalidDataTypeException
810
-     * @throws InvalidInterfaceException
811
-     * @throws ReflectionException
812
-     */
813
-    public function primary_registration()
814
-    {
815
-        $registrations = (array) $this->get_many_related(
816
-            'Registration',
817
-            [['REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT]]
818
-        );
819
-        foreach ($registrations as $registration) {
820
-            // valid registration that is NOT cancelled or declined ?
821
-            if (
822
-                $registration instanceof EE_Registration
823
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
824
-            ) {
825
-                return $registration;
826
-            }
827
-        }
828
-        // nothing valid found, so just return first thing from array of results
829
-        return reset($registrations);
830
-    }
831
-
832
-
833
-    /**
834
-     * Gets the URL for viewing the receipt
835
-     *
836
-     * @param string $type 'pdf' or 'html' (default is 'html')
837
-     * @return string
838
-     * @throws DomainException
839
-     * @throws EE_Error
840
-     * @throws InvalidArgumentException
841
-     * @throws InvalidDataTypeException
842
-     * @throws InvalidInterfaceException
843
-     * @throws ReflectionException
844
-     */
845
-    public function receipt_url($type = 'html')
846
-    {
847
-        $REG = $this->primary_registration();
848
-        if (! $REG instanceof EE_Registration) {
849
-            return '';
850
-        }
851
-        return $REG->receipt_url($type);
852
-    }
853
-
854
-
855
-    /**
856
-     * Gets the URL of the thank you page with this registration REG_url_link added as
857
-     * a query parameter
858
-     *
859
-     * @return string
860
-     * @throws EE_Error
861
-     * @throws InvalidArgumentException
862
-     * @throws InvalidDataTypeException
863
-     * @throws InvalidInterfaceException
864
-     * @throws ReflectionException
865
-     */
866
-    public function payment_overview_url()
867
-    {
868
-        $primary_registration = $this->primary_registration();
869
-        return $primary_registration instanceof EE_Registration
870
-            ? $primary_registration->payment_overview_url()
871
-            : false;
872
-    }
873
-
874
-
875
-    /**
876
-     * @return string
877
-     * @throws EE_Error
878
-     * @throws InvalidArgumentException
879
-     * @throws InvalidDataTypeException
880
-     * @throws InvalidInterfaceException
881
-     * @throws ReflectionException
882
-     */
883
-    public function gateway_response_on_transaction()
884
-    {
885
-        $payment = $this->get_first_related('Payment');
886
-        return $payment instanceof EE_Payment
887
-            ? $payment->gateway_response()
888
-            : '';
889
-    }
890
-
891
-
892
-    /**
893
-     * Get the status object of this object
894
-     *
895
-     * @return EE_Base_Class|EE_Status
896
-     * @throws EE_Error
897
-     * @throws InvalidArgumentException
898
-     * @throws InvalidDataTypeException
899
-     * @throws InvalidInterfaceException
900
-     * @throws ReflectionException
901
-     */
902
-    public function status_obj()
903
-    {
904
-        return $this->get_first_related('Status');
905
-    }
906
-
907
-
908
-    /**
909
-     * Gets all the extra meta info on this payment
910
-     *
911
-     * @param array $query_params
912
-     * @return EE_Base_Class[]|EE_Extra_Meta
913
-     * @throws EE_Error
914
-     * @throws InvalidArgumentException
915
-     * @throws InvalidDataTypeException
916
-     * @throws InvalidInterfaceException
917
-     * @throws ReflectionException
918
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
919
-     */
920
-    public function extra_meta($query_params = [])
921
-    {
922
-        return $this->get_many_related('Extra_Meta', $query_params);
923
-    }
924
-
925
-
926
-    /**
927
-     * Wrapper for _add_relation_to
928
-     *
929
-     * @param EE_Registration $registration
930
-     * @return EE_Base_Class the relation was added to
931
-     * @throws EE_Error
932
-     * @throws InvalidArgumentException
933
-     * @throws InvalidDataTypeException
934
-     * @throws InvalidInterfaceException
935
-     * @throws ReflectionException
936
-     */
937
-    public function add_registration(EE_Registration $registration)
938
-    {
939
-        return $this->_add_relation_to($registration, 'Registration');
940
-    }
941
-
942
-
943
-    /**
944
-     * Removes the given registration from being related (even before saving this transaction).
945
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
946
-     *
947
-     * @param int $registration_or_id
948
-     * @return EE_Base_Class that was removed from being related
949
-     * @throws EE_Error
950
-     * @throws InvalidArgumentException
951
-     * @throws InvalidDataTypeException
952
-     * @throws InvalidInterfaceException
953
-     * @throws ReflectionException
954
-     */
955
-    public function remove_registration_with_id($registration_or_id)
956
-    {
957
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
958
-    }
959
-
960
-
961
-    /**
962
-     * Gets all the line items which are for ACTUAL items
963
-     *
964
-     * @return EE_Line_Item[]
965
-     * @throws EE_Error
966
-     * @throws InvalidArgumentException
967
-     * @throws InvalidDataTypeException
968
-     * @throws InvalidInterfaceException
969
-     * @throws ReflectionException
970
-     */
971
-    public function items_purchased()
972
-    {
973
-        return $this->line_items([['LIN_type' => EEM_Line_Item::type_line_item]]);
974
-    }
975
-
976
-
977
-    /**
978
-     * Wrapper for _add_relation_to
979
-     *
980
-     * @param EE_Line_Item $line_item
981
-     * @return EE_Base_Class the relation was added to
982
-     * @throws EE_Error
983
-     * @throws InvalidArgumentException
984
-     * @throws InvalidDataTypeException
985
-     * @throws InvalidInterfaceException
986
-     * @throws ReflectionException
987
-     */
988
-    public function add_line_item(EE_Line_Item $line_item)
989
-    {
990
-        return $this->_add_relation_to($line_item, 'Line_Item');
991
-    }
992
-
993
-
994
-    /**
995
-     * Gets ALL the line items related to this transaction (unstructured)
996
-     *
997
-     * @param array $query_params
998
-     * @return EE_Base_Class[]|EE_Line_Item[]
999
-     * @throws EE_Error
1000
-     * @throws InvalidArgumentException
1001
-     * @throws InvalidDataTypeException
1002
-     * @throws InvalidInterfaceException
1003
-     * @throws ReflectionException
1004
-     */
1005
-    public function line_items($query_params = [])
1006
-    {
1007
-        return $this->get_many_related('Line_Item', $query_params);
1008
-    }
1009
-
1010
-
1011
-    /**
1012
-     * Gets all the line items which are taxes on the total
1013
-     *
1014
-     * @return EE_Line_Item[]
1015
-     * @throws EE_Error
1016
-     * @throws InvalidArgumentException
1017
-     * @throws InvalidDataTypeException
1018
-     * @throws InvalidInterfaceException
1019
-     * @throws ReflectionException
1020
-     */
1021
-    public function tax_items()
1022
-    {
1023
-        return $this->line_items([['LIN_type' => EEM_Line_Item::type_tax]]);
1024
-    }
1025
-
1026
-
1027
-    /**
1028
-     * Gets the total line item (which is a parent of all other related line items,
1029
-     * meaning it takes them all into account on its total)
1030
-     *
1031
-     * @param bool $create_if_not_found
1032
-     * @return EE_Line_Item|null
1033
-     * @throws EE_Error
1034
-     * @throws InvalidArgumentException
1035
-     * @throws InvalidDataTypeException
1036
-     * @throws InvalidInterfaceException
1037
-     * @throws ReflectionException
1038
-     */
1039
-    public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1040
-    {
1041
-        $item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1042
-        if ($item instanceof EE_Line_Item) {
1043
-            return $item;
1044
-        }
1045
-        return $create_if_not_found
1046
-            ? EEH_Line_Item::create_total_line_item($this)
1047
-            : null;
1048
-    }
1049
-
1050
-
1051
-    /**
1052
-     * Returns the total amount of tax on this transaction
1053
-     * (assumes there's only one tax subtotal line item)
1054
-     *
1055
-     * @return float
1056
-     * @throws EE_Error
1057
-     * @throws InvalidArgumentException
1058
-     * @throws InvalidDataTypeException
1059
-     * @throws InvalidInterfaceException
1060
-     * @throws ReflectionException
1061
-     */
1062
-    public function tax_total()
1063
-    {
1064
-        $tax_line_item = $this->tax_total_line_item();
1065
-        if ($tax_line_item) {
1066
-            return (float) $tax_line_item->total();
1067
-        }
1068
-        return (float) 0;
1069
-    }
1070
-
1071
-
1072
-    /**
1073
-     * Gets the tax subtotal line item (assumes there's only one)
1074
-     *
1075
-     * @return EE_Line_Item
1076
-     * @throws EE_Error
1077
-     * @throws InvalidArgumentException
1078
-     * @throws InvalidDataTypeException
1079
-     * @throws InvalidInterfaceException
1080
-     * @throws ReflectionException
1081
-     */
1082
-    public function tax_total_line_item()
1083
-    {
1084
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1085
-    }
1086
-
1087
-
1088
-    /**
1089
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1090
-     *
1091
-     * @return EE_Form_Section_Proper
1092
-     * @throws EE_Error
1093
-     * @throws InvalidArgumentException
1094
-     * @throws InvalidDataTypeException
1095
-     * @throws InvalidInterfaceException
1096
-     * @throws ReflectionException
1097
-     */
1098
-    public function billing_info()
1099
-    {
1100
-        $payment_method = $this->payment_method();
1101
-        if (! $payment_method) {
1102
-            EE_Error::add_error(
1103
-                esc_html__(
1104
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
1105
-                    'event_espresso'
1106
-                ),
1107
-                __FILE__,
1108
-                __FUNCTION__,
1109
-                __LINE__
1110
-            );
1111
-            return null;
1112
-        }
1113
-        $primary_reg = $this->primary_registration();
1114
-        if (! $primary_reg) {
1115
-            EE_Error::add_error(
1116
-                esc_html__(
1117
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1118
-                    'event_espresso'
1119
-                ),
1120
-                __FILE__,
1121
-                __FUNCTION__,
1122
-                __LINE__
1123
-            );
1124
-            return null;
1125
-        }
1126
-        $attendee = $primary_reg->attendee();
1127
-        if (! $attendee) {
1128
-            EE_Error::add_error(
1129
-                esc_html__(
1130
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1131
-                    'event_espresso'
1132
-                ),
1133
-                __FILE__,
1134
-                __FUNCTION__,
1135
-                __LINE__
1136
-            );
1137
-            return null;
1138
-        }
1139
-        return $attendee->billing_info_for_payment_method($payment_method);
1140
-    }
1141
-
1142
-
1143
-    /**
1144
-     * Gets PMD_ID
1145
-     *
1146
-     * @return int
1147
-     * @throws EE_Error
1148
-     * @throws InvalidArgumentException
1149
-     * @throws InvalidDataTypeException
1150
-     * @throws InvalidInterfaceException
1151
-     * @throws ReflectionException
1152
-     */
1153
-    public function payment_method_ID()
1154
-    {
1155
-        return $this->get('PMD_ID');
1156
-    }
1157
-
1158
-
1159
-    /**
1160
-     * Sets PMD_ID
1161
-     *
1162
-     * @param int $PMD_ID
1163
-     * @throws EE_Error
1164
-     * @throws InvalidArgumentException
1165
-     * @throws InvalidDataTypeException
1166
-     * @throws InvalidInterfaceException
1167
-     * @throws ReflectionException
1168
-     */
1169
-    public function set_payment_method_ID($PMD_ID)
1170
-    {
1171
-        $this->set('PMD_ID', $PMD_ID);
1172
-    }
1173
-
1174
-
1175
-    /**
1176
-     * Gets the last-used payment method on this transaction
1177
-     * (we COULD just use the last-made payment, but some payment methods, namely
1178
-     * offline ones, dont' create payments)
1179
-     *
1180
-     * @return EE_Payment_Method
1181
-     * @throws EE_Error
1182
-     * @throws InvalidArgumentException
1183
-     * @throws InvalidDataTypeException
1184
-     * @throws InvalidInterfaceException
1185
-     * @throws ReflectionException
1186
-     */
1187
-    public function payment_method()
1188
-    {
1189
-        $pm = $this->get_first_related('Payment_Method');
1190
-        if ($pm instanceof EE_Payment_Method) {
1191
-            return $pm;
1192
-        }
1193
-        $last_payment = $this->last_payment();
1194
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1195
-            return $last_payment->payment_method();
1196
-        }
1197
-        return null;
1198
-    }
1199
-
1200
-
1201
-    /**
1202
-     * Gets the last payment made
1203
-     *
1204
-     * @return EE_Base_Class|EE_Payment|null
1205
-     * @throws EE_Error
1206
-     * @throws InvalidArgumentException
1207
-     * @throws InvalidDataTypeException
1208
-     * @throws InvalidInterfaceException
1209
-     * @throws ReflectionException
1210
-     */
1211
-    public function last_payment(): ?EE_Payment
1212
-    {
1213
-        return $this->get_first_related('Payment', ['order_by' => ['PAY_ID' => 'desc']]);
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * Gets all the line items which are unrelated to tickets on this transaction
1219
-     *
1220
-     * @return EE_Line_Item[]
1221
-     * @throws EE_Error
1222
-     * @throws InvalidArgumentException
1223
-     * @throws InvalidDataTypeException
1224
-     * @throws InvalidInterfaceException
1225
-     * @throws ReflectionException
1226
-     */
1227
-    public function non_ticket_line_items()
1228
-    {
1229
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1230
-    }
1231
-
1232
-
1233
-    /**
1234
-     * possibly toggles TXN status
1235
-     *
1236
-     * @param boolean $update whether to save the TXN
1237
-     * @return bool whether the TXN was saved
1238
-     * @throws EE_Error
1239
-     * @throws InvalidArgumentException
1240
-     * @throws InvalidDataTypeException
1241
-     * @throws InvalidInterfaceException
1242
-     * @throws ReflectionException
1243
-     * @throws RuntimeException
1244
-     */
1245
-    public function update_status_based_on_total_paid($update = true)
1246
-    {
1247
-        // set transaction status based on comparison of TXN_paid vs TXN_total
1248
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1249
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
1250
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1251
-            $new_txn_status = EEM_Transaction::complete_status_code;
1252
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1253
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
1254
-        } else {
1255
-            throw new RuntimeException(
1256
-                esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1257
-            );
1258
-        }
1259
-        if ($new_txn_status !== $this->status_ID()) {
1260
-            $this->set_status($new_txn_status);
1261
-            if ($update) {
1262
-                return (bool) $this->save();
1263
-            }
1264
-        }
1265
-        return false;
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * Updates the transaction's status and total_paid based on all the payments
1271
-     * that apply to it
1272
-     *
1273
-     * @return array|bool
1274
-     * @throws EE_Error
1275
-     * @throws InvalidArgumentException
1276
-     * @throws ReflectionException
1277
-     * @throws InvalidDataTypeException
1278
-     * @throws InvalidInterfaceException
1279
-     * @deprecated
1280
-     */
1281
-    public function update_based_on_payments()
1282
-    {
1283
-        EE_Error::doing_it_wrong(
1284
-            __CLASS__ . '::' . __FUNCTION__,
1285
-            sprintf(
1286
-                esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1287
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1288
-            ),
1289
-            '4.6.0'
1290
-        );
1291
-        /** @type EE_Transaction_Processor $transaction_processor */
1292
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1293
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1294
-    }
1295
-
1296
-
1297
-    /**
1298
-     * @return string
1299
-     */
1300
-    public function old_txn_status()
1301
-    {
1302
-        return $this->_old_txn_status;
1303
-    }
1304
-
1305
-
1306
-    /**
1307
-     * @param string $old_txn_status
1308
-     */
1309
-    public function set_old_txn_status($old_txn_status)
1310
-    {
1311
-        // only set the first time
1312
-        if ($this->_old_txn_status === null) {
1313
-            $this->_old_txn_status = $old_txn_status;
1314
-        }
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * reg_status_updated
1320
-     *
1321
-     * @return bool
1322
-     * @throws EE_Error
1323
-     * @throws InvalidArgumentException
1324
-     * @throws InvalidDataTypeException
1325
-     * @throws InvalidInterfaceException
1326
-     * @throws ReflectionException
1327
-     */
1328
-    public function txn_status_updated()
1329
-    {
1330
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1331
-    }
1332
-
1333
-
1334
-    /**
1335
-     * _reg_steps_completed
1336
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1337
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1338
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1339
-     *
1340
-     * @param string $reg_step_slug
1341
-     * @param bool   $check_all
1342
-     * @return bool|int
1343
-     * @throws EE_Error
1344
-     * @throws InvalidArgumentException
1345
-     * @throws InvalidDataTypeException
1346
-     * @throws InvalidInterfaceException
1347
-     * @throws ReflectionException
1348
-     */
1349
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1350
-    {
1351
-        $reg_steps = $this->reg_steps();
1352
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1353
-            return false;
1354
-        }
1355
-        // loop thru reg steps array)
1356
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1357
-            // if NOT checking ALL steps (only checking one step)
1358
-            if (! $check_all) {
1359
-                // and this is the one
1360
-                if ($slug === $reg_step_slug) {
1361
-                    return $reg_step_completed;
1362
-                }
1363
-                // skip to next reg step in loop
1364
-                continue;
1365
-            }
1366
-            // $check_all must be true, else we would never have gotten to this point
1367
-            if ($slug === $reg_step_slug) {
1368
-                // if we reach this point, then we are testing either:
1369
-                // all_reg_steps_completed_except() or
1370
-                // all_reg_steps_completed_except_final_step(),
1371
-                // and since this is the reg step EXCEPTION being tested
1372
-                // we want to return true (yes true) if this reg step is NOT completed
1373
-                // ie: "is everything completed except the final step?"
1374
-                // "that is correct... the final step is not completed, but all others are."
1375
-                return $reg_step_completed !== true;
1376
-            }
1377
-            if ($reg_step_completed !== true) {
1378
-                // if any reg step is NOT completed, then ALL steps are not completed
1379
-                return false;
1380
-            }
1381
-        }
1382
-        return true;
1383
-    }
1384
-
1385
-
1386
-    /**
1387
-     * all_reg_steps_completed
1388
-     * returns:
1389
-     *    true if ALL reg steps have been marked as completed
1390
-     *        or false if any step is not completed
1391
-     *
1392
-     * @return bool
1393
-     * @throws EE_Error
1394
-     * @throws InvalidArgumentException
1395
-     * @throws InvalidDataTypeException
1396
-     * @throws InvalidInterfaceException
1397
-     * @throws ReflectionException
1398
-     */
1399
-    public function all_reg_steps_completed()
1400
-    {
1401
-        return $this->_reg_steps_completed();
1402
-    }
1403
-
1404
-
1405
-    /**
1406
-     * all_reg_steps_completed_except
1407
-     * returns:
1408
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1409
-     *        or false if any other step is not completed
1410
-     *        or false if ALL steps are completed including the exception you are testing !!!
1411
-     *
1412
-     * @param string $exception
1413
-     * @return bool
1414
-     * @throws EE_Error
1415
-     * @throws InvalidArgumentException
1416
-     * @throws InvalidDataTypeException
1417
-     * @throws InvalidInterfaceException
1418
-     * @throws ReflectionException
1419
-     */
1420
-    public function all_reg_steps_completed_except($exception = '')
1421
-    {
1422
-        return $this->_reg_steps_completed($exception);
1423
-    }
1424
-
1425
-
1426
-    /**
1427
-     * all_reg_steps_completed_except
1428
-     * returns:
1429
-     *        true if ALL reg steps, except the final step, have been marked as completed
1430
-     *        or false if any step is not completed
1431
-     *    or false if ALL steps are completed including the final step !!!
1432
-     *
1433
-     * @return bool
1434
-     * @throws EE_Error
1435
-     * @throws InvalidArgumentException
1436
-     * @throws InvalidDataTypeException
1437
-     * @throws InvalidInterfaceException
1438
-     * @throws ReflectionException
1439
-     */
1440
-    public function all_reg_steps_completed_except_final_step()
1441
-    {
1442
-        return $this->_reg_steps_completed('finalize_registration');
1443
-    }
1444
-
1445
-
1446
-    /**
1447
-     * reg_step_completed
1448
-     * returns:
1449
-     *    true if a specific reg step has been marked as completed
1450
-     *    a Unix timestamp if it has been initialized but not yet completed,
1451
-     *    or false if it has not yet been initialized
1452
-     *
1453
-     * @param string $reg_step_slug
1454
-     * @return bool|int
1455
-     * @throws EE_Error
1456
-     * @throws InvalidArgumentException
1457
-     * @throws InvalidDataTypeException
1458
-     * @throws InvalidInterfaceException
1459
-     * @throws ReflectionException
1460
-     */
1461
-    public function reg_step_completed($reg_step_slug)
1462
-    {
1463
-        return $this->_reg_steps_completed($reg_step_slug, false);
1464
-    }
1465
-
1466
-
1467
-    /**
1468
-     * completed_final_reg_step
1469
-     * returns:
1470
-     *    true if the finalize_registration reg step has been marked as completed
1471
-     *    a Unix timestamp if it has been initialized but not yet completed,
1472
-     *    or false if it has not yet been initialized
1473
-     *
1474
-     * @return bool|int
1475
-     * @throws EE_Error
1476
-     * @throws InvalidArgumentException
1477
-     * @throws InvalidDataTypeException
1478
-     * @throws InvalidInterfaceException
1479
-     * @throws ReflectionException
1480
-     */
1481
-    public function final_reg_step_completed()
1482
-    {
1483
-        return $this->_reg_steps_completed('finalize_registration', false);
1484
-    }
1485
-
1486
-
1487
-    /**
1488
-     * set_reg_step_initiated
1489
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1490
-     *
1491
-     * @param string $reg_step_slug
1492
-     * @return boolean
1493
-     * @throws EE_Error
1494
-     * @throws InvalidArgumentException
1495
-     * @throws InvalidDataTypeException
1496
-     * @throws InvalidInterfaceException
1497
-     * @throws ReflectionException
1498
-     */
1499
-    public function set_reg_step_initiated($reg_step_slug)
1500
-    {
1501
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1502
-    }
1503
-
1504
-
1505
-    /**
1506
-     * set_reg_step_completed
1507
-     * given a valid TXN_reg_step, this sets the step as completed
1508
-     *
1509
-     * @param string $reg_step_slug
1510
-     * @return boolean
1511
-     * @throws EE_Error
1512
-     * @throws InvalidArgumentException
1513
-     * @throws InvalidDataTypeException
1514
-     * @throws InvalidInterfaceException
1515
-     * @throws ReflectionException
1516
-     */
1517
-    public function set_reg_step_completed($reg_step_slug)
1518
-    {
1519
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1520
-    }
1521
-
1522
-
1523
-    /**
1524
-     * set_reg_step_completed
1525
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1526
-     *
1527
-     * @param string $reg_step_slug
1528
-     * @return boolean
1529
-     * @throws EE_Error
1530
-     * @throws InvalidArgumentException
1531
-     * @throws InvalidDataTypeException
1532
-     * @throws InvalidInterfaceException
1533
-     * @throws ReflectionException
1534
-     */
1535
-    public function set_reg_step_not_completed($reg_step_slug)
1536
-    {
1537
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1538
-    }
1539
-
1540
-
1541
-    /**
1542
-     * set_reg_step_completed
1543
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1544
-     *
1545
-     * @param string      $reg_step_slug
1546
-     * @param boolean|int $status
1547
-     * @return boolean
1548
-     * @throws EE_Error
1549
-     * @throws InvalidArgumentException
1550
-     * @throws InvalidDataTypeException
1551
-     * @throws InvalidInterfaceException
1552
-     * @throws ReflectionException
1553
-     */
1554
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1555
-    {
1556
-        // validate status
1557
-        $status = is_bool($status) || is_int($status)
1558
-            ? $status
1559
-            : false;
1560
-        // get reg steps array
1561
-        $txn_reg_steps = $this->reg_steps();
1562
-        // if reg step does NOT exist
1563
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1564
-            return false;
1565
-        }
1566
-        // if  we're trying to complete a step that is already completed
1567
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1568
-            return true;
1569
-        }
1570
-        // if  we're trying to complete a step that hasn't even started
1571
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1572
-            return false;
1573
-        }
1574
-        // if current status value matches the incoming value (no change)
1575
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1576
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1577
-            // this will happen in cases where multiple AJAX requests occur during the same step
1578
-            return true;
1579
-        }
1580
-        // if we're trying to set a start time, but it has already been set...
1581
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1582
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1583
-            return true;
1584
-        }
1585
-        // update completed status
1586
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1587
-        $this->set_reg_steps($txn_reg_steps);
1588
-        $this->save();
1589
-        return true;
1590
-    }
1591
-
1592
-
1593
-    /**
1594
-     * remove_reg_step
1595
-     * given a valid TXN_reg_step slug, this will remove (unset)
1596
-     * the reg step from the TXN reg step array
1597
-     *
1598
-     * @param string $reg_step_slug
1599
-     * @return void
1600
-     * @throws EE_Error
1601
-     * @throws InvalidArgumentException
1602
-     * @throws InvalidDataTypeException
1603
-     * @throws InvalidInterfaceException
1604
-     * @throws ReflectionException
1605
-     */
1606
-    public function remove_reg_step($reg_step_slug)
1607
-    {
1608
-        // get reg steps array
1609
-        $txn_reg_steps = $this->reg_steps();
1610
-        unset($txn_reg_steps[ $reg_step_slug ]);
1611
-        $this->set_reg_steps($txn_reg_steps);
1612
-    }
1613
-
1614
-
1615
-    /**
1616
-     * toggle_failed_transaction_status
1617
-     * upgrades a TXNs status from failed to abandoned,
1618
-     * meaning that contact information has been captured for at least one registrant
1619
-     *
1620
-     * @param bool $save
1621
-     * @return bool
1622
-     * @throws EE_Error
1623
-     * @throws InvalidArgumentException
1624
-     * @throws InvalidDataTypeException
1625
-     * @throws InvalidInterfaceException
1626
-     * @throws ReflectionException
1627
-     */
1628
-    public function toggle_failed_transaction_status($save = true)
1629
-    {
1630
-        // if TXN status is still set as "failed"...
1631
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1632
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1633
-            if ($save) {
1634
-                $this->save();
1635
-            }
1636
-            return true;
1637
-        }
1638
-        return false;
1639
-    }
1640
-
1641
-
1642
-    /**
1643
-     * toggle_abandoned_transaction_status
1644
-     * upgrades a TXNs status from failed or abandoned to incomplete
1645
-     *
1646
-     * @return bool
1647
-     * @throws EE_Error
1648
-     * @throws InvalidArgumentException
1649
-     * @throws InvalidDataTypeException
1650
-     * @throws InvalidInterfaceException
1651
-     * @throws ReflectionException
1652
-     */
1653
-    public function toggle_abandoned_transaction_status()
1654
-    {
1655
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1656
-        $txn_status = $this->status_ID();
1657
-        if (
1658
-            $txn_status === EEM_Transaction::failed_status_code
1659
-            || $txn_status === EEM_Transaction::abandoned_status_code
1660
-        ) {
1661
-            // if a contact record for the primary registrant has been created
1662
-            if (
1663
-                $this->primary_registration() instanceof EE_Registration
1664
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1665
-            ) {
1666
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1667
-            } else {
1668
-                // no contact record? yer abandoned!
1669
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1670
-            }
1671
-            return true;
1672
-        }
1673
-        return false;
1674
-    }
1675
-
1676
-
1677
-    /**
1678
-     * checks if an Abandoned TXN has any related payments, and if so,
1679
-     * updates the TXN status based on the amount paid
1680
-     *
1681
-     * @throws EE_Error
1682
-     * @throws InvalidArgumentException
1683
-     * @throws InvalidDataTypeException
1684
-     * @throws InvalidInterfaceException
1685
-     * @throws ReflectionException
1686
-     * @throws RuntimeException
1687
-     * @throws ReflectionException
1688
-     */
1689
-    public function verify_abandoned_transaction_status()
1690
-    {
1691
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1692
-            return;
1693
-        }
1694
-        $payments = $this->get_many_related('Payment');
1695
-        if (! empty($payments)) {
1696
-            foreach ($payments as $payment) {
1697
-                if ($payment instanceof EE_Payment) {
1698
-                    // kk this TXN should NOT be abandoned
1699
-                    $this->update_status_based_on_total_paid();
1700
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1701
-                        EE_Error::add_attention(
1702
-                            sprintf(
1703
-                                esc_html__(
1704
-                                    '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.',
1705
-                                    'event_espresso'
1706
-                                ),
1707
-                                $this->ID(),
1708
-                                $this->pretty_status()
1709
-                            )
1710
-                        );
1711
-                    }
1712
-                    // get final reg step status
1713
-                    $finalized = $this->final_reg_step_completed();
1714
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1715
-                    // but has not yet been fully completed (TRUE)
1716
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1717
-                        $this->set_reg_step_completed('finalize_registration');
1718
-                        $this->save();
1719
-                    }
1720
-                }
1721
-            }
1722
-        }
1723
-    }
1724
-
1725
-
1726
-    /**
1727
-     * @throws EE_Error
1728
-     * @throws InvalidArgumentException
1729
-     * @throws InvalidDataTypeException
1730
-     * @throws InvalidInterfaceException
1731
-     * @throws ReflectionException
1732
-     * @throws RuntimeException
1733
-     * @since 4.10.4.p
1734
-     */
1735
-    public function recalculateLineItems()
1736
-    {
1737
-        $total_line_item = $this->total_line_item(false);
1738
-        if ($total_line_item instanceof EE_Line_Item) {
1739
-            EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1740
-            return EEH_Line_Item::apply_taxes($total_line_item, true);
1741
-        }
1742
-        return false;
1743
-    }
16
+	/**
17
+	 * The length of time in seconds that a lock is applied before being considered expired.
18
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
19
+	 */
20
+	const LOCK_EXPIRATION = 2;
21
+
22
+	/**
23
+	 * txn status upon initial construction.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $_old_txn_status;
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values          incoming values
32
+	 * @param string $timezone                incoming timezone
33
+	 *                                        (if not set the timezone set for the website will be used.)
34
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
+	 *                                        date_format and the second value is the time format
36
+	 * @return EE_Transaction
37
+	 * @throws EE_Error
38
+	 * @throws InvalidArgumentException
39
+	 * @throws InvalidDataTypeException
40
+	 * @throws InvalidInterfaceException
41
+	 * @throws ReflectionException
42
+	 */
43
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
44
+	{
45
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
+		$txn        = $has_object
47
+			? $has_object
48
+			: new self($props_n_values, false, $timezone, $date_formats);
49
+		if (! $has_object) {
50
+			$txn->set_old_txn_status($txn->status_ID());
51
+		}
52
+		return $txn;
53
+	}
54
+
55
+
56
+	/**
57
+	 * @param array  $props_n_values  incoming values from the database
58
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
+	 *                                the website will be used.
60
+	 * @return EE_Transaction
61
+	 * @throws EE_Error
62
+	 * @throws InvalidArgumentException
63
+	 * @throws InvalidDataTypeException
64
+	 * @throws InvalidInterfaceException
65
+	 * @throws ReflectionException
66
+	 */
67
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
68
+	{
69
+		$txn = new self($props_n_values, true, $timezone);
70
+		$txn->set_old_txn_status($txn->status_ID());
71
+		return $txn;
72
+	}
73
+
74
+
75
+	/**
76
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
+	 * If that also fails, then an exception is thrown.
79
+	 *
80
+	 * @throws EE_Error
81
+	 * @throws InvalidArgumentException
82
+	 * @throws InvalidDataTypeException
83
+	 * @throws InvalidInterfaceException
84
+	 * @throws ReflectionException
85
+	 */
86
+	public function lock()
87
+	{
88
+		// attempt to set lock, but if that fails...
89
+		if (! $this->add_extra_meta('lock', time(), true)) {
90
+			// then attempt to remove the lock in case it is expired
91
+			if ($this->_remove_expired_lock()) {
92
+				// if removal was successful, then try setting lock again
93
+				$this->lock();
94
+			} else {
95
+				// but if the lock can not be removed, then throw an exception
96
+				throw new EE_Error(
97
+					sprintf(
98
+						esc_html__(
99
+							'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.',
100
+							'event_espresso'
101
+						),
102
+						$this->ID()
103
+					)
104
+				);
105
+			}
106
+		}
107
+	}
108
+
109
+
110
+	/**
111
+	 * removes transaction lock applied in EE_Transaction::lock()
112
+	 *
113
+	 * @return int
114
+	 * @throws EE_Error
115
+	 * @throws InvalidArgumentException
116
+	 * @throws InvalidDataTypeException
117
+	 * @throws InvalidInterfaceException
118
+	 * @throws ReflectionException
119
+	 */
120
+	public function unlock()
121
+	{
122
+		return $this->delete_extra_meta('lock');
123
+	}
124
+
125
+
126
+	/**
127
+	 * Decides whether or not now is the right time to update the transaction.
128
+	 * This is useful because we don't always know if it is safe to update the transaction
129
+	 * and its related data. why?
130
+	 * because it's possible that the transaction is being used in another
131
+	 * request and could overwrite anything we save.
132
+	 * So we want to only update the txn once we know that won't happen.
133
+	 * We also check that the lock isn't expired, and remove it if it is
134
+	 *
135
+	 * @return boolean
136
+	 * @throws EE_Error
137
+	 * @throws InvalidArgumentException
138
+	 * @throws InvalidDataTypeException
139
+	 * @throws InvalidInterfaceException
140
+	 * @throws ReflectionException
141
+	 */
142
+	public function is_locked()
143
+	{
144
+		// if TXN is not locked, then return false immediately
145
+		if (! $this->_get_lock()) {
146
+			return false;
147
+		}
148
+		// if not, then let's try and remove the lock in case it's expired...
149
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
+		// and a positive number if the lock was removed (ie: number of locks deleted),
151
+		// so we need to return the opposite
152
+		return ! $this->_remove_expired_lock();
153
+	}
154
+
155
+
156
+	/**
157
+	 * Gets the meta field indicating that this TXN is locked
158
+	 *
159
+	 * @return int
160
+	 * @throws EE_Error
161
+	 * @throws InvalidArgumentException
162
+	 * @throws InvalidDataTypeException
163
+	 * @throws InvalidInterfaceException
164
+	 * @throws ReflectionException
165
+	 */
166
+	protected function _get_lock()
167
+	{
168
+		return (int) $this->get_extra_meta('lock', true, 0);
169
+	}
170
+
171
+
172
+	/**
173
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
+	 *
175
+	 * @return int
176
+	 * @throws EE_Error
177
+	 * @throws InvalidArgumentException
178
+	 * @throws InvalidDataTypeException
179
+	 * @throws InvalidInterfaceException
180
+	 * @throws ReflectionException
181
+	 */
182
+	protected function _remove_expired_lock()
183
+	{
184
+		$locked = $this->_get_lock();
185
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
+			return $this->unlock();
187
+		}
188
+		return 0;
189
+	}
190
+
191
+
192
+	/**
193
+	 * Set transaction total
194
+	 *
195
+	 * @param float $total total value of transaction
196
+	 * @throws EE_Error
197
+	 * @throws InvalidArgumentException
198
+	 * @throws InvalidDataTypeException
199
+	 * @throws InvalidInterfaceException
200
+	 * @throws ReflectionException
201
+	 */
202
+	public function set_total($total = 0.00)
203
+	{
204
+		$this->set('TXN_total', (float) $total);
205
+	}
206
+
207
+
208
+	/**
209
+	 * Set Total Amount Paid to Date
210
+	 *
211
+	 * @param float $total_paid total amount paid to date (sum of all payments)
212
+	 * @throws EE_Error
213
+	 * @throws InvalidArgumentException
214
+	 * @throws InvalidDataTypeException
215
+	 * @throws InvalidInterfaceException
216
+	 * @throws ReflectionException
217
+	 */
218
+	public function set_paid($total_paid = 0.00)
219
+	{
220
+		$this->set('TXN_paid', (float) $total_paid);
221
+	}
222
+
223
+
224
+	/**
225
+	 * Set transaction status
226
+	 *
227
+	 * @param string $status        whether the transaction is open, declined, accepted,
228
+	 *                              or any number of custom values that can be set
229
+	 * @throws EE_Error
230
+	 * @throws InvalidArgumentException
231
+	 * @throws InvalidDataTypeException
232
+	 * @throws InvalidInterfaceException
233
+	 * @throws ReflectionException
234
+	 */
235
+	public function set_status($status = '')
236
+	{
237
+		$this->set('STS_ID', $status);
238
+	}
239
+
240
+
241
+	/**
242
+	 * Set hash salt
243
+	 *
244
+	 * @param string $hash_salt required for some payment gateways
245
+	 * @throws EE_Error
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidDataTypeException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function set_hash_salt($hash_salt = '')
252
+	{
253
+		$this->set('TXN_hash_salt', $hash_salt);
254
+	}
255
+
256
+
257
+	/**
258
+	 * Sets TXN_reg_steps array
259
+	 *
260
+	 * @param array $txn_reg_steps
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public function set_reg_steps(array $txn_reg_steps)
268
+	{
269
+		$this->set('TXN_reg_steps', $txn_reg_steps);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Gets TXN_reg_steps
275
+	 *
276
+	 * @return array
277
+	 * @throws EE_Error
278
+	 * @throws InvalidArgumentException
279
+	 * @throws InvalidDataTypeException
280
+	 * @throws InvalidInterfaceException
281
+	 * @throws ReflectionException
282
+	 */
283
+	public function reg_steps()
284
+	{
285
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
286
+		return is_array($TXN_reg_steps)
287
+			? $TXN_reg_steps
288
+			: [];
289
+	}
290
+
291
+
292
+	/**
293
+	 * @return string of transaction's total cost, with currency symbol and decimal
294
+	 * @throws EE_Error
295
+	 * @throws InvalidArgumentException
296
+	 * @throws InvalidDataTypeException
297
+	 * @throws InvalidInterfaceException
298
+	 * @throws ReflectionException
299
+	 */
300
+	public function pretty_total()
301
+	{
302
+		return $this->get_pretty('TXN_total');
303
+	}
304
+
305
+
306
+	/**
307
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
308
+	 *
309
+	 * @return string
310
+	 * @throws EE_Error
311
+	 * @throws InvalidArgumentException
312
+	 * @throws InvalidDataTypeException
313
+	 * @throws InvalidInterfaceException
314
+	 * @throws ReflectionException
315
+	 */
316
+	public function pretty_paid()
317
+	{
318
+		return $this->get_pretty('TXN_paid');
319
+	}
320
+
321
+
322
+	/**
323
+	 * calculate the amount remaining for this transaction and return;
324
+	 *
325
+	 * @return float amount remaining
326
+	 * @throws EE_Error
327
+	 * @throws InvalidArgumentException
328
+	 * @throws InvalidDataTypeException
329
+	 * @throws InvalidInterfaceException
330
+	 * @throws ReflectionException
331
+	 */
332
+	public function remaining()
333
+	{
334
+		return $this->total() - $this->paid();
335
+	}
336
+
337
+
338
+	/**
339
+	 * get Transaction Total
340
+	 *
341
+	 * @return float
342
+	 * @throws EE_Error
343
+	 * @throws InvalidArgumentException
344
+	 * @throws InvalidDataTypeException
345
+	 * @throws InvalidInterfaceException
346
+	 * @throws ReflectionException
347
+	 */
348
+	public function total()
349
+	{
350
+		return (float) $this->get('TXN_total');
351
+	}
352
+
353
+
354
+	/**
355
+	 * get Total Amount Paid to Date
356
+	 *
357
+	 * @return float
358
+	 * @throws EE_Error
359
+	 * @throws InvalidArgumentException
360
+	 * @throws InvalidDataTypeException
361
+	 * @throws InvalidInterfaceException
362
+	 * @throws ReflectionException
363
+	 */
364
+	public function paid()
365
+	{
366
+		return (float) $this->get('TXN_paid');
367
+	}
368
+
369
+
370
+	/**
371
+	 * @return mixed|null
372
+	 * @throws EE_Error
373
+	 * @throws InvalidArgumentException
374
+	 * @throws InvalidDataTypeException
375
+	 * @throws InvalidInterfaceException
376
+	 * @throws ReflectionException
377
+	 */
378
+	public function get_cart_session()
379
+	{
380
+		$session_data = (array) $this->get('TXN_session_data');
381
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
382
+			? $session_data['cart']
383
+			: null;
384
+	}
385
+
386
+
387
+	/**
388
+	 * get Transaction session data
389
+	 *
390
+	 * @return array|mixed
391
+	 * @throws EE_Error
392
+	 * @throws InvalidArgumentException
393
+	 * @throws InvalidDataTypeException
394
+	 * @throws InvalidInterfaceException
395
+	 * @throws ReflectionException
396
+	 */
397
+	public function session_data()
398
+	{
399
+		$session_data = $this->get('TXN_session_data');
400
+		if (empty($session_data)) {
401
+			$session_data = [
402
+				'id'            => null,
403
+				'user_id'       => null,
404
+				'ip_address'    => null,
405
+				'user_agent'    => null,
406
+				'init_access'   => null,
407
+				'last_access'   => null,
408
+				'pages_visited' => [],
409
+			];
410
+		}
411
+		return $session_data;
412
+	}
413
+
414
+
415
+	/**
416
+	 * Set session data within the TXN object
417
+	 *
418
+	 * @param EE_Session|array|null $session_data
419
+	 * @throws EE_Error
420
+	 * @throws InvalidArgumentException
421
+	 * @throws InvalidDataTypeException
422
+	 * @throws InvalidInterfaceException
423
+	 * @throws ReflectionException
424
+	 */
425
+	public function set_txn_session_data($session_data)
426
+	{
427
+		if ($session_data instanceof EE_Session) {
428
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
429
+		} else {
430
+			$this->set('TXN_session_data', $session_data);
431
+		}
432
+	}
433
+
434
+
435
+	/**
436
+	 * get Transaction hash salt
437
+	 *
438
+	 * @return mixed
439
+	 * @throws EE_Error
440
+	 * @throws InvalidArgumentException
441
+	 * @throws InvalidDataTypeException
442
+	 * @throws InvalidInterfaceException
443
+	 * @throws ReflectionException
444
+	 */
445
+	public function hash_salt_()
446
+	{
447
+		return $this->get('TXN_hash_salt');
448
+	}
449
+
450
+
451
+	/**
452
+	 * Returns the transaction datetime as either:
453
+	 *            - unix timestamp format ($format = false, $gmt = true)
454
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
455
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
456
+	 *              set timezone in this class differs from what the timezone is on the blog.
457
+	 *            - formatted date string including the UTC (timezone) offset (default).
458
+	 *
459
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
460
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
461
+	 *                          or no UTC offset applied
462
+	 * @return string | int
463
+	 * @throws EE_Error
464
+	 * @throws InvalidArgumentException
465
+	 * @throws InvalidDataTypeException
466
+	 * @throws InvalidInterfaceException
467
+	 * @throws ReflectionException
468
+	 */
469
+	public function datetime($format = false, $gmt = false)
470
+	{
471
+		if ($format) {
472
+			return $this->get_pretty('TXN_timestamp');
473
+		}
474
+		if ($gmt) {
475
+			return $this->get_raw('TXN_timestamp');
476
+		}
477
+		return $this->get('TXN_timestamp');
478
+	}
479
+
480
+
481
+	/**
482
+	 * Gets registrations on this transaction
483
+	 *
484
+	 * @param array   $query_params array of query parameters
485
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
486
+	 * @return EE_Base_Class[]|EE_Registration[]
487
+	 * @throws EE_Error
488
+	 * @throws InvalidArgumentException
489
+	 * @throws InvalidDataTypeException
490
+	 * @throws InvalidInterfaceException
491
+	 * @throws ReflectionException
492
+	 */
493
+	public function registrations($query_params = [], $get_cached = false)
494
+	{
495
+		$query_params = (empty($query_params) || ! is_array($query_params))
496
+			? [
497
+				'order_by' => [
498
+					'Event.EVT_name'     => 'ASC',
499
+					'Attendee.ATT_lname' => 'ASC',
500
+					'Attendee.ATT_fname' => 'ASC',
501
+					'REG_ID'             => 'ASC',
502
+				],
503
+			]
504
+			: $query_params;
505
+		$query_params = $get_cached
506
+			? []
507
+			: $query_params;
508
+		return $this->get_many_related('Registration', $query_params);
509
+	}
510
+
511
+
512
+	/**
513
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
514
+	 * function for getting attendees and how many registrations they each have for an event)
515
+	 *
516
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
517
+	 * @throws EE_Error
518
+	 * @throws InvalidArgumentException
519
+	 * @throws InvalidDataTypeException
520
+	 * @throws InvalidInterfaceException
521
+	 * @throws ReflectionException
522
+	 */
523
+	public function attendees()
524
+	{
525
+		return $this->get_many_related('Attendee', [['Registration.Transaction.TXN_ID' => $this->ID()]]);
526
+	}
527
+
528
+
529
+	/**
530
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
531
+	 *
532
+	 * @param array $query_params @see
533
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
534
+	 * @return EE_Base_Class[]|EE_Payment[]
535
+	 * @throws EE_Error
536
+	 * @throws InvalidArgumentException
537
+	 * @throws InvalidDataTypeException
538
+	 * @throws InvalidInterfaceException
539
+	 * @throws ReflectionException
540
+	 */
541
+	public function payments($query_params = [])
542
+	{
543
+		return $this->get_many_related('Payment', $query_params);
544
+	}
545
+
546
+
547
+	/**
548
+	 * gets only approved payments for this transaction
549
+	 *
550
+	 * @return EE_Base_Class[]|EE_Payment[]
551
+	 * @throws EE_Error
552
+	 * @throws InvalidArgumentException
553
+	 * @throws ReflectionException
554
+	 * @throws InvalidDataTypeException
555
+	 * @throws InvalidInterfaceException
556
+	 */
557
+	public function approved_payments()
558
+	{
559
+		EE_Registry::instance()->load_model('Payment');
560
+		return $this->get_many_related(
561
+			'Payment',
562
+			[
563
+				['STS_ID' => EEM_Payment::status_id_approved],
564
+				'order_by' => ['PAY_timestamp' => 'DESC'],
565
+			]
566
+		);
567
+	}
568
+
569
+
570
+	/**
571
+	 * Gets all payments which have not been approved
572
+	 *
573
+	 * @return EE_Base_Class[]|EEI_Payment[]
574
+	 * @throws EE_Error if a model is misconfigured somehow
575
+	 * @throws InvalidArgumentException
576
+	 * @throws InvalidDataTypeException
577
+	 * @throws InvalidInterfaceException
578
+	 * @throws ReflectionException
579
+	 */
580
+	public function pending_payments()
581
+	{
582
+		return $this->get_many_related(
583
+			'Payment',
584
+			[
585
+				[
586
+					'STS_ID' => EEM_Payment::status_id_pending,
587
+				],
588
+				'order_by' => [
589
+					'PAY_timestamp' => 'DESC',
590
+				],
591
+			]
592
+		);
593
+	}
594
+
595
+
596
+	/**
597
+	 * echoes $this->pretty_status()
598
+	 *
599
+	 * @param bool $show_icons
600
+	 * @throws EE_Error
601
+	 * @throws InvalidArgumentException
602
+	 * @throws InvalidDataTypeException
603
+	 * @throws InvalidInterfaceException
604
+	 * @throws ReflectionException
605
+	 */
606
+	public function e_pretty_status($show_icons = false)
607
+	{
608
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
609
+	}
610
+
611
+
612
+	/**
613
+	 * returns a pretty version of the status, good for displaying to users
614
+	 *
615
+	 * @param bool $show_icons
616
+	 * @return string
617
+	 * @throws EE_Error
618
+	 * @throws InvalidArgumentException
619
+	 * @throws InvalidDataTypeException
620
+	 * @throws InvalidInterfaceException
621
+	 * @throws ReflectionException
622
+	 */
623
+	public function pretty_status($show_icons = false)
624
+	{
625
+		$status = EEM_Status::instance()->localized_status(
626
+			[$this->status_ID() => esc_html__('unknown', 'event_espresso')],
627
+			false,
628
+			'sentence'
629
+		);
630
+		$icon   = '';
631
+		switch ($this->status_ID()) {
632
+			case EEM_Transaction::complete_status_code:
633
+				$icon = $show_icons
634
+					? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
635
+					: '';
636
+				break;
637
+			case EEM_Transaction::incomplete_status_code:
638
+				$icon = $show_icons
639
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
640
+					: '';
641
+				break;
642
+			case EEM_Transaction::abandoned_status_code:
643
+				$icon = $show_icons
644
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>'
645
+					: '';
646
+				break;
647
+			case EEM_Transaction::failed_status_code:
648
+				$icon = $show_icons
649
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
650
+					: '';
651
+				break;
652
+			case EEM_Transaction::overpaid_status_code:
653
+				$icon = $show_icons
654
+					? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>'
655
+					: '';
656
+				break;
657
+		}
658
+		return $icon . $status[ $this->status_ID() ];
659
+	}
660
+
661
+
662
+	/**
663
+	 * get Transaction Status
664
+	 *
665
+	 * @return mixed
666
+	 * @throws EE_Error
667
+	 * @throws InvalidArgumentException
668
+	 * @throws InvalidDataTypeException
669
+	 * @throws InvalidInterfaceException
670
+	 * @throws ReflectionException
671
+	 */
672
+	public function status_ID()
673
+	{
674
+		return $this->get('STS_ID');
675
+	}
676
+
677
+
678
+	/**
679
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
680
+	 *
681
+	 * @return boolean
682
+	 * @throws EE_Error
683
+	 * @throws InvalidArgumentException
684
+	 * @throws InvalidDataTypeException
685
+	 * @throws InvalidInterfaceException
686
+	 * @throws ReflectionException
687
+	 */
688
+	public function is_free()
689
+	{
690
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
691
+	}
692
+
693
+
694
+	/**
695
+	 * Returns whether this transaction is complete
696
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
697
+	 *
698
+	 * @return boolean
699
+	 * @throws EE_Error
700
+	 * @throws InvalidArgumentException
701
+	 * @throws InvalidDataTypeException
702
+	 * @throws InvalidInterfaceException
703
+	 * @throws ReflectionException
704
+	 */
705
+	public function is_completed()
706
+	{
707
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
708
+	}
709
+
710
+
711
+	/**
712
+	 * Returns whether this transaction is incomplete
713
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
714
+	 *
715
+	 * @return boolean
716
+	 * @throws EE_Error
717
+	 * @throws InvalidArgumentException
718
+	 * @throws InvalidDataTypeException
719
+	 * @throws InvalidInterfaceException
720
+	 * @throws ReflectionException
721
+	 */
722
+	public function is_incomplete()
723
+	{
724
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
725
+	}
726
+
727
+
728
+	/**
729
+	 * Returns whether this transaction is overpaid
730
+	 * Useful in templates and other logic for deciding if monies need to be refunded
731
+	 *
732
+	 * @return boolean
733
+	 * @throws EE_Error
734
+	 * @throws InvalidArgumentException
735
+	 * @throws InvalidDataTypeException
736
+	 * @throws InvalidInterfaceException
737
+	 * @throws ReflectionException
738
+	 */
739
+	public function is_overpaid()
740
+	{
741
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
742
+	}
743
+
744
+
745
+	/**
746
+	 * Returns whether this transaction was abandoned
747
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
748
+	 * but that contact information exists for at least one registrant
749
+	 *
750
+	 * @return boolean
751
+	 * @throws EE_Error
752
+	 * @throws InvalidArgumentException
753
+	 * @throws InvalidDataTypeException
754
+	 * @throws InvalidInterfaceException
755
+	 * @throws ReflectionException
756
+	 */
757
+	public function is_abandoned()
758
+	{
759
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
760
+	}
761
+
762
+
763
+	/**
764
+	 * Returns whether this transaction failed
765
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
766
+	 * and that NO contact information exists for any registrants
767
+	 *
768
+	 * @return boolean
769
+	 * @throws EE_Error
770
+	 * @throws InvalidArgumentException
771
+	 * @throws InvalidDataTypeException
772
+	 * @throws InvalidInterfaceException
773
+	 * @throws ReflectionException
774
+	 */
775
+	public function failed()
776
+	{
777
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
778
+	}
779
+
780
+
781
+	/**
782
+	 * This returns the url for the invoice of this transaction
783
+	 *
784
+	 * @param string $type 'html' or 'pdf' (default is pdf)
785
+	 * @return string
786
+	 * @throws DomainException
787
+	 * @throws EE_Error
788
+	 * @throws InvalidArgumentException
789
+	 * @throws InvalidDataTypeException
790
+	 * @throws InvalidInterfaceException
791
+	 * @throws ReflectionException
792
+	 */
793
+	public function invoice_url($type = 'html')
794
+	{
795
+		$REG = $this->primary_registration();
796
+		if (! $REG instanceof EE_Registration) {
797
+			return '';
798
+		}
799
+		return $REG->invoice_url($type);
800
+	}
801
+
802
+
803
+	/**
804
+	 * Gets the primary registration only
805
+	 *
806
+	 * @return EE_Base_Class|EE_Registration
807
+	 * @throws EE_Error
808
+	 * @throws InvalidArgumentException
809
+	 * @throws InvalidDataTypeException
810
+	 * @throws InvalidInterfaceException
811
+	 * @throws ReflectionException
812
+	 */
813
+	public function primary_registration()
814
+	{
815
+		$registrations = (array) $this->get_many_related(
816
+			'Registration',
817
+			[['REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT]]
818
+		);
819
+		foreach ($registrations as $registration) {
820
+			// valid registration that is NOT cancelled or declined ?
821
+			if (
822
+				$registration instanceof EE_Registration
823
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
824
+			) {
825
+				return $registration;
826
+			}
827
+		}
828
+		// nothing valid found, so just return first thing from array of results
829
+		return reset($registrations);
830
+	}
831
+
832
+
833
+	/**
834
+	 * Gets the URL for viewing the receipt
835
+	 *
836
+	 * @param string $type 'pdf' or 'html' (default is 'html')
837
+	 * @return string
838
+	 * @throws DomainException
839
+	 * @throws EE_Error
840
+	 * @throws InvalidArgumentException
841
+	 * @throws InvalidDataTypeException
842
+	 * @throws InvalidInterfaceException
843
+	 * @throws ReflectionException
844
+	 */
845
+	public function receipt_url($type = 'html')
846
+	{
847
+		$REG = $this->primary_registration();
848
+		if (! $REG instanceof EE_Registration) {
849
+			return '';
850
+		}
851
+		return $REG->receipt_url($type);
852
+	}
853
+
854
+
855
+	/**
856
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
857
+	 * a query parameter
858
+	 *
859
+	 * @return string
860
+	 * @throws EE_Error
861
+	 * @throws InvalidArgumentException
862
+	 * @throws InvalidDataTypeException
863
+	 * @throws InvalidInterfaceException
864
+	 * @throws ReflectionException
865
+	 */
866
+	public function payment_overview_url()
867
+	{
868
+		$primary_registration = $this->primary_registration();
869
+		return $primary_registration instanceof EE_Registration
870
+			? $primary_registration->payment_overview_url()
871
+			: false;
872
+	}
873
+
874
+
875
+	/**
876
+	 * @return string
877
+	 * @throws EE_Error
878
+	 * @throws InvalidArgumentException
879
+	 * @throws InvalidDataTypeException
880
+	 * @throws InvalidInterfaceException
881
+	 * @throws ReflectionException
882
+	 */
883
+	public function gateway_response_on_transaction()
884
+	{
885
+		$payment = $this->get_first_related('Payment');
886
+		return $payment instanceof EE_Payment
887
+			? $payment->gateway_response()
888
+			: '';
889
+	}
890
+
891
+
892
+	/**
893
+	 * Get the status object of this object
894
+	 *
895
+	 * @return EE_Base_Class|EE_Status
896
+	 * @throws EE_Error
897
+	 * @throws InvalidArgumentException
898
+	 * @throws InvalidDataTypeException
899
+	 * @throws InvalidInterfaceException
900
+	 * @throws ReflectionException
901
+	 */
902
+	public function status_obj()
903
+	{
904
+		return $this->get_first_related('Status');
905
+	}
906
+
907
+
908
+	/**
909
+	 * Gets all the extra meta info on this payment
910
+	 *
911
+	 * @param array $query_params
912
+	 * @return EE_Base_Class[]|EE_Extra_Meta
913
+	 * @throws EE_Error
914
+	 * @throws InvalidArgumentException
915
+	 * @throws InvalidDataTypeException
916
+	 * @throws InvalidInterfaceException
917
+	 * @throws ReflectionException
918
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
919
+	 */
920
+	public function extra_meta($query_params = [])
921
+	{
922
+		return $this->get_many_related('Extra_Meta', $query_params);
923
+	}
924
+
925
+
926
+	/**
927
+	 * Wrapper for _add_relation_to
928
+	 *
929
+	 * @param EE_Registration $registration
930
+	 * @return EE_Base_Class the relation was added to
931
+	 * @throws EE_Error
932
+	 * @throws InvalidArgumentException
933
+	 * @throws InvalidDataTypeException
934
+	 * @throws InvalidInterfaceException
935
+	 * @throws ReflectionException
936
+	 */
937
+	public function add_registration(EE_Registration $registration)
938
+	{
939
+		return $this->_add_relation_to($registration, 'Registration');
940
+	}
941
+
942
+
943
+	/**
944
+	 * Removes the given registration from being related (even before saving this transaction).
945
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
946
+	 *
947
+	 * @param int $registration_or_id
948
+	 * @return EE_Base_Class that was removed from being related
949
+	 * @throws EE_Error
950
+	 * @throws InvalidArgumentException
951
+	 * @throws InvalidDataTypeException
952
+	 * @throws InvalidInterfaceException
953
+	 * @throws ReflectionException
954
+	 */
955
+	public function remove_registration_with_id($registration_or_id)
956
+	{
957
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
958
+	}
959
+
960
+
961
+	/**
962
+	 * Gets all the line items which are for ACTUAL items
963
+	 *
964
+	 * @return EE_Line_Item[]
965
+	 * @throws EE_Error
966
+	 * @throws InvalidArgumentException
967
+	 * @throws InvalidDataTypeException
968
+	 * @throws InvalidInterfaceException
969
+	 * @throws ReflectionException
970
+	 */
971
+	public function items_purchased()
972
+	{
973
+		return $this->line_items([['LIN_type' => EEM_Line_Item::type_line_item]]);
974
+	}
975
+
976
+
977
+	/**
978
+	 * Wrapper for _add_relation_to
979
+	 *
980
+	 * @param EE_Line_Item $line_item
981
+	 * @return EE_Base_Class the relation was added to
982
+	 * @throws EE_Error
983
+	 * @throws InvalidArgumentException
984
+	 * @throws InvalidDataTypeException
985
+	 * @throws InvalidInterfaceException
986
+	 * @throws ReflectionException
987
+	 */
988
+	public function add_line_item(EE_Line_Item $line_item)
989
+	{
990
+		return $this->_add_relation_to($line_item, 'Line_Item');
991
+	}
992
+
993
+
994
+	/**
995
+	 * Gets ALL the line items related to this transaction (unstructured)
996
+	 *
997
+	 * @param array $query_params
998
+	 * @return EE_Base_Class[]|EE_Line_Item[]
999
+	 * @throws EE_Error
1000
+	 * @throws InvalidArgumentException
1001
+	 * @throws InvalidDataTypeException
1002
+	 * @throws InvalidInterfaceException
1003
+	 * @throws ReflectionException
1004
+	 */
1005
+	public function line_items($query_params = [])
1006
+	{
1007
+		return $this->get_many_related('Line_Item', $query_params);
1008
+	}
1009
+
1010
+
1011
+	/**
1012
+	 * Gets all the line items which are taxes on the total
1013
+	 *
1014
+	 * @return EE_Line_Item[]
1015
+	 * @throws EE_Error
1016
+	 * @throws InvalidArgumentException
1017
+	 * @throws InvalidDataTypeException
1018
+	 * @throws InvalidInterfaceException
1019
+	 * @throws ReflectionException
1020
+	 */
1021
+	public function tax_items()
1022
+	{
1023
+		return $this->line_items([['LIN_type' => EEM_Line_Item::type_tax]]);
1024
+	}
1025
+
1026
+
1027
+	/**
1028
+	 * Gets the total line item (which is a parent of all other related line items,
1029
+	 * meaning it takes them all into account on its total)
1030
+	 *
1031
+	 * @param bool $create_if_not_found
1032
+	 * @return EE_Line_Item|null
1033
+	 * @throws EE_Error
1034
+	 * @throws InvalidArgumentException
1035
+	 * @throws InvalidDataTypeException
1036
+	 * @throws InvalidInterfaceException
1037
+	 * @throws ReflectionException
1038
+	 */
1039
+	public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1040
+	{
1041
+		$item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1042
+		if ($item instanceof EE_Line_Item) {
1043
+			return $item;
1044
+		}
1045
+		return $create_if_not_found
1046
+			? EEH_Line_Item::create_total_line_item($this)
1047
+			: null;
1048
+	}
1049
+
1050
+
1051
+	/**
1052
+	 * Returns the total amount of tax on this transaction
1053
+	 * (assumes there's only one tax subtotal line item)
1054
+	 *
1055
+	 * @return float
1056
+	 * @throws EE_Error
1057
+	 * @throws InvalidArgumentException
1058
+	 * @throws InvalidDataTypeException
1059
+	 * @throws InvalidInterfaceException
1060
+	 * @throws ReflectionException
1061
+	 */
1062
+	public function tax_total()
1063
+	{
1064
+		$tax_line_item = $this->tax_total_line_item();
1065
+		if ($tax_line_item) {
1066
+			return (float) $tax_line_item->total();
1067
+		}
1068
+		return (float) 0;
1069
+	}
1070
+
1071
+
1072
+	/**
1073
+	 * Gets the tax subtotal line item (assumes there's only one)
1074
+	 *
1075
+	 * @return EE_Line_Item
1076
+	 * @throws EE_Error
1077
+	 * @throws InvalidArgumentException
1078
+	 * @throws InvalidDataTypeException
1079
+	 * @throws InvalidInterfaceException
1080
+	 * @throws ReflectionException
1081
+	 */
1082
+	public function tax_total_line_item()
1083
+	{
1084
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1085
+	}
1086
+
1087
+
1088
+	/**
1089
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1090
+	 *
1091
+	 * @return EE_Form_Section_Proper
1092
+	 * @throws EE_Error
1093
+	 * @throws InvalidArgumentException
1094
+	 * @throws InvalidDataTypeException
1095
+	 * @throws InvalidInterfaceException
1096
+	 * @throws ReflectionException
1097
+	 */
1098
+	public function billing_info()
1099
+	{
1100
+		$payment_method = $this->payment_method();
1101
+		if (! $payment_method) {
1102
+			EE_Error::add_error(
1103
+				esc_html__(
1104
+					'Could not find billing info for transaction because no gateway has been used for it yet',
1105
+					'event_espresso'
1106
+				),
1107
+				__FILE__,
1108
+				__FUNCTION__,
1109
+				__LINE__
1110
+			);
1111
+			return null;
1112
+		}
1113
+		$primary_reg = $this->primary_registration();
1114
+		if (! $primary_reg) {
1115
+			EE_Error::add_error(
1116
+				esc_html__(
1117
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1118
+					'event_espresso'
1119
+				),
1120
+				__FILE__,
1121
+				__FUNCTION__,
1122
+				__LINE__
1123
+			);
1124
+			return null;
1125
+		}
1126
+		$attendee = $primary_reg->attendee();
1127
+		if (! $attendee) {
1128
+			EE_Error::add_error(
1129
+				esc_html__(
1130
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1131
+					'event_espresso'
1132
+				),
1133
+				__FILE__,
1134
+				__FUNCTION__,
1135
+				__LINE__
1136
+			);
1137
+			return null;
1138
+		}
1139
+		return $attendee->billing_info_for_payment_method($payment_method);
1140
+	}
1141
+
1142
+
1143
+	/**
1144
+	 * Gets PMD_ID
1145
+	 *
1146
+	 * @return int
1147
+	 * @throws EE_Error
1148
+	 * @throws InvalidArgumentException
1149
+	 * @throws InvalidDataTypeException
1150
+	 * @throws InvalidInterfaceException
1151
+	 * @throws ReflectionException
1152
+	 */
1153
+	public function payment_method_ID()
1154
+	{
1155
+		return $this->get('PMD_ID');
1156
+	}
1157
+
1158
+
1159
+	/**
1160
+	 * Sets PMD_ID
1161
+	 *
1162
+	 * @param int $PMD_ID
1163
+	 * @throws EE_Error
1164
+	 * @throws InvalidArgumentException
1165
+	 * @throws InvalidDataTypeException
1166
+	 * @throws InvalidInterfaceException
1167
+	 * @throws ReflectionException
1168
+	 */
1169
+	public function set_payment_method_ID($PMD_ID)
1170
+	{
1171
+		$this->set('PMD_ID', $PMD_ID);
1172
+	}
1173
+
1174
+
1175
+	/**
1176
+	 * Gets the last-used payment method on this transaction
1177
+	 * (we COULD just use the last-made payment, but some payment methods, namely
1178
+	 * offline ones, dont' create payments)
1179
+	 *
1180
+	 * @return EE_Payment_Method
1181
+	 * @throws EE_Error
1182
+	 * @throws InvalidArgumentException
1183
+	 * @throws InvalidDataTypeException
1184
+	 * @throws InvalidInterfaceException
1185
+	 * @throws ReflectionException
1186
+	 */
1187
+	public function payment_method()
1188
+	{
1189
+		$pm = $this->get_first_related('Payment_Method');
1190
+		if ($pm instanceof EE_Payment_Method) {
1191
+			return $pm;
1192
+		}
1193
+		$last_payment = $this->last_payment();
1194
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1195
+			return $last_payment->payment_method();
1196
+		}
1197
+		return null;
1198
+	}
1199
+
1200
+
1201
+	/**
1202
+	 * Gets the last payment made
1203
+	 *
1204
+	 * @return EE_Base_Class|EE_Payment|null
1205
+	 * @throws EE_Error
1206
+	 * @throws InvalidArgumentException
1207
+	 * @throws InvalidDataTypeException
1208
+	 * @throws InvalidInterfaceException
1209
+	 * @throws ReflectionException
1210
+	 */
1211
+	public function last_payment(): ?EE_Payment
1212
+	{
1213
+		return $this->get_first_related('Payment', ['order_by' => ['PAY_ID' => 'desc']]);
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * Gets all the line items which are unrelated to tickets on this transaction
1219
+	 *
1220
+	 * @return EE_Line_Item[]
1221
+	 * @throws EE_Error
1222
+	 * @throws InvalidArgumentException
1223
+	 * @throws InvalidDataTypeException
1224
+	 * @throws InvalidInterfaceException
1225
+	 * @throws ReflectionException
1226
+	 */
1227
+	public function non_ticket_line_items()
1228
+	{
1229
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1230
+	}
1231
+
1232
+
1233
+	/**
1234
+	 * possibly toggles TXN status
1235
+	 *
1236
+	 * @param boolean $update whether to save the TXN
1237
+	 * @return bool whether the TXN was saved
1238
+	 * @throws EE_Error
1239
+	 * @throws InvalidArgumentException
1240
+	 * @throws InvalidDataTypeException
1241
+	 * @throws InvalidInterfaceException
1242
+	 * @throws ReflectionException
1243
+	 * @throws RuntimeException
1244
+	 */
1245
+	public function update_status_based_on_total_paid($update = true)
1246
+	{
1247
+		// set transaction status based on comparison of TXN_paid vs TXN_total
1248
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1249
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
1250
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1251
+			$new_txn_status = EEM_Transaction::complete_status_code;
1252
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1253
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
1254
+		} else {
1255
+			throw new RuntimeException(
1256
+				esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1257
+			);
1258
+		}
1259
+		if ($new_txn_status !== $this->status_ID()) {
1260
+			$this->set_status($new_txn_status);
1261
+			if ($update) {
1262
+				return (bool) $this->save();
1263
+			}
1264
+		}
1265
+		return false;
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * Updates the transaction's status and total_paid based on all the payments
1271
+	 * that apply to it
1272
+	 *
1273
+	 * @return array|bool
1274
+	 * @throws EE_Error
1275
+	 * @throws InvalidArgumentException
1276
+	 * @throws ReflectionException
1277
+	 * @throws InvalidDataTypeException
1278
+	 * @throws InvalidInterfaceException
1279
+	 * @deprecated
1280
+	 */
1281
+	public function update_based_on_payments()
1282
+	{
1283
+		EE_Error::doing_it_wrong(
1284
+			__CLASS__ . '::' . __FUNCTION__,
1285
+			sprintf(
1286
+				esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1287
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1288
+			),
1289
+			'4.6.0'
1290
+		);
1291
+		/** @type EE_Transaction_Processor $transaction_processor */
1292
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1293
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1294
+	}
1295
+
1296
+
1297
+	/**
1298
+	 * @return string
1299
+	 */
1300
+	public function old_txn_status()
1301
+	{
1302
+		return $this->_old_txn_status;
1303
+	}
1304
+
1305
+
1306
+	/**
1307
+	 * @param string $old_txn_status
1308
+	 */
1309
+	public function set_old_txn_status($old_txn_status)
1310
+	{
1311
+		// only set the first time
1312
+		if ($this->_old_txn_status === null) {
1313
+			$this->_old_txn_status = $old_txn_status;
1314
+		}
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * reg_status_updated
1320
+	 *
1321
+	 * @return bool
1322
+	 * @throws EE_Error
1323
+	 * @throws InvalidArgumentException
1324
+	 * @throws InvalidDataTypeException
1325
+	 * @throws InvalidInterfaceException
1326
+	 * @throws ReflectionException
1327
+	 */
1328
+	public function txn_status_updated()
1329
+	{
1330
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1331
+	}
1332
+
1333
+
1334
+	/**
1335
+	 * _reg_steps_completed
1336
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1337
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1338
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1339
+	 *
1340
+	 * @param string $reg_step_slug
1341
+	 * @param bool   $check_all
1342
+	 * @return bool|int
1343
+	 * @throws EE_Error
1344
+	 * @throws InvalidArgumentException
1345
+	 * @throws InvalidDataTypeException
1346
+	 * @throws InvalidInterfaceException
1347
+	 * @throws ReflectionException
1348
+	 */
1349
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1350
+	{
1351
+		$reg_steps = $this->reg_steps();
1352
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1353
+			return false;
1354
+		}
1355
+		// loop thru reg steps array)
1356
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1357
+			// if NOT checking ALL steps (only checking one step)
1358
+			if (! $check_all) {
1359
+				// and this is the one
1360
+				if ($slug === $reg_step_slug) {
1361
+					return $reg_step_completed;
1362
+				}
1363
+				// skip to next reg step in loop
1364
+				continue;
1365
+			}
1366
+			// $check_all must be true, else we would never have gotten to this point
1367
+			if ($slug === $reg_step_slug) {
1368
+				// if we reach this point, then we are testing either:
1369
+				// all_reg_steps_completed_except() or
1370
+				// all_reg_steps_completed_except_final_step(),
1371
+				// and since this is the reg step EXCEPTION being tested
1372
+				// we want to return true (yes true) if this reg step is NOT completed
1373
+				// ie: "is everything completed except the final step?"
1374
+				// "that is correct... the final step is not completed, but all others are."
1375
+				return $reg_step_completed !== true;
1376
+			}
1377
+			if ($reg_step_completed !== true) {
1378
+				// if any reg step is NOT completed, then ALL steps are not completed
1379
+				return false;
1380
+			}
1381
+		}
1382
+		return true;
1383
+	}
1384
+
1385
+
1386
+	/**
1387
+	 * all_reg_steps_completed
1388
+	 * returns:
1389
+	 *    true if ALL reg steps have been marked as completed
1390
+	 *        or false if any step is not completed
1391
+	 *
1392
+	 * @return bool
1393
+	 * @throws EE_Error
1394
+	 * @throws InvalidArgumentException
1395
+	 * @throws InvalidDataTypeException
1396
+	 * @throws InvalidInterfaceException
1397
+	 * @throws ReflectionException
1398
+	 */
1399
+	public function all_reg_steps_completed()
1400
+	{
1401
+		return $this->_reg_steps_completed();
1402
+	}
1403
+
1404
+
1405
+	/**
1406
+	 * all_reg_steps_completed_except
1407
+	 * returns:
1408
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1409
+	 *        or false if any other step is not completed
1410
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1411
+	 *
1412
+	 * @param string $exception
1413
+	 * @return bool
1414
+	 * @throws EE_Error
1415
+	 * @throws InvalidArgumentException
1416
+	 * @throws InvalidDataTypeException
1417
+	 * @throws InvalidInterfaceException
1418
+	 * @throws ReflectionException
1419
+	 */
1420
+	public function all_reg_steps_completed_except($exception = '')
1421
+	{
1422
+		return $this->_reg_steps_completed($exception);
1423
+	}
1424
+
1425
+
1426
+	/**
1427
+	 * all_reg_steps_completed_except
1428
+	 * returns:
1429
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1430
+	 *        or false if any step is not completed
1431
+	 *    or false if ALL steps are completed including the final step !!!
1432
+	 *
1433
+	 * @return bool
1434
+	 * @throws EE_Error
1435
+	 * @throws InvalidArgumentException
1436
+	 * @throws InvalidDataTypeException
1437
+	 * @throws InvalidInterfaceException
1438
+	 * @throws ReflectionException
1439
+	 */
1440
+	public function all_reg_steps_completed_except_final_step()
1441
+	{
1442
+		return $this->_reg_steps_completed('finalize_registration');
1443
+	}
1444
+
1445
+
1446
+	/**
1447
+	 * reg_step_completed
1448
+	 * returns:
1449
+	 *    true if a specific reg step has been marked as completed
1450
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1451
+	 *    or false if it has not yet been initialized
1452
+	 *
1453
+	 * @param string $reg_step_slug
1454
+	 * @return bool|int
1455
+	 * @throws EE_Error
1456
+	 * @throws InvalidArgumentException
1457
+	 * @throws InvalidDataTypeException
1458
+	 * @throws InvalidInterfaceException
1459
+	 * @throws ReflectionException
1460
+	 */
1461
+	public function reg_step_completed($reg_step_slug)
1462
+	{
1463
+		return $this->_reg_steps_completed($reg_step_slug, false);
1464
+	}
1465
+
1466
+
1467
+	/**
1468
+	 * completed_final_reg_step
1469
+	 * returns:
1470
+	 *    true if the finalize_registration reg step has been marked as completed
1471
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1472
+	 *    or false if it has not yet been initialized
1473
+	 *
1474
+	 * @return bool|int
1475
+	 * @throws EE_Error
1476
+	 * @throws InvalidArgumentException
1477
+	 * @throws InvalidDataTypeException
1478
+	 * @throws InvalidInterfaceException
1479
+	 * @throws ReflectionException
1480
+	 */
1481
+	public function final_reg_step_completed()
1482
+	{
1483
+		return $this->_reg_steps_completed('finalize_registration', false);
1484
+	}
1485
+
1486
+
1487
+	/**
1488
+	 * set_reg_step_initiated
1489
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1490
+	 *
1491
+	 * @param string $reg_step_slug
1492
+	 * @return boolean
1493
+	 * @throws EE_Error
1494
+	 * @throws InvalidArgumentException
1495
+	 * @throws InvalidDataTypeException
1496
+	 * @throws InvalidInterfaceException
1497
+	 * @throws ReflectionException
1498
+	 */
1499
+	public function set_reg_step_initiated($reg_step_slug)
1500
+	{
1501
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1502
+	}
1503
+
1504
+
1505
+	/**
1506
+	 * set_reg_step_completed
1507
+	 * given a valid TXN_reg_step, this sets the step as completed
1508
+	 *
1509
+	 * @param string $reg_step_slug
1510
+	 * @return boolean
1511
+	 * @throws EE_Error
1512
+	 * @throws InvalidArgumentException
1513
+	 * @throws InvalidDataTypeException
1514
+	 * @throws InvalidInterfaceException
1515
+	 * @throws ReflectionException
1516
+	 */
1517
+	public function set_reg_step_completed($reg_step_slug)
1518
+	{
1519
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1520
+	}
1521
+
1522
+
1523
+	/**
1524
+	 * set_reg_step_completed
1525
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1526
+	 *
1527
+	 * @param string $reg_step_slug
1528
+	 * @return boolean
1529
+	 * @throws EE_Error
1530
+	 * @throws InvalidArgumentException
1531
+	 * @throws InvalidDataTypeException
1532
+	 * @throws InvalidInterfaceException
1533
+	 * @throws ReflectionException
1534
+	 */
1535
+	public function set_reg_step_not_completed($reg_step_slug)
1536
+	{
1537
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1538
+	}
1539
+
1540
+
1541
+	/**
1542
+	 * set_reg_step_completed
1543
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1544
+	 *
1545
+	 * @param string      $reg_step_slug
1546
+	 * @param boolean|int $status
1547
+	 * @return boolean
1548
+	 * @throws EE_Error
1549
+	 * @throws InvalidArgumentException
1550
+	 * @throws InvalidDataTypeException
1551
+	 * @throws InvalidInterfaceException
1552
+	 * @throws ReflectionException
1553
+	 */
1554
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1555
+	{
1556
+		// validate status
1557
+		$status = is_bool($status) || is_int($status)
1558
+			? $status
1559
+			: false;
1560
+		// get reg steps array
1561
+		$txn_reg_steps = $this->reg_steps();
1562
+		// if reg step does NOT exist
1563
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1564
+			return false;
1565
+		}
1566
+		// if  we're trying to complete a step that is already completed
1567
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1568
+			return true;
1569
+		}
1570
+		// if  we're trying to complete a step that hasn't even started
1571
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1572
+			return false;
1573
+		}
1574
+		// if current status value matches the incoming value (no change)
1575
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1576
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1577
+			// this will happen in cases where multiple AJAX requests occur during the same step
1578
+			return true;
1579
+		}
1580
+		// if we're trying to set a start time, but it has already been set...
1581
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1582
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1583
+			return true;
1584
+		}
1585
+		// update completed status
1586
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1587
+		$this->set_reg_steps($txn_reg_steps);
1588
+		$this->save();
1589
+		return true;
1590
+	}
1591
+
1592
+
1593
+	/**
1594
+	 * remove_reg_step
1595
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1596
+	 * the reg step from the TXN reg step array
1597
+	 *
1598
+	 * @param string $reg_step_slug
1599
+	 * @return void
1600
+	 * @throws EE_Error
1601
+	 * @throws InvalidArgumentException
1602
+	 * @throws InvalidDataTypeException
1603
+	 * @throws InvalidInterfaceException
1604
+	 * @throws ReflectionException
1605
+	 */
1606
+	public function remove_reg_step($reg_step_slug)
1607
+	{
1608
+		// get reg steps array
1609
+		$txn_reg_steps = $this->reg_steps();
1610
+		unset($txn_reg_steps[ $reg_step_slug ]);
1611
+		$this->set_reg_steps($txn_reg_steps);
1612
+	}
1613
+
1614
+
1615
+	/**
1616
+	 * toggle_failed_transaction_status
1617
+	 * upgrades a TXNs status from failed to abandoned,
1618
+	 * meaning that contact information has been captured for at least one registrant
1619
+	 *
1620
+	 * @param bool $save
1621
+	 * @return bool
1622
+	 * @throws EE_Error
1623
+	 * @throws InvalidArgumentException
1624
+	 * @throws InvalidDataTypeException
1625
+	 * @throws InvalidInterfaceException
1626
+	 * @throws ReflectionException
1627
+	 */
1628
+	public function toggle_failed_transaction_status($save = true)
1629
+	{
1630
+		// if TXN status is still set as "failed"...
1631
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1632
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1633
+			if ($save) {
1634
+				$this->save();
1635
+			}
1636
+			return true;
1637
+		}
1638
+		return false;
1639
+	}
1640
+
1641
+
1642
+	/**
1643
+	 * toggle_abandoned_transaction_status
1644
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1645
+	 *
1646
+	 * @return bool
1647
+	 * @throws EE_Error
1648
+	 * @throws InvalidArgumentException
1649
+	 * @throws InvalidDataTypeException
1650
+	 * @throws InvalidInterfaceException
1651
+	 * @throws ReflectionException
1652
+	 */
1653
+	public function toggle_abandoned_transaction_status()
1654
+	{
1655
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1656
+		$txn_status = $this->status_ID();
1657
+		if (
1658
+			$txn_status === EEM_Transaction::failed_status_code
1659
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1660
+		) {
1661
+			// if a contact record for the primary registrant has been created
1662
+			if (
1663
+				$this->primary_registration() instanceof EE_Registration
1664
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1665
+			) {
1666
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1667
+			} else {
1668
+				// no contact record? yer abandoned!
1669
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1670
+			}
1671
+			return true;
1672
+		}
1673
+		return false;
1674
+	}
1675
+
1676
+
1677
+	/**
1678
+	 * checks if an Abandoned TXN has any related payments, and if so,
1679
+	 * updates the TXN status based on the amount paid
1680
+	 *
1681
+	 * @throws EE_Error
1682
+	 * @throws InvalidArgumentException
1683
+	 * @throws InvalidDataTypeException
1684
+	 * @throws InvalidInterfaceException
1685
+	 * @throws ReflectionException
1686
+	 * @throws RuntimeException
1687
+	 * @throws ReflectionException
1688
+	 */
1689
+	public function verify_abandoned_transaction_status()
1690
+	{
1691
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1692
+			return;
1693
+		}
1694
+		$payments = $this->get_many_related('Payment');
1695
+		if (! empty($payments)) {
1696
+			foreach ($payments as $payment) {
1697
+				if ($payment instanceof EE_Payment) {
1698
+					// kk this TXN should NOT be abandoned
1699
+					$this->update_status_based_on_total_paid();
1700
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1701
+						EE_Error::add_attention(
1702
+							sprintf(
1703
+								esc_html__(
1704
+									'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.',
1705
+									'event_espresso'
1706
+								),
1707
+								$this->ID(),
1708
+								$this->pretty_status()
1709
+							)
1710
+						);
1711
+					}
1712
+					// get final reg step status
1713
+					$finalized = $this->final_reg_step_completed();
1714
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1715
+					// but has not yet been fully completed (TRUE)
1716
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1717
+						$this->set_reg_step_completed('finalize_registration');
1718
+						$this->save();
1719
+					}
1720
+				}
1721
+			}
1722
+		}
1723
+	}
1724
+
1725
+
1726
+	/**
1727
+	 * @throws EE_Error
1728
+	 * @throws InvalidArgumentException
1729
+	 * @throws InvalidDataTypeException
1730
+	 * @throws InvalidInterfaceException
1731
+	 * @throws ReflectionException
1732
+	 * @throws RuntimeException
1733
+	 * @since 4.10.4.p
1734
+	 */
1735
+	public function recalculateLineItems()
1736
+	{
1737
+		$total_line_item = $this->total_line_item(false);
1738
+		if ($total_line_item instanceof EE_Line_Item) {
1739
+			EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1740
+			return EEH_Line_Item::apply_taxes($total_line_item, true);
1741
+		}
1742
+		return false;
1743
+	}
1744 1744
 }
Please login to merge, or discard this patch.
Spacing   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@  discard block
 block discarded – undo
46 46
         $txn        = $has_object
47 47
             ? $has_object
48 48
             : new self($props_n_values, false, $timezone, $date_formats);
49
-        if (! $has_object) {
49
+        if ( ! $has_object) {
50 50
             $txn->set_old_txn_status($txn->status_ID());
51 51
         }
52 52
         return $txn;
@@ -86,7 +86,7 @@  discard block
 block discarded – undo
86 86
     public function lock()
87 87
     {
88 88
         // attempt to set lock, but if that fails...
89
-        if (! $this->add_extra_meta('lock', time(), true)) {
89
+        if ( ! $this->add_extra_meta('lock', time(), true)) {
90 90
             // then attempt to remove the lock in case it is expired
91 91
             if ($this->_remove_expired_lock()) {
92 92
                 // if removal was successful, then try setting lock again
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
     public function is_locked()
143 143
     {
144 144
         // if TXN is not locked, then return false immediately
145
-        if (! $this->_get_lock()) {
145
+        if ( ! $this->_get_lock()) {
146 146
             return false;
147 147
         }
148 148
         // if not, then let's try and remove the lock in case it's expired...
@@ -627,7 +627,7 @@  discard block
 block discarded – undo
627 627
             false,
628 628
             'sentence'
629 629
         );
630
-        $icon   = '';
630
+        $icon = '';
631 631
         switch ($this->status_ID()) {
632 632
             case EEM_Transaction::complete_status_code:
633 633
                 $icon = $show_icons
@@ -655,7 +655,7 @@  discard block
 block discarded – undo
655 655
                     : '';
656 656
                 break;
657 657
         }
658
-        return $icon . $status[ $this->status_ID() ];
658
+        return $icon.$status[$this->status_ID()];
659 659
     }
660 660
 
661 661
 
@@ -793,7 +793,7 @@  discard block
 block discarded – undo
793 793
     public function invoice_url($type = 'html')
794 794
     {
795 795
         $REG = $this->primary_registration();
796
-        if (! $REG instanceof EE_Registration) {
796
+        if ( ! $REG instanceof EE_Registration) {
797 797
             return '';
798 798
         }
799 799
         return $REG->invoice_url($type);
@@ -845,7 +845,7 @@  discard block
 block discarded – undo
845 845
     public function receipt_url($type = 'html')
846 846
     {
847 847
         $REG = $this->primary_registration();
848
-        if (! $REG instanceof EE_Registration) {
848
+        if ( ! $REG instanceof EE_Registration) {
849 849
             return '';
850 850
         }
851 851
         return $REG->receipt_url($type);
@@ -1098,7 +1098,7 @@  discard block
 block discarded – undo
1098 1098
     public function billing_info()
1099 1099
     {
1100 1100
         $payment_method = $this->payment_method();
1101
-        if (! $payment_method) {
1101
+        if ( ! $payment_method) {
1102 1102
             EE_Error::add_error(
1103 1103
                 esc_html__(
1104 1104
                     'Could not find billing info for transaction because no gateway has been used for it yet',
@@ -1111,7 +1111,7 @@  discard block
 block discarded – undo
1111 1111
             return null;
1112 1112
         }
1113 1113
         $primary_reg = $this->primary_registration();
1114
-        if (! $primary_reg) {
1114
+        if ( ! $primary_reg) {
1115 1115
             EE_Error::add_error(
1116 1116
                 esc_html__(
1117 1117
                     'Cannot get billing info for gateway %s on transaction because no primary registration exists',
@@ -1124,7 +1124,7 @@  discard block
 block discarded – undo
1124 1124
             return null;
1125 1125
         }
1126 1126
         $attendee = $primary_reg->attendee();
1127
-        if (! $attendee) {
1127
+        if ( ! $attendee) {
1128 1128
             EE_Error::add_error(
1129 1129
                 esc_html__(
1130 1130
                     'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
@@ -1281,7 +1281,7 @@  discard block
 block discarded – undo
1281 1281
     public function update_based_on_payments()
1282 1282
     {
1283 1283
         EE_Error::doing_it_wrong(
1284
-            __CLASS__ . '::' . __FUNCTION__,
1284
+            __CLASS__.'::'.__FUNCTION__,
1285 1285
             sprintf(
1286 1286
                 esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1287 1287
                 'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
@@ -1349,13 +1349,13 @@  discard block
 block discarded – undo
1349 1349
     private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1350 1350
     {
1351 1351
         $reg_steps = $this->reg_steps();
1352
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1352
+        if ( ! is_array($reg_steps) || empty($reg_steps)) {
1353 1353
             return false;
1354 1354
         }
1355 1355
         // loop thru reg steps array)
1356 1356
         foreach ($reg_steps as $slug => $reg_step_completed) {
1357 1357
             // if NOT checking ALL steps (only checking one step)
1358
-            if (! $check_all) {
1358
+            if ( ! $check_all) {
1359 1359
                 // and this is the one
1360 1360
                 if ($slug === $reg_step_slug) {
1361 1361
                     return $reg_step_completed;
@@ -1560,30 +1560,30 @@  discard block
 block discarded – undo
1560 1560
         // get reg steps array
1561 1561
         $txn_reg_steps = $this->reg_steps();
1562 1562
         // if reg step does NOT exist
1563
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1563
+        if ( ! isset($txn_reg_steps[$reg_step_slug])) {
1564 1564
             return false;
1565 1565
         }
1566 1566
         // if  we're trying to complete a step that is already completed
1567
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1567
+        if ($txn_reg_steps[$reg_step_slug] === true) {
1568 1568
             return true;
1569 1569
         }
1570 1570
         // if  we're trying to complete a step that hasn't even started
1571
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1571
+        if ($status === true && $txn_reg_steps[$reg_step_slug] === false) {
1572 1572
             return false;
1573 1573
         }
1574 1574
         // if current status value matches the incoming value (no change)
1575 1575
         // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1576
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1576
+        if ((int) $txn_reg_steps[$reg_step_slug] === (int) $status) {
1577 1577
             // this will happen in cases where multiple AJAX requests occur during the same step
1578 1578
             return true;
1579 1579
         }
1580 1580
         // if we're trying to set a start time, but it has already been set...
1581
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1581
+        if (is_numeric($status) && is_numeric($txn_reg_steps[$reg_step_slug])) {
1582 1582
             // skip the update below, but don't return FALSE so that errors won't be displayed
1583 1583
             return true;
1584 1584
         }
1585 1585
         // update completed status
1586
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1586
+        $txn_reg_steps[$reg_step_slug] = $status;
1587 1587
         $this->set_reg_steps($txn_reg_steps);
1588 1588
         $this->save();
1589 1589
         return true;
@@ -1607,7 +1607,7 @@  discard block
 block discarded – undo
1607 1607
     {
1608 1608
         // get reg steps array
1609 1609
         $txn_reg_steps = $this->reg_steps();
1610
-        unset($txn_reg_steps[ $reg_step_slug ]);
1610
+        unset($txn_reg_steps[$reg_step_slug]);
1611 1611
         $this->set_reg_steps($txn_reg_steps);
1612 1612
     }
1613 1613
 
@@ -1692,12 +1692,12 @@  discard block
 block discarded – undo
1692 1692
             return;
1693 1693
         }
1694 1694
         $payments = $this->get_many_related('Payment');
1695
-        if (! empty($payments)) {
1695
+        if ( ! empty($payments)) {
1696 1696
             foreach ($payments as $payment) {
1697 1697
                 if ($payment instanceof EE_Payment) {
1698 1698
                     // kk this TXN should NOT be abandoned
1699 1699
                     $this->update_status_based_on_total_paid();
1700
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1700
+                    if ( ! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1701 1701
                         EE_Error::add_attention(
1702 1702
                             sprintf(
1703 1703
                                 esc_html__(
Please login to merge, or discard this patch.
core/db_classes/EE_Extra_Join.class.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -11,25 +11,25 @@
 block discarded – undo
11 11
  */
12 12
 class EE_Extra_Join extends EE_Base_Class
13 13
 {
14
-    /**
15
-     * @param array $props_n_values
16
-     * @param null  $timezone
17
-     * @return EE_Extra_Join|mixed
18
-     */
19
-    public static function new_instance($props_n_values = [], $timezone = '')
20
-    {
21
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
22
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone);
23
-    }
14
+	/**
15
+	 * @param array $props_n_values
16
+	 * @param null  $timezone
17
+	 * @return EE_Extra_Join|mixed
18
+	 */
19
+	public static function new_instance($props_n_values = [], $timezone = '')
20
+	{
21
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone);
22
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone);
23
+	}
24 24
 
25 25
 
26
-    /**
27
-     * @param array $props_n_values
28
-     * @param null  $timezone
29
-     * @return EE_Extra_Join
30
-     */
31
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
32
-    {
33
-        return new self($props_n_values, true, $timezone);
34
-    }
26
+	/**
27
+	 * @param array $props_n_values
28
+	 * @param null  $timezone
29
+	 * @return EE_Extra_Join
30
+	 */
31
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
32
+	{
33
+		return new self($props_n_values, true, $timezone);
34
+	}
35 35
 }
Please login to merge, or discard this patch.