Completed
Branch master (8cdeac)
by
unknown
06:44
created
espresso.php 1 patch
Indentation   +107 added lines, -107 removed lines patch added patch discarded remove patch
@@ -37,138 +37,138 @@
 block discarded – undo
37 37
  * @since       4.0
38 38
  */
39 39
 if (function_exists('espresso_version')) {
40
-    if (! function_exists('espresso_duplicate_plugin_error')) {
41
-        /**
42
-         *    espresso_duplicate_plugin_error
43
-         *    displays if more than one version of EE is activated at the same time.
44
-         */
45
-        function espresso_duplicate_plugin_error()
46
-        {
47
-            ?>
40
+	if (! function_exists('espresso_duplicate_plugin_error')) {
41
+		/**
42
+		 *    espresso_duplicate_plugin_error
43
+		 *    displays if more than one version of EE is activated at the same time.
44
+		 */
45
+		function espresso_duplicate_plugin_error()
46
+		{
47
+			?>
48 48
 <div class="error">
49 49
     <p>
50 50
         <?php
51
-                    echo esc_html__(
52
-                        'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
-                        'event_espresso'
54
-                    ); ?>
51
+					echo esc_html__(
52
+						'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
+						'event_espresso'
54
+					); ?>
55 55
     </p>
56 56
 </div>
57 57
 <?php
58
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
59
-        }
60
-    }
61
-    add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
58
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
59
+		}
60
+	}
61
+	add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
62 62
 } else {
63
-    define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
-        /**
66
-         * espresso_minimum_php_version_error
67
-         *
68
-         * @return void
69
-         */
70
-        function espresso_minimum_php_version_error()
71
-        {
72
-            ?>
63
+	define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
+	if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
+		/**
66
+		 * espresso_minimum_php_version_error
67
+		 *
68
+		 * @return void
69
+		 */
70
+		function espresso_minimum_php_version_error()
71
+		{
72
+			?>
73 73
 <div class="error">
74 74
     <p>
75 75
         <?php
76
-                    printf(
77
-                        esc_html__(
78
-                            'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
-                            'event_espresso'
80
-                        ),
81
-                        EE_MIN_PHP_VER_REQUIRED,
82
-                        PHP_VERSION,
83
-                        '<br/>',
84
-                        '<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
-                    );
86
-        ?>
76
+					printf(
77
+						esc_html__(
78
+							'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
+							'event_espresso'
80
+						),
81
+						EE_MIN_PHP_VER_REQUIRED,
82
+						PHP_VERSION,
83
+						'<br/>',
84
+						'<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
+					);
86
+		?>
87 87
     </p>
88 88
 </div>
89 89
 <?php
90
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
91
-        }
90
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
91
+		}
92 92
 
93
-        add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
-    } else {
95
-        define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
93
+		add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
+	} else {
95
+		define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
96 96
 
97
-        require_once __DIR__ . '/vendor/autoload.php';
97
+		require_once __DIR__ . '/vendor/autoload.php';
98 98
 
99
-        /**
100
-         * espresso_version
101
-         * Returns the plugin version
102
-         *
103
-         * @return string
104
-         */
105
-        function espresso_version(): string
106
-        {
107
-            return apply_filters('FHEE__espresso__espresso_version', '5.0.33.rc.000');
108
-        }
99
+		/**
100
+		 * espresso_version
101
+		 * Returns the plugin version
102
+		 *
103
+		 * @return string
104
+		 */
105
+		function espresso_version(): string
106
+		{
107
+			return apply_filters('FHEE__espresso__espresso_version', '5.0.33.rc.000');
108
+		}
109 109
 
110
-        /**
111
-         * espresso_plugin_activation
112
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
-         */
114
-        function espresso_plugin_activation()
115
-        {
116
-            update_option('ee_espresso_activation', true);
117
-            update_option('event-espresso-core_allow_tracking', 'no');
118
-            update_option('event-espresso-core_tracking_notice', 'hide');
119
-            // Run WP GraphQL activation callback
120
-            espressoLoadWpGraphQL();
121
-            graphql_activation_callback();
122
-        }
110
+		/**
111
+		 * espresso_plugin_activation
112
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
+		 */
114
+		function espresso_plugin_activation()
115
+		{
116
+			update_option('ee_espresso_activation', true);
117
+			update_option('event-espresso-core_allow_tracking', 'no');
118
+			update_option('event-espresso-core_tracking_notice', 'hide');
119
+			// Run WP GraphQL activation callback
120
+			espressoLoadWpGraphQL();
121
+			graphql_activation_callback();
122
+		}
123 123
 
124
-        register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
124
+		register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
125 125
 
126
-        /**
127
-         * espresso_plugin_deactivation
128
-         */
129
-        function espresso_plugin_deactivation()
130
-        {
131
-            // Run WP GraphQL deactivation callback
132
-            espressoLoadWpGraphQL();
133
-            graphql_deactivation_callback();
134
-            delete_option('event-espresso-core_allow_tracking');
135
-            delete_option('event-espresso-core_tracking_notice');
136
-        }
137
-        register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
126
+		/**
127
+		 * espresso_plugin_deactivation
128
+		 */
129
+		function espresso_plugin_deactivation()
130
+		{
131
+			// Run WP GraphQL deactivation callback
132
+			espressoLoadWpGraphQL();
133
+			graphql_deactivation_callback();
134
+			delete_option('event-espresso-core_allow_tracking');
135
+			delete_option('event-espresso-core_tracking_notice');
136
+		}
137
+		register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
138 138
 
139
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
140
-        bootstrap_espresso();
141
-    }
139
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
140
+		bootstrap_espresso();
141
+	}
142 142
 }
143 143
 
144 144
 if (! function_exists('espresso_deactivate_plugin')) {
145
-    /**
146
-     *    deactivate_plugin
147
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
-     *
149
-     * @access public
150
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
-     * @return    void
152
-     */
153
-    function espresso_deactivate_plugin(string $plugin_basename = '')
154
-    {
155
-        if (! function_exists('deactivate_plugins')) {
156
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
-        }
158
-        unset($_GET['activate'], $_REQUEST['activate']);
159
-        deactivate_plugins($plugin_basename);
160
-    }
145
+	/**
146
+	 *    deactivate_plugin
147
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
+	 *
149
+	 * @access public
150
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
+	 * @return    void
152
+	 */
153
+	function espresso_deactivate_plugin(string $plugin_basename = '')
154
+	{
155
+		if (! function_exists('deactivate_plugins')) {
156
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
+		}
158
+		unset($_GET['activate'], $_REQUEST['activate']);
159
+		deactivate_plugins($plugin_basename);
160
+	}
161 161
 }
162 162
 
163 163
 
164 164
 if (! function_exists('espressoLoadWpGraphQL')) {
165
-    function espressoLoadWpGraphQL()
166
-    {
167
-        if (
168
-            ! class_exists('WPGraphQL')
169
-            && is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
-        ) {
171
-            require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
-        }
173
-    }
165
+	function espressoLoadWpGraphQL()
166
+	{
167
+		if (
168
+			! class_exists('WPGraphQL')
169
+			&& is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
+		) {
171
+			require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
+		}
173
+	}
174 174
 }
Please login to merge, or discard this patch.
core/domain/services/service_changes/PaymentMethodDeprecations2025.php 2 patches
Indentation   +516 added lines, -516 removed lines patch added patch discarded remove patch
@@ -26,525 +26,525 @@
 block discarded – undo
26 26
  */
27 27
 class PaymentMethodDeprecations2025
28 28
 {
29
-    public const  PM_DEPRECATION_DATE       = '2025-04-15';
30
-
31
-    private const NOTIFICATION_DATE_WARNING = '2025-01-15';
32
-
33
-    private const NOTIFICATION_DATE_URGENT  = '2025-03-15';
34
-
35
-    private const NOTIFICATION_NAME_PREFIX  = 'pm-deprecations-2025-';
36
-
37
-    private const OPTION_NAME               = 'ee_pm_deprecations_2025';
38
-
39
-    private const TYPE_INITIAL              = 'initial';
40
-
41
-    private const TYPE_WARNING              = 'warning';
42
-
43
-    private const TYPE_URGENT               = 'urgent';
44
-
45
-    private const TYPE_FINAL                = 'final';
46
-
47
-    private const URL_ACCEPT                = 'https://eventespresso.com/product/eea-authorizenet-accept/';
48
-
49
-    private const URL_STRIPE                = 'https://eventespresso.com/product/eea-stripe-gateway/';
50
-
51
-    private const URL_SQUARE                = 'https://eventespresso.com/product/eea-square-gateway/';
52
-
53
-    private const URL_SUPPORT_DOCS          = 'https://support.eventespresso.com/article/612-deprecated-payment-methods-2025';
54
-
55
-
56
-
57
-    private EEM_Payment_Method $payment_method_model;
58
-
59
-    private ?array $active_deprecated_payment_methods = null;
60
-
61
-    private array $deprecated_payment_methods = [
62
-        'aim'                  => 'aim',
63
-        'authorizenet_sim'     => 'authorizenet_sim',
64
-        'braintree_dropin'     => 'braintree_dropin',
65
-        'payflow_pro_onsite'   => 'payflow_pro_onsite',
66
-        'paypal_express'       => 'paypal_express',
67
-        'paypal_pro'           => 'paypal_pro',
68
-        'paypal_smart_buttons' => 'paypal_smart_buttons',
69
-        'paypal_standard'      => 'paypal_standard',
70
-    ];
71
-
72
-    private Datetime $deprecation_date;
73
-
74
-    private Datetime $now;
75
-
76
-
77
-    /**
78
-     * @param EEM_Payment_Method $payment_method_model
79
-     */
80
-    public function __construct(EEM_Payment_Method $payment_method_model)
81
-    {
82
-        $this->payment_method_model = $payment_method_model;
83
-        $this->deprecation_date     = new DateTime(self::PM_DEPRECATION_DATE);
84
-        $this->now                  = new Datetime('now');
85
-        // TODO: remove the following comments before release
86
-        // uncomment next line to trigger warning notice
87
-        // $this->now = new Datetime('2025-02-20'); // Feb 20, 2025
88
-        // uncomment next line to trigger urgent notice
89
-        // $this->now = new Datetime('2025-03-20'); // Mar 20, 2025
90
-        // uncomment next line to trigger final notice & PM deactivation
91
-        // $this->now = new Datetime('2025-04-20'); // Apr 20, 2025
92
-        // uncomment the following block of code to "unhide" PMs
93
-        // add_filter(
94
-        //     'FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide',
95
-        //     fn() => []
96
-        // );
97
-        // uncomment the following to display ALL notices regardless of dates
98
-        // $this->displayNotification(self::TYPE_INITIAL);
99
-        // $this->displayNotification(self::TYPE_WARNING);
100
-        // $this->displayNotification(self::TYPE_URGENT);
101
-        // $this->displayNotification(self::TYPE_FINAL);
102
-    }
103
-
104
-
105
-    /**
106
-     * @return void
107
-     * @throws EE_Error
108
-     * @throws ReflectionException
109
-     */
110
-    public function setHooks()
111
-    {
112
-        // giving customers more time to migrate,
113
-        if ($this->now < new Datetime(self::NOTIFICATION_DATE_WARNING)) {
114
-            $this->deleteNotifications();
115
-        }
116
-        $this->deactivateDeprecatedPaymentMethods();
117
-        // don't bother if we're 6 months past the deprecation date
118
-        if ($this->now > $this->deprecation_date->modify('+6 months')) {
119
-            return;
120
-        }
121
-        // check for deprecated payment methods on every request
122
-        // jumping in at priority 0 before persistent admin notices are loaded
123
-        add_action(
124
-            'admin_notices',
125
-            [$this, 'loadAdminNotices'],
126
-            0
127
-        );
128
-        // check again whenever a payment method is activated
129
-        add_action(
130
-            'AHEE__EE_Payment_Method_Manager__activate_a_payment_method_of_type__after_activation',
131
-            [$this, 'maybeDismissNotifications']
132
-        );
133
-        // and also when a payment method is deactivated
134
-        add_action(
135
-            'AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method',
136
-            [$this, 'paymentMethodDeactivation']
137
-        );
138
-    }
139
-
140
-
141
-    /**
142
-     * If there are no active deprecated payment methods, then dismiss all notices.
143
-     * Callback for the `AHEE__EE_Payment_Method_Manager__activate_a_payment_method_of_type__after_activation` action.
144
-     *
145
-     * @return void
146
-     * @throws EE_Error
147
-     * @throws ReflectionException
148
-     */
149
-    public function maybeDismissNotifications()
150
-    {
151
-        if (! $this->activeDeprecatedPaymentMethods()) {
152
-            $notices = [
153
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL,
154
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING,
155
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT,
156
-            ];
157
-            foreach ($notices as $notice) {
158
-                PersistentAdminNoticeManager::dismissPersistentAdminNotice($notice);
159
-            }
160
-        }
161
-    }
162
-
163
-
164
-    /**
165
-     * If the payment method being deactivated is in the deprecated list,
166
-     * then we need to check if there are any other deprecated payment methods still active.
167
-     * `AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method` callback
168
-     *
169
-     * @param string $payment_method_slug
170
-     * @return void
171
-     * @throws EE_Error
172
-     * @throws ReflectionException
173
-     */
174
-    public function paymentMethodDeactivation(string $payment_method_slug)
175
-    {
176
-        if (in_array($payment_method_slug, array_keys($this->deprecated_payment_methods), true)) {
177
-            $this->maybeDismissNotifications();
178
-        }
179
-    }
180
-
181
-
182
-    /**
183
-     * IF we are past the deprecation date, then deactivate any active deprecated payment methods
184
-     *
185
-     * @return void
186
-     * @throws EE_Error
187
-     * @throws ReflectionException
188
-     */
189
-    private function deactivateDeprecatedPaymentMethods()
190
-    {
191
-        if ($this->now >= $this->deprecation_date) {
192
-            $active_deprecated_payment_methods = $this->activeDeprecatedPaymentMethods();
193
-            foreach ($active_deprecated_payment_methods as $active_deprecated_pm) {
194
-                if ($active_deprecated_pm instanceof EE_Payment_Method) {
195
-                    $active_deprecated_pm->deactivate();
196
-                    unset($this->active_deprecated_payment_methods[ $active_deprecated_pm->slug() ]);
197
-                }
198
-            }
199
-            $this->maybeDismissNotifications();
200
-            // get the PM Manager to hide the deprecated PMs
201
-            add_filter(
202
-                'FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide',
203
-                [$this, 'hideDeprecatedPaymentMethods']
204
-            );
205
-        }
206
-    }
207
-
208
-
209
-    /**
210
-     * adds the list of deprecated PMs to the list of PMs to remove from the UI
211
-     * Callback for FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide
212
-     *
213
-     * @param array $pms_can_hide
214
-     * @return array
215
-     */
216
-    public function hideDeprecatedPaymentMethods(array $pms_can_hide): array
217
-    {
218
-        return array_merge($pms_can_hide, $this->deprecated_payment_methods);
219
-    }
220
-
221
-
222
-    /**
223
-     * Returns an array of deprecated payment methods that are currently active
224
-     *
225
-     * @return EE_Payment_Method[]
226
-     * @throws EE_Error
227
-     * @throws ReflectionException
228
-     */
229
-    private function activeDeprecatedPaymentMethods(): array
230
-    {
231
-        if ($this->active_deprecated_payment_methods === null) {
232
-            $this->active_deprecated_payment_methods = [];
233
-            $deprecated_payment_methods              = $this->payment_method_model->get_all(
234
-                [['PMD_slug' => ['IN', array_keys($this->deprecated_payment_methods)]]]
235
-            );
236
-            if (! $deprecated_payment_methods) {
237
-                return $this->active_deprecated_payment_methods;
238
-            }
239
-            foreach ($deprecated_payment_methods as $deprecated_pm) {
240
-                if ($deprecated_pm instanceof EE_Payment_Method && $deprecated_pm->active()) {
241
-                    $this->active_deprecated_payment_methods[ $deprecated_pm->slug() ] = $deprecated_pm;
242
-                }
243
-            }
244
-        }
245
-        return $this->active_deprecated_payment_methods;
246
-    }
247
-
248
-
249
-    /**
250
-     * If there are any deprecated payment methods active,
251
-     * then load the appropriate admin notices based on the current date.
252
-     * Callback for the `admin_notices` action.
253
-     *
254
-     * @return void
255
-     * @throws EE_Error
256
-     * @throws ReflectionException
257
-     */
258
-    public function loadAdminNotices()
259
-    {
260
-        if ($this->activeDeprecatedPaymentMethods()) {
261
-            if ($this->now < new DateTime(self::NOTIFICATION_DATE_WARNING)) {
262
-                $this->displayNotification(self::TYPE_INITIAL);
263
-                return;
264
-            }
265
-            if ($this->now < new DateTime(self::NOTIFICATION_DATE_URGENT)) {
266
-                $this->displayNotification(self::TYPE_WARNING);
267
-                return;
268
-            }
269
-            if ($this->now < new DateTime(self::PM_DEPRECATION_DATE)) {
270
-                $this->displayNotification(self::TYPE_URGENT);
271
-                return;
272
-            }
273
-        }
274
-        if ($this->now > new DateTime(self::PM_DEPRECATION_DATE)) {
275
-            $this->displayNotification(self::TYPE_FINAL);
276
-        }
277
-    }
278
-
279
-
280
-    /**
281
-     * @return void
282
-     */
283
-    private function deleteNotifications()
284
-    {
285
-        $pm_deprecations = get_option(self::OPTION_NAME, []);
286
-        // initialize the option if it doesn't exist
287
-        if (empty($pm_deprecations)) {
288
-            add_option(self::OPTION_NAME, [], '', 'no');
289
-        }
290
-        if (! empty($pm_deprecations['jan-6-update'])) {
291
-            return;
292
-        }
293
-        PersistentAdminNoticeManager::deletePersistentAdminNotice(
294
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING
295
-        );
296
-        PersistentAdminNoticeManager::deletePersistentAdminNotice(
297
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT
298
-        );
299
-        PersistentAdminNoticeManager::deletePersistentAdminNotice(
300
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL
301
-        );
302
-        PersistentAdminNoticeManager::deletePersistentAdminNotice(
303
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL
304
-        );
305
-        $pm_deprecations['jan-6-update'] = true;
306
-        update_option(self::OPTION_NAME, $pm_deprecations);
307
-    }
308
-
309
-
310
-    /**
311
-     * @param string $type
312
-     * @return void
313
-     */
314
-    private function displayNotification(string $type)
315
-    {
316
-        switch ($type) {
317
-            case self::TYPE_WARNING:
318
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING;
319
-                $notice_type = 'attention';
320
-                break;
321
-            case self::TYPE_URGENT:
322
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT;
323
-                $notice_type = 'warning';
324
-                break;
325
-            case self::TYPE_FINAL:
326
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL;
327
-                $notice_type = 'error';
328
-                break;
329
-            case self::TYPE_INITIAL:
330
-            default:
331
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL;
332
-                $notice_type = 'info';
333
-        }
334
-
335
-        $message = sprintf(
336
-            '
29
+	public const  PM_DEPRECATION_DATE       = '2025-04-15';
30
+
31
+	private const NOTIFICATION_DATE_WARNING = '2025-01-15';
32
+
33
+	private const NOTIFICATION_DATE_URGENT  = '2025-03-15';
34
+
35
+	private const NOTIFICATION_NAME_PREFIX  = 'pm-deprecations-2025-';
36
+
37
+	private const OPTION_NAME               = 'ee_pm_deprecations_2025';
38
+
39
+	private const TYPE_INITIAL              = 'initial';
40
+
41
+	private const TYPE_WARNING              = 'warning';
42
+
43
+	private const TYPE_URGENT               = 'urgent';
44
+
45
+	private const TYPE_FINAL                = 'final';
46
+
47
+	private const URL_ACCEPT                = 'https://eventespresso.com/product/eea-authorizenet-accept/';
48
+
49
+	private const URL_STRIPE                = 'https://eventespresso.com/product/eea-stripe-gateway/';
50
+
51
+	private const URL_SQUARE                = 'https://eventespresso.com/product/eea-square-gateway/';
52
+
53
+	private const URL_SUPPORT_DOCS          = 'https://support.eventespresso.com/article/612-deprecated-payment-methods-2025';
54
+
55
+
56
+
57
+	private EEM_Payment_Method $payment_method_model;
58
+
59
+	private ?array $active_deprecated_payment_methods = null;
60
+
61
+	private array $deprecated_payment_methods = [
62
+		'aim'                  => 'aim',
63
+		'authorizenet_sim'     => 'authorizenet_sim',
64
+		'braintree_dropin'     => 'braintree_dropin',
65
+		'payflow_pro_onsite'   => 'payflow_pro_onsite',
66
+		'paypal_express'       => 'paypal_express',
67
+		'paypal_pro'           => 'paypal_pro',
68
+		'paypal_smart_buttons' => 'paypal_smart_buttons',
69
+		'paypal_standard'      => 'paypal_standard',
70
+	];
71
+
72
+	private Datetime $deprecation_date;
73
+
74
+	private Datetime $now;
75
+
76
+
77
+	/**
78
+	 * @param EEM_Payment_Method $payment_method_model
79
+	 */
80
+	public function __construct(EEM_Payment_Method $payment_method_model)
81
+	{
82
+		$this->payment_method_model = $payment_method_model;
83
+		$this->deprecation_date     = new DateTime(self::PM_DEPRECATION_DATE);
84
+		$this->now                  = new Datetime('now');
85
+		// TODO: remove the following comments before release
86
+		// uncomment next line to trigger warning notice
87
+		// $this->now = new Datetime('2025-02-20'); // Feb 20, 2025
88
+		// uncomment next line to trigger urgent notice
89
+		// $this->now = new Datetime('2025-03-20'); // Mar 20, 2025
90
+		// uncomment next line to trigger final notice & PM deactivation
91
+		// $this->now = new Datetime('2025-04-20'); // Apr 20, 2025
92
+		// uncomment the following block of code to "unhide" PMs
93
+		// add_filter(
94
+		//     'FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide',
95
+		//     fn() => []
96
+		// );
97
+		// uncomment the following to display ALL notices regardless of dates
98
+		// $this->displayNotification(self::TYPE_INITIAL);
99
+		// $this->displayNotification(self::TYPE_WARNING);
100
+		// $this->displayNotification(self::TYPE_URGENT);
101
+		// $this->displayNotification(self::TYPE_FINAL);
102
+	}
103
+
104
+
105
+	/**
106
+	 * @return void
107
+	 * @throws EE_Error
108
+	 * @throws ReflectionException
109
+	 */
110
+	public function setHooks()
111
+	{
112
+		// giving customers more time to migrate,
113
+		if ($this->now < new Datetime(self::NOTIFICATION_DATE_WARNING)) {
114
+			$this->deleteNotifications();
115
+		}
116
+		$this->deactivateDeprecatedPaymentMethods();
117
+		// don't bother if we're 6 months past the deprecation date
118
+		if ($this->now > $this->deprecation_date->modify('+6 months')) {
119
+			return;
120
+		}
121
+		// check for deprecated payment methods on every request
122
+		// jumping in at priority 0 before persistent admin notices are loaded
123
+		add_action(
124
+			'admin_notices',
125
+			[$this, 'loadAdminNotices'],
126
+			0
127
+		);
128
+		// check again whenever a payment method is activated
129
+		add_action(
130
+			'AHEE__EE_Payment_Method_Manager__activate_a_payment_method_of_type__after_activation',
131
+			[$this, 'maybeDismissNotifications']
132
+		);
133
+		// and also when a payment method is deactivated
134
+		add_action(
135
+			'AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method',
136
+			[$this, 'paymentMethodDeactivation']
137
+		);
138
+	}
139
+
140
+
141
+	/**
142
+	 * If there are no active deprecated payment methods, then dismiss all notices.
143
+	 * Callback for the `AHEE__EE_Payment_Method_Manager__activate_a_payment_method_of_type__after_activation` action.
144
+	 *
145
+	 * @return void
146
+	 * @throws EE_Error
147
+	 * @throws ReflectionException
148
+	 */
149
+	public function maybeDismissNotifications()
150
+	{
151
+		if (! $this->activeDeprecatedPaymentMethods()) {
152
+			$notices = [
153
+				self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL,
154
+				self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING,
155
+				self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT,
156
+			];
157
+			foreach ($notices as $notice) {
158
+				PersistentAdminNoticeManager::dismissPersistentAdminNotice($notice);
159
+			}
160
+		}
161
+	}
162
+
163
+
164
+	/**
165
+	 * If the payment method being deactivated is in the deprecated list,
166
+	 * then we need to check if there are any other deprecated payment methods still active.
167
+	 * `AHEE__EE_Payment_Method_Manager__deactivate_payment_method__after_deactivating_payment_method` callback
168
+	 *
169
+	 * @param string $payment_method_slug
170
+	 * @return void
171
+	 * @throws EE_Error
172
+	 * @throws ReflectionException
173
+	 */
174
+	public function paymentMethodDeactivation(string $payment_method_slug)
175
+	{
176
+		if (in_array($payment_method_slug, array_keys($this->deprecated_payment_methods), true)) {
177
+			$this->maybeDismissNotifications();
178
+		}
179
+	}
180
+
181
+
182
+	/**
183
+	 * IF we are past the deprecation date, then deactivate any active deprecated payment methods
184
+	 *
185
+	 * @return void
186
+	 * @throws EE_Error
187
+	 * @throws ReflectionException
188
+	 */
189
+	private function deactivateDeprecatedPaymentMethods()
190
+	{
191
+		if ($this->now >= $this->deprecation_date) {
192
+			$active_deprecated_payment_methods = $this->activeDeprecatedPaymentMethods();
193
+			foreach ($active_deprecated_payment_methods as $active_deprecated_pm) {
194
+				if ($active_deprecated_pm instanceof EE_Payment_Method) {
195
+					$active_deprecated_pm->deactivate();
196
+					unset($this->active_deprecated_payment_methods[ $active_deprecated_pm->slug() ]);
197
+				}
198
+			}
199
+			$this->maybeDismissNotifications();
200
+			// get the PM Manager to hide the deprecated PMs
201
+			add_filter(
202
+				'FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide',
203
+				[$this, 'hideDeprecatedPaymentMethods']
204
+			);
205
+		}
206
+	}
207
+
208
+
209
+	/**
210
+	 * adds the list of deprecated PMs to the list of PMs to remove from the UI
211
+	 * Callback for FHEE__EventEspresso_PaymentMethods_Manager__hidePaymentMethods__pms_can_hide
212
+	 *
213
+	 * @param array $pms_can_hide
214
+	 * @return array
215
+	 */
216
+	public function hideDeprecatedPaymentMethods(array $pms_can_hide): array
217
+	{
218
+		return array_merge($pms_can_hide, $this->deprecated_payment_methods);
219
+	}
220
+
221
+
222
+	/**
223
+	 * Returns an array of deprecated payment methods that are currently active
224
+	 *
225
+	 * @return EE_Payment_Method[]
226
+	 * @throws EE_Error
227
+	 * @throws ReflectionException
228
+	 */
229
+	private function activeDeprecatedPaymentMethods(): array
230
+	{
231
+		if ($this->active_deprecated_payment_methods === null) {
232
+			$this->active_deprecated_payment_methods = [];
233
+			$deprecated_payment_methods              = $this->payment_method_model->get_all(
234
+				[['PMD_slug' => ['IN', array_keys($this->deprecated_payment_methods)]]]
235
+			);
236
+			if (! $deprecated_payment_methods) {
237
+				return $this->active_deprecated_payment_methods;
238
+			}
239
+			foreach ($deprecated_payment_methods as $deprecated_pm) {
240
+				if ($deprecated_pm instanceof EE_Payment_Method && $deprecated_pm->active()) {
241
+					$this->active_deprecated_payment_methods[ $deprecated_pm->slug() ] = $deprecated_pm;
242
+				}
243
+			}
244
+		}
245
+		return $this->active_deprecated_payment_methods;
246
+	}
247
+
248
+
249
+	/**
250
+	 * If there are any deprecated payment methods active,
251
+	 * then load the appropriate admin notices based on the current date.
252
+	 * Callback for the `admin_notices` action.
253
+	 *
254
+	 * @return void
255
+	 * @throws EE_Error
256
+	 * @throws ReflectionException
257
+	 */
258
+	public function loadAdminNotices()
259
+	{
260
+		if ($this->activeDeprecatedPaymentMethods()) {
261
+			if ($this->now < new DateTime(self::NOTIFICATION_DATE_WARNING)) {
262
+				$this->displayNotification(self::TYPE_INITIAL);
263
+				return;
264
+			}
265
+			if ($this->now < new DateTime(self::NOTIFICATION_DATE_URGENT)) {
266
+				$this->displayNotification(self::TYPE_WARNING);
267
+				return;
268
+			}
269
+			if ($this->now < new DateTime(self::PM_DEPRECATION_DATE)) {
270
+				$this->displayNotification(self::TYPE_URGENT);
271
+				return;
272
+			}
273
+		}
274
+		if ($this->now > new DateTime(self::PM_DEPRECATION_DATE)) {
275
+			$this->displayNotification(self::TYPE_FINAL);
276
+		}
277
+	}
278
+
279
+
280
+	/**
281
+	 * @return void
282
+	 */
283
+	private function deleteNotifications()
284
+	{
285
+		$pm_deprecations = get_option(self::OPTION_NAME, []);
286
+		// initialize the option if it doesn't exist
287
+		if (empty($pm_deprecations)) {
288
+			add_option(self::OPTION_NAME, [], '', 'no');
289
+		}
290
+		if (! empty($pm_deprecations['jan-6-update'])) {
291
+			return;
292
+		}
293
+		PersistentAdminNoticeManager::deletePersistentAdminNotice(
294
+			self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING
295
+		);
296
+		PersistentAdminNoticeManager::deletePersistentAdminNotice(
297
+			self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT
298
+		);
299
+		PersistentAdminNoticeManager::deletePersistentAdminNotice(
300
+			self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL
301
+		);
302
+		PersistentAdminNoticeManager::deletePersistentAdminNotice(
303
+			self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL
304
+		);
305
+		$pm_deprecations['jan-6-update'] = true;
306
+		update_option(self::OPTION_NAME, $pm_deprecations);
307
+	}
308
+
309
+
310
+	/**
311
+	 * @param string $type
312
+	 * @return void
313
+	 */
314
+	private function displayNotification(string $type)
315
+	{
316
+		switch ($type) {
317
+			case self::TYPE_WARNING:
318
+				$name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING;
319
+				$notice_type = 'attention';
320
+				break;
321
+			case self::TYPE_URGENT:
322
+				$name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT;
323
+				$notice_type = 'warning';
324
+				break;
325
+			case self::TYPE_FINAL:
326
+				$name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL;
327
+				$notice_type = 'error';
328
+				break;
329
+			case self::TYPE_INITIAL:
330
+			default:
331
+				$name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL;
332
+				$notice_type = 'info';
333
+		}
334
+
335
+		$message = sprintf(
336
+			'
337 337
             <h3>%1$s</h3>
338 338
             %2$s
339 339
             <span class="dashicons dashicons-bank dashicons--huge"></span>',
340
-            $this->heading($type),
341
-            $this->firstPg($type)
342
-            . $this->theFollowingPaymentsPg()
343
-            . $this->whatYouNeedToDoPg($type)
344
-            . $this->toMigratePg()
345
-            . $this->supportPg()
346
-        );
347
-
348
-        new PersistentAdminNotice(
349
-            $name,
350
-            $message,
351
-            $notice_type === 'warning',
352
-            'manage_options',
353
-            'view persistent admin notice',
354
-            false,
355
-            $notice_type,
356
-            'ee-service-change'
357
-        );
358
-    }
359
-
360
-
361
-    private function heading(string $type = self::TYPE_INITIAL): string
362
-    {
363
-        switch ($type) {
364
-            case self::TYPE_WARNING:
365
-                return esc_html__(
366
-                    'Urgent Reminder: To continue processing payments, migrate to a new payment integration today.',
367
-                    'event_espresso'
368
-                );
369
-            case self::TYPE_URGENT:
370
-                return esc_html__(
371
-                    'Immediate Action Required: To continue processing payments, migrate to a new payment integration NOW!',
372
-                    'event_espresso'
373
-                );
374
-            case self::TYPE_FINAL:
375
-                return esc_html__('Payment Gateway Integrations Deactivated!', 'event_espresso');
376
-            case self::TYPE_INITIAL:
377
-            default:
378
-                return esc_html__(
379
-                    'Action Required: To continue processing payments, migrate to a new payment integration soon.',
380
-                    'event_espresso'
381
-                );
382
-        }
383
-    }
384
-
385
-
386
-    private function firstPg(string $type = self::TYPE_INITIAL): string
387
-    {
388
-        switch ($type) {
389
-            case self::TYPE_WARNING:
390
-                return sprintf(
391
-                    esc_html__(
392
-                        '%1$sRemember, we will soon deprecate and remove the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration as soon as possible. Our replacement payment integrations offer more features and security.%2$s',
393
-                        'event_espresso'
394
-                    ),
395
-                    '<p>',
396
-                    '</p>'
397
-                );
398
-            case self::TYPE_URGENT:
399
-                $date = new DateTime(self::PM_DEPRECATION_DATE);
400
-                return sprintf(
401
-                    esc_html__(
402
-                        '%1$sOn %3$s we will deprecate and remove the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration now. Our replacement payment integrations offer more features and security.%2$s',
403
-                        'event_espresso'
404
-                    ),
405
-                    '<p>',
406
-                    '</p>',
407
-                    $date->format('F jS, Y')
408
-                );
409
-            case self::TYPE_FINAL:
410
-                return sprintf(
411
-                    esc_html__(
412
-                        '%1$sThe following payment integrations have been deactivated: PayPal Standard, PayPal Express, PayPal Smart Buttons, PayPal Pro, Braintree, Payflow Pro, Authorize.net SIM and Authorize.net AIM.%2$s',
413
-                        'event_espresso'
414
-                    ),
415
-                    '<p>',
416
-                    '</p>'
417
-                );
418
-            case self::TYPE_INITIAL:
419
-            default:
420
-                return sprintf(
421
-                    esc_html__(
422
-                        '%1$sIn early 2025 we will be deprecating the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration as soon as possible. Our replacement payment integrations offer more features and security.%2$s',
423
-                        'event_espresso'
424
-                    ),
425
-                    '<p>',
426
-                    '</p>'
427
-                );
428
-        }
429
-    }
430
-
431
-
432
-    private function theFollowingPaymentsPg(): string
433
-    {
434
-        return sprintf(
435
-            esc_html__(
436
-                '%1$sThe following payment integrations will be %3$sremoved%4$s: PayPal Standard, PayPal Express, PayPal Smart Buttons, PayPal Pro, Braintree, Payflow Pro, Authorize.net SIM and Authorize.net AIM.%2$s',
437
-                'event_espresso'
438
-            ),
439
-            '<p>',
440
-            '</p>',
441
-            '<strong>',
442
-            '</strong>'
443
-        );
444
-    }
445
-
446
-
447
-    private function whatYouNeedToDoPg(string $type = self::TYPE_INITIAL): string
448
-    {
449
-        // link to the payment settings page
450
-        // ex: '/wp-admin/admin.php?page=espresso_payment_settings&action=default&payment_method=paypalcheckout'
451
-        $paypal_url = add_query_arg(
452
-            [
453
-                'page'           => 'espresso_payment_settings',
454
-                'action'         => 'default',
455
-                'payment_method' => 'paypalcheckout',
456
-            ],
457
-            admin_url('admin.php')
458
-        );
459
-        switch ($type) {
460
-            case self::TYPE_WARNING:
461
-                return sprintf(
462
-                    esc_html__(
463
-                        '%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s soon.%4$s',
464
-                        'event_espresso'
465
-                    ),
466
-                    '<h4>',
467
-                    '</h4>',
468
-                    '<p>',
469
-                    '</p>',
470
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
471
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
472
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
473
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
474
-                    '</a>',
475
-                );
476
-            case self::TYPE_URGENT:
477
-            case self::TYPE_FINAL:
478
-                return sprintf(
479
-                    esc_html__(
480
-                        '%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s immediately to avoid payment processing interruptions.%4$s',
481
-                        'event_espresso'
482
-                    ),
483
-                    '<h4>',
484
-                    '</h4>',
485
-                    '<p>',
486
-                    '</p>',
487
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
488
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
489
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
490
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
491
-                    '</a>',
492
-                );
493
-            case self::TYPE_INITIAL:
494
-            default:
495
-                return sprintf(
496
-                    esc_html__(
497
-                        '%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s at your earliest convenience.%4$s',
498
-                        'event_espresso'
499
-                    ),
500
-                    '<h4>',
501
-                    '</h4>',
502
-                    '<p>',
503
-                    '</p>',
504
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
505
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
506
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
507
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
508
-                    '</a>',
509
-                );
510
-        }
511
-    }
512
-
513
-
514
-    private function toMigratePg(): string
515
-    {
516
-        return sprintf(
517
-            esc_html__(
518
-                '%1$sTo migrate to PayPal Commerce, simply go to %3$sEvent Espresso%4$s > %3$sPayment Methods%4$s > click the PayPal Commerce tab > activate PayPal Commerce > and click the blue "Connect with PayPal" button and follow the prompts.%2$s',
519
-                'event_espresso'
520
-            ),
521
-            '<p>',
522
-            '</p>',
523
-            '<strong>',
524
-            '</strong>'
525
-        );
526
-    }
527
-
528
-
529
-    private function supportPg(): string
530
-    {
531
-        return sprintf(
532
-            esc_html__(
533
-                '%1$sSupport%2$s%3$sOur support team is available to assist you with the transition process and answer any questions you may have. Contact us at %5$s for help, or click the following link to %9$slearn more %10$s. This message will disappear once you deactivate any of the affected gateways.%4$s
340
+			$this->heading($type),
341
+			$this->firstPg($type)
342
+			. $this->theFollowingPaymentsPg()
343
+			. $this->whatYouNeedToDoPg($type)
344
+			. $this->toMigratePg()
345
+			. $this->supportPg()
346
+		);
347
+
348
+		new PersistentAdminNotice(
349
+			$name,
350
+			$message,
351
+			$notice_type === 'warning',
352
+			'manage_options',
353
+			'view persistent admin notice',
354
+			false,
355
+			$notice_type,
356
+			'ee-service-change'
357
+		);
358
+	}
359
+
360
+
361
+	private function heading(string $type = self::TYPE_INITIAL): string
362
+	{
363
+		switch ($type) {
364
+			case self::TYPE_WARNING:
365
+				return esc_html__(
366
+					'Urgent Reminder: To continue processing payments, migrate to a new payment integration today.',
367
+					'event_espresso'
368
+				);
369
+			case self::TYPE_URGENT:
370
+				return esc_html__(
371
+					'Immediate Action Required: To continue processing payments, migrate to a new payment integration NOW!',
372
+					'event_espresso'
373
+				);
374
+			case self::TYPE_FINAL:
375
+				return esc_html__('Payment Gateway Integrations Deactivated!', 'event_espresso');
376
+			case self::TYPE_INITIAL:
377
+			default:
378
+				return esc_html__(
379
+					'Action Required: To continue processing payments, migrate to a new payment integration soon.',
380
+					'event_espresso'
381
+				);
382
+		}
383
+	}
384
+
385
+
386
+	private function firstPg(string $type = self::TYPE_INITIAL): string
387
+	{
388
+		switch ($type) {
389
+			case self::TYPE_WARNING:
390
+				return sprintf(
391
+					esc_html__(
392
+						'%1$sRemember, we will soon deprecate and remove the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration as soon as possible. Our replacement payment integrations offer more features and security.%2$s',
393
+						'event_espresso'
394
+					),
395
+					'<p>',
396
+					'</p>'
397
+				);
398
+			case self::TYPE_URGENT:
399
+				$date = new DateTime(self::PM_DEPRECATION_DATE);
400
+				return sprintf(
401
+					esc_html__(
402
+						'%1$sOn %3$s we will deprecate and remove the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration now. Our replacement payment integrations offer more features and security.%2$s',
403
+						'event_espresso'
404
+					),
405
+					'<p>',
406
+					'</p>',
407
+					$date->format('F jS, Y')
408
+				);
409
+			case self::TYPE_FINAL:
410
+				return sprintf(
411
+					esc_html__(
412
+						'%1$sThe following payment integrations have been deactivated: PayPal Standard, PayPal Express, PayPal Smart Buttons, PayPal Pro, Braintree, Payflow Pro, Authorize.net SIM and Authorize.net AIM.%2$s',
413
+						'event_espresso'
414
+					),
415
+					'<p>',
416
+					'</p>'
417
+				);
418
+			case self::TYPE_INITIAL:
419
+			default:
420
+				return sprintf(
421
+					esc_html__(
422
+						'%1$sIn early 2025 we will be deprecating the following outdated or obsolete payment integrations and ask you to migrate to a new payment integration as soon as possible. Our replacement payment integrations offer more features and security.%2$s',
423
+						'event_espresso'
424
+					),
425
+					'<p>',
426
+					'</p>'
427
+				);
428
+		}
429
+	}
430
+
431
+
432
+	private function theFollowingPaymentsPg(): string
433
+	{
434
+		return sprintf(
435
+			esc_html__(
436
+				'%1$sThe following payment integrations will be %3$sremoved%4$s: PayPal Standard, PayPal Express, PayPal Smart Buttons, PayPal Pro, Braintree, Payflow Pro, Authorize.net SIM and Authorize.net AIM.%2$s',
437
+				'event_espresso'
438
+			),
439
+			'<p>',
440
+			'</p>',
441
+			'<strong>',
442
+			'</strong>'
443
+		);
444
+	}
445
+
446
+
447
+	private function whatYouNeedToDoPg(string $type = self::TYPE_INITIAL): string
448
+	{
449
+		// link to the payment settings page
450
+		// ex: '/wp-admin/admin.php?page=espresso_payment_settings&action=default&payment_method=paypalcheckout'
451
+		$paypal_url = add_query_arg(
452
+			[
453
+				'page'           => 'espresso_payment_settings',
454
+				'action'         => 'default',
455
+				'payment_method' => 'paypalcheckout',
456
+			],
457
+			admin_url('admin.php')
458
+		);
459
+		switch ($type) {
460
+			case self::TYPE_WARNING:
461
+				return sprintf(
462
+					esc_html__(
463
+						'%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s soon.%4$s',
464
+						'event_espresso'
465
+					),
466
+					'<h4>',
467
+					'</h4>',
468
+					'<p>',
469
+					'</p>',
470
+					'<a href="' . esc_url_raw($paypal_url) . '">',
471
+					'<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
472
+					'<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
473
+					'<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
474
+					'</a>',
475
+				);
476
+			case self::TYPE_URGENT:
477
+			case self::TYPE_FINAL:
478
+				return sprintf(
479
+					esc_html__(
480
+						'%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s immediately to avoid payment processing interruptions.%4$s',
481
+						'event_espresso'
482
+					),
483
+					'<h4>',
484
+					'</h4>',
485
+					'<p>',
486
+					'</p>',
487
+					'<a href="' . esc_url_raw($paypal_url) . '">',
488
+					'<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
489
+					'<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
490
+					'<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
491
+					'</a>',
492
+				);
493
+			case self::TYPE_INITIAL:
494
+			default:
495
+				return sprintf(
496
+					esc_html__(
497
+						'%1$sWhat You Need To Do%2$s%3$sPlease begin transitioning to one of our other payment integrations such as %5$sPayPal Commerce,%9$s %6$sStripe,%9$s %7$sSquare,%9$s or %8$sAuthorize.net Accept%9$s at your earliest convenience.%4$s',
498
+						'event_espresso'
499
+					),
500
+					'<h4>',
501
+					'</h4>',
502
+					'<p>',
503
+					'</p>',
504
+					'<a href="' . esc_url_raw($paypal_url) . '">',
505
+					'<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
506
+					'<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
507
+					'<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
508
+					'</a>',
509
+				);
510
+		}
511
+	}
512
+
513
+
514
+	private function toMigratePg(): string
515
+	{
516
+		return sprintf(
517
+			esc_html__(
518
+				'%1$sTo migrate to PayPal Commerce, simply go to %3$sEvent Espresso%4$s > %3$sPayment Methods%4$s > click the PayPal Commerce tab > activate PayPal Commerce > and click the blue "Connect with PayPal" button and follow the prompts.%2$s',
519
+				'event_espresso'
520
+			),
521
+			'<p>',
522
+			'</p>',
523
+			'<strong>',
524
+			'</strong>'
525
+		);
526
+	}
527
+
528
+
529
+	private function supportPg(): string
530
+	{
531
+		return sprintf(
532
+			esc_html__(
533
+				'%1$sSupport%2$s%3$sOur support team is available to assist you with the transition process and answer any questions you may have. Contact us at %5$s for help, or click the following link to %9$slearn more %10$s. This message will disappear once you deactivate any of the affected gateways.%4$s
534 534
 %3$sWe appreciate your understanding and cooperation as we replace legacy integrations with new integrations.%4$s
535 535
 %3$s%6$sThe team at Event Espresso %8$s%7$s%4$s',
536
-                'event_espresso'
537
-            ),
538
-            '<h4>',
539
-            '</h4>',
540
-            '<p>',
541
-            '</p>',
542
-            '<a href="mailto:[email protected]">[email protected]</a>',
543
-            '<h5>',
544
-            '</h5>',
545
-            '<span class="ee-icon ee-icon-ee-cup-thick"></span>',
546
-            '<a href="' . esc_url_raw(self::URL_SUPPORT_DOCS) . '" target="_blank">',
547
-            '</a>'
548
-        );
549
-    }
536
+				'event_espresso'
537
+			),
538
+			'<h4>',
539
+			'</h4>',
540
+			'<p>',
541
+			'</p>',
542
+			'<a href="mailto:[email protected]">[email protected]</a>',
543
+			'<h5>',
544
+			'</h5>',
545
+			'<span class="ee-icon ee-icon-ee-cup-thick"></span>',
546
+			'<a href="' . esc_url_raw(self::URL_SUPPORT_DOCS) . '" target="_blank">',
547
+			'</a>'
548
+		);
549
+	}
550 550
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -148,11 +148,11 @@  discard block
 block discarded – undo
148 148
      */
149 149
     public function maybeDismissNotifications()
150 150
     {
151
-        if (! $this->activeDeprecatedPaymentMethods()) {
151
+        if ( ! $this->activeDeprecatedPaymentMethods()) {
152 152
             $notices = [
153
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL,
154
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING,
155
-                self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT,
153
+                self::NOTIFICATION_NAME_PREFIX.self::TYPE_INITIAL,
154
+                self::NOTIFICATION_NAME_PREFIX.self::TYPE_WARNING,
155
+                self::NOTIFICATION_NAME_PREFIX.self::TYPE_URGENT,
156 156
             ];
157 157
             foreach ($notices as $notice) {
158 158
                 PersistentAdminNoticeManager::dismissPersistentAdminNotice($notice);
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
             foreach ($active_deprecated_payment_methods as $active_deprecated_pm) {
194 194
                 if ($active_deprecated_pm instanceof EE_Payment_Method) {
195 195
                     $active_deprecated_pm->deactivate();
196
-                    unset($this->active_deprecated_payment_methods[ $active_deprecated_pm->slug() ]);
196
+                    unset($this->active_deprecated_payment_methods[$active_deprecated_pm->slug()]);
197 197
                 }
198 198
             }
199 199
             $this->maybeDismissNotifications();
@@ -233,12 +233,12 @@  discard block
 block discarded – undo
233 233
             $deprecated_payment_methods              = $this->payment_method_model->get_all(
234 234
                 [['PMD_slug' => ['IN', array_keys($this->deprecated_payment_methods)]]]
235 235
             );
236
-            if (! $deprecated_payment_methods) {
236
+            if ( ! $deprecated_payment_methods) {
237 237
                 return $this->active_deprecated_payment_methods;
238 238
             }
239 239
             foreach ($deprecated_payment_methods as $deprecated_pm) {
240 240
                 if ($deprecated_pm instanceof EE_Payment_Method && $deprecated_pm->active()) {
241
-                    $this->active_deprecated_payment_methods[ $deprecated_pm->slug() ] = $deprecated_pm;
241
+                    $this->active_deprecated_payment_methods[$deprecated_pm->slug()] = $deprecated_pm;
242 242
                 }
243 243
             }
244 244
         }
@@ -287,20 +287,20 @@  discard block
 block discarded – undo
287 287
         if (empty($pm_deprecations)) {
288 288
             add_option(self::OPTION_NAME, [], '', 'no');
289 289
         }
290
-        if (! empty($pm_deprecations['jan-6-update'])) {
290
+        if ( ! empty($pm_deprecations['jan-6-update'])) {
291 291
             return;
292 292
         }
293 293
         PersistentAdminNoticeManager::deletePersistentAdminNotice(
294
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING
294
+            self::NOTIFICATION_NAME_PREFIX.self::TYPE_WARNING
295 295
         );
296 296
         PersistentAdminNoticeManager::deletePersistentAdminNotice(
297
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT
297
+            self::NOTIFICATION_NAME_PREFIX.self::TYPE_URGENT
298 298
         );
299 299
         PersistentAdminNoticeManager::deletePersistentAdminNotice(
300
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL
300
+            self::NOTIFICATION_NAME_PREFIX.self::TYPE_FINAL
301 301
         );
302 302
         PersistentAdminNoticeManager::deletePersistentAdminNotice(
303
-            self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL
303
+            self::NOTIFICATION_NAME_PREFIX.self::TYPE_INITIAL
304 304
         );
305 305
         $pm_deprecations['jan-6-update'] = true;
306 306
         update_option(self::OPTION_NAME, $pm_deprecations);
@@ -315,20 +315,20 @@  discard block
 block discarded – undo
315 315
     {
316 316
         switch ($type) {
317 317
             case self::TYPE_WARNING:
318
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_WARNING;
318
+                $name        = self::NOTIFICATION_NAME_PREFIX.self::TYPE_WARNING;
319 319
                 $notice_type = 'attention';
320 320
                 break;
321 321
             case self::TYPE_URGENT:
322
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_URGENT;
322
+                $name        = self::NOTIFICATION_NAME_PREFIX.self::TYPE_URGENT;
323 323
                 $notice_type = 'warning';
324 324
                 break;
325 325
             case self::TYPE_FINAL:
326
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_FINAL;
326
+                $name        = self::NOTIFICATION_NAME_PREFIX.self::TYPE_FINAL;
327 327
                 $notice_type = 'error';
328 328
                 break;
329 329
             case self::TYPE_INITIAL:
330 330
             default:
331
-                $name        = self::NOTIFICATION_NAME_PREFIX . self::TYPE_INITIAL;
331
+                $name        = self::NOTIFICATION_NAME_PREFIX.self::TYPE_INITIAL;
332 332
                 $notice_type = 'info';
333 333
         }
334 334
 
@@ -467,10 +467,10 @@  discard block
 block discarded – undo
467 467
                     '</h4>',
468 468
                     '<p>',
469 469
                     '</p>',
470
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
471
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
472
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
473
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
470
+                    '<a href="'.esc_url_raw($paypal_url).'">',
471
+                    '<a href="'.esc_url_raw(self::URL_STRIPE).'" target="_blank">',
472
+                    '<a href="'.esc_url_raw(self::URL_SQUARE).'" target="_blank">',
473
+                    '<a href="'.esc_url_raw(self::URL_ACCEPT).'" target="_blank">',
474 474
                     '</a>',
475 475
                 );
476 476
             case self::TYPE_URGENT:
@@ -484,10 +484,10 @@  discard block
 block discarded – undo
484 484
                     '</h4>',
485 485
                     '<p>',
486 486
                     '</p>',
487
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
488
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
489
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
490
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
487
+                    '<a href="'.esc_url_raw($paypal_url).'">',
488
+                    '<a href="'.esc_url_raw(self::URL_STRIPE).'" target="_blank">',
489
+                    '<a href="'.esc_url_raw(self::URL_SQUARE).'" target="_blank">',
490
+                    '<a href="'.esc_url_raw(self::URL_ACCEPT).'" target="_blank">',
491 491
                     '</a>',
492 492
                 );
493 493
             case self::TYPE_INITIAL:
@@ -501,10 +501,10 @@  discard block
 block discarded – undo
501 501
                     '</h4>',
502 502
                     '<p>',
503 503
                     '</p>',
504
-                    '<a href="' . esc_url_raw($paypal_url) . '">',
505
-                    '<a href="' . esc_url_raw(self::URL_STRIPE) . '" target="_blank">',
506
-                    '<a href="' . esc_url_raw(self::URL_SQUARE) . '" target="_blank">',
507
-                    '<a href="' . esc_url_raw(self::URL_ACCEPT) . '" target="_blank">',
504
+                    '<a href="'.esc_url_raw($paypal_url).'">',
505
+                    '<a href="'.esc_url_raw(self::URL_STRIPE).'" target="_blank">',
506
+                    '<a href="'.esc_url_raw(self::URL_SQUARE).'" target="_blank">',
507
+                    '<a href="'.esc_url_raw(self::URL_ACCEPT).'" target="_blank">',
508 508
                     '</a>',
509 509
                 );
510 510
         }
@@ -543,7 +543,7 @@  discard block
 block discarded – undo
543 543
             '<h5>',
544 544
             '</h5>',
545 545
             '<span class="ee-icon ee-icon-ee-cup-thick"></span>',
546
-            '<a href="' . esc_url_raw(self::URL_SUPPORT_DOCS) . '" target="_blank">',
546
+            '<a href="'.esc_url_raw(self::URL_SUPPORT_DOCS).'" target="_blank">',
547 547
             '</a>'
548 548
         );
549 549
     }
Please login to merge, or discard this patch.
libraries/line_item_display/EE_SPCO_Line_Item_Display_Strategy.strategy.php 2 patches
Indentation   +721 added lines, -721 removed lines patch added patch discarded remove patch
@@ -13,725 +13,725 @@
 block discarded – undo
13 13
  */
14 14
 class EE_SPCO_Line_Item_Display_Strategy implements EEI_Line_Item_Display
15 15
 {
16
-    use DebugDisplay;
17
-
18
-    protected bool $prices_include_taxes = false;
19
-
20
-    /**
21
-     * array of events
22
-     *
23
-     * @type EE_Line_Item[] $_events
24
-     */
25
-    private array $_events = [];
26
-
27
-    /**
28
-     * whether to display the taxes row or not
29
-     *
30
-     * @type bool $_show_taxes
31
-     */
32
-    private bool $_show_taxes = false;
33
-
34
-    /**
35
-     * html for any tax rows
36
-     *
37
-     * @type string $_show_taxes
38
-     */
39
-    private string $_taxes_html = '';
40
-
41
-    /**
42
-     * total amount including tax we can bill for at this time
43
-     *
44
-     * @type float $_grand_total
45
-     */
46
-    private float $_grand_total = 0.00;
47
-
48
-    /**
49
-     * total number of items being billed for
50
-     *
51
-     * @type int $_total_items
52
-     */
53
-    private int $_total_items = 0;
54
-
55
-    private bool $debug = false;   //  true  false
56
-
57
-
58
-    public function __construct()
59
-    {
60
-        $this->prices_include_taxes = EE_Registry::instance()->CFG->tax_settings->prices_displayed_including_taxes;
61
-        $this->initializeDebugDisplay();
62
-    }
63
-
64
-
65
-    /**
66
-     * @return float
67
-     */
68
-    public function grand_total(): float
69
-    {
70
-        return $this->_grand_total;
71
-    }
72
-
73
-
74
-    /**
75
-     * @return int
76
-     */
77
-    public function total_items(): int
78
-    {
79
-        return $this->_total_items;
80
-    }
81
-
82
-
83
-    /**
84
-     * @param EE_Line_Item      $line_item
85
-     * @param array             $options
86
-     * @param EE_Line_Item|null $parent_line_item
87
-     * @return string
88
-     * @throws EE_Error
89
-     * @throws ReflectionException
90
-     */
91
-    public function display_line_item(
92
-        EE_Line_Item $line_item,
93
-        $options = [],
94
-        ?EE_Line_Item $parent_line_item = null
95
-    ): string {
96
-        $html = '';
97
-        // set some default options and merge with incoming
98
-        $options += [
99
-            'show_desc' => true,  //    true        false
100
-            'odd'       => false,
101
-        ];
102
-
103
-        $this->debugLog('', 0);
104
-        $this->debugLog(__FUNCTION__);
105
-        $this->debugLog($line_item->name() . ': ' . $line_item->code() . ' (' . $line_item->type() . ')');
106
-        if ($line_item->type() === EEM_Line_Item::type_total) {
107
-            $this->debugLog('******************************************************************', 2);
108
-        }
109
-
110
-        switch ($line_item->type()) {
111
-            case EEM_Line_Item::type_line_item:
112
-                $sub_taxes         = EEH_Line_Item::get_nearest_descendant_of_type(
113
-                    $line_item,
114
-                    EEM_Line_Item::type_sub_tax
115
-                );
116
-                $show_taxes = $line_item->is_taxable() || $sub_taxes;
117
-                $this->_show_taxes = $show_taxes ? true : $this->_show_taxes;
118
-                $html              .= $line_item->OBJ_type() === 'Ticket'
119
-                    ? $this->ticketRow($line_item, $options, $show_taxes)
120
-                    : $this->itemRow($line_item, $options, $show_taxes);
121
-                if (
122
-                    apply_filters(
123
-                        'FHEE__EE_SPCO_Line_Item_Display_Strategy__display_line_item__display_sub_line_items',
124
-                        true
125
-                    )
126
-                ) {
127
-                    // got any kids?
128
-                    $child_line_items = $line_item->children();
129
-                    if ($child_line_items) {
130
-                        foreach ($child_line_items as $child_line_item) {
131
-                            $html .= $this->display_line_item($child_line_item, $options, $line_item);
132
-                        }
133
-                    }
134
-                }
135
-                break;
136
-
137
-            case EEM_Line_Item::type_sub_line_item:
138
-                $html .= $this->subItemRow($line_item, $options, $parent_line_item);
139
-                break;
140
-
141
-            case EEM_Line_Item::type_sub_tax:
142
-                $this->_show_taxes = true;
143
-                break;
144
-
145
-            case EEM_Line_Item::type_sub_total:
146
-                static $sub_total = 0;
147
-                $event_sub_total = 0;
148
-                $text            = esc_html__('Sub-Total', 'event_espresso');
149
-                if ($line_item->OBJ_type() === 'Event') {
150
-                    $options['event_id'] = $line_item->OBJ_ID();
151
-                    if (! isset($this->_events[ $options['event_id'] ])) {
152
-                        $this->_events[ $options['event_id'] ] = 0;
153
-                        $event                                 = EEM_Event::instance()->get_one_by_ID(
154
-                            $options['event_id']
155
-                        );
156
-                        $event_name                            = $event instanceof EE_Event ? $event->name() . ' ' : '';
157
-                        // if event has default reg status of Not Approved, then don't display info on it
158
-                        if (
159
-                            $event instanceof EE_Event
160
-                            && $event->default_registration_status() === RegStatus::AWAITING_REVIEW
161
-                        ) {
162
-                            // unless display is forced
163
-                            $display_event = false;
164
-                            // unless there are registrations for it that are returning to pay
165
-                            if (isset($options['registrations']) && is_array($options['registrations'])) {
166
-                                foreach ($options['registrations'] as $registration) {
167
-                                    if (! $registration instanceof EE_Registration) {
168
-                                        continue;
169
-                                    }
170
-                                    $display_event = $registration->event_ID() === $options['event_id']
171
-                                    && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
172
-                                        ? true
173
-                                        : $display_event;
174
-                                }
175
-                            }
176
-                            if (! $display_event) {
177
-                                return '';
178
-                            }
179
-                        }
180
-                        $this->_events[ $options['event_id'] ] = 0;
181
-
182
-                        $html .= $this->eventRow($line_item);
183
-                        $text = $event_name . esc_html__('Event Sub-Total', 'event_espresso');
184
-                    }
185
-                }
186
-                $child_line_items = $line_item->children();
187
-                // loop thru children
188
-                foreach ($child_line_items as $child_line_item) {
189
-                    // recursively feed children back into this method
190
-                    $html .= $this->display_line_item($child_line_item, $options, $line_item);
191
-                }
192
-                if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
193
-                    $event_sub_total += $this->_events[ $options['event_id'] ];
194
-                }
195
-                $sub_total += $event_sub_total;
196
-                $this->debugLog(' = count($child_line_items): ' . count($child_line_items), 3);
197
-                $this->debugLog(' = count($this->_events): ' . count($this->_events), 3);
198
-                if (
199
-                    (
200
-                        // event subtotals
201
-                        $line_item->code() !== 'pre-tax-subtotal'
202
-                        && count($child_line_items) > 1
203
-                    ) || (
204
-                        // pre-tax subtotals
205
-                        $line_item->code() === 'pre-tax-subtotal'
206
-                        && count($this->_events) > 1
207
-                    )
208
-                ) {
209
-                    $options['sub_total'] = $line_item->OBJ_type() === 'Event' ? $event_sub_total : $sub_total;
210
-                    $html                 .= $this->subTotalRow($line_item, $text, $options);
211
-                } else {
212
-                    $this->debugLog('NO EVENT SUBTOTAL', 3);
213
-                }
214
-                break;
215
-
216
-            case EEM_Line_Item::type_tax:
217
-                if ($this->_show_taxes) {
218
-                    $this->_taxes_html .= $this->taxRow($line_item, $options);
219
-                }
220
-                break;
221
-
222
-            case EEM_Line_Item::type_tax_sub_total:
223
-                if ($this->_show_taxes) {
224
-                    $child_line_items = $line_item->children();
225
-                    // loop thru children
226
-                    foreach ($child_line_items as $child_line_item) {
227
-                        // recursively feed children back into this method
228
-                        $html .= $this->display_line_item($child_line_item, $options, $line_item);
229
-                    }
230
-                    if ($child_line_items) {
231
-                        $this->_taxes_html .= $this->totalTaxRow(
232
-                            $line_item,
233
-                            esc_html__('Tax Total', 'event_espresso')
234
-                        );
235
-                    }
236
-                }
237
-                break;
238
-
239
-            case EEM_Line_Item::type_total:
240
-                // get all child line items
241
-                $children = $line_item->children();
242
-                // loop thru all non-tax child line items
243
-                foreach ($children as $child_line_item) {
244
-                    if ($child_line_item->type() !== EEM_Line_Item::type_tax_sub_total) {
245
-                        // recursively feed children back into this method
246
-                        $html .= $this->display_line_item($child_line_item, $options, $line_item);
247
-                    }
248
-                }
249
-                // now loop thru  tax child line items
250
-                foreach ($children as $child_line_item) {
251
-                    if ($child_line_item->type() === EEM_Line_Item::type_tax_sub_total) {
252
-                        // recursively feed children back into this method
253
-                        $html .= $this->display_line_item($child_line_item, $options, $line_item);
254
-                    }
255
-                }
256
-                $html .= $this->_taxes_html;
257
-                $html .= $this->totalRow($line_item, esc_html__('Total', 'event_espresso'));
258
-                $html .= $this->paymentsAndAmountOwingRows($line_item, $options);
259
-                break;
260
-        }
261
-        return $html;
262
-    }
263
-
264
-
265
-    /**
266
-     * _event_row - basically a Heading row displayed once above each event's ticket rows
267
-     *
268
-     * @param EE_Line_Item $line_item
269
-     * @return string
270
-     * @throws EE_Error
271
-     * @throws ReflectionException
272
-     */
273
-    private function eventRow(EE_Line_Item $line_item): string
274
-    {
275
-        $this->debugLog(' - ' . __FUNCTION__);
276
-        // start of row
277
-        $html = EEH_HTML::tr('', 'event-cart-total-row', 'total_tr odd');
278
-        // event name td
279
-        $html .= EEH_HTML::td(
280
-            EEH_HTML::strong($line_item->name()),
281
-            '',
282
-            'event-header',
283
-            '',
284
-            ' colspan="4"'
285
-        );
286
-        // end of row
287
-        $html .= EEH_HTML::trx();
288
-        return $html;
289
-    }
290
-
291
-
292
-    /**
293
-     * _ticket_row
294
-     *
295
-     * @param EE_Line_Item $line_item
296
-     * @param array        $options
297
-     * @param bool         $show_taxes
298
-     * @return string
299
-     * @throws EE_Error
300
-     * @throws ReflectionException
301
-     */
302
-    private function ticketRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
303
-    {
304
-        $this->debugLog(' - ' . __FUNCTION__);
305
-        // start of row
306
-        $row_class = $options['odd'] ? 'item odd' : 'item';
307
-        $html      = EEH_HTML::tr('', '', $row_class);
308
-        // name && desc
309
-        $name_and_desc = apply_filters(
310
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__name',
311
-            $line_item->name(),
312
-            $line_item
313
-        );
314
-        $name_and_desc .= apply_filters(
315
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
316
-            $options['show_desc']
317
-                ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
318
-                : '',
319
-            $line_item,
320
-            $options
321
-        );
322
-        $name_and_desc .= $show_taxes ? ' * ' : '';
323
-        $name_and_desc = apply_filters(
324
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__name_and_desc',
325
-            $name_and_desc,
326
-            $line_item,
327
-            $options
328
-        );
329
-
330
-        // name td
331
-        $html .= EEH_HTML::td(
332
-            $name_and_desc,
333
-            '',
334
-            'item_l'
335
-        );
336
-        // price td
337
-        $price = apply_filters(
338
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__price',
339
-            $line_item->unit_price_no_code(),
340
-            $line_item
341
-        );
342
-        $html  .= EEH_HTML::td($price, '', 'spco-nowrap item_c jst-rght');
343
-        // quantity td
344
-        $html               .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
345
-        $this->_total_items += $line_item->quantity();
346
-        // determine total for line item
347
-        $total = apply_filters(
348
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__total',
349
-            $line_item->pretaxTotal(),
350
-            $line_item
351
-        );
352
-        if (isset($this->_events[ $options['event_id'] ])) {
353
-            $this->_events[ $options['event_id'] ] += $total;
354
-        }
355
-        // total td
356
-        $html .= EEH_HTML::td(
357
-            EEH_Template::format_currency($total, false, false),
358
-            '',
359
-            'spco-nowrap item_r jst-rght'
360
-        );
361
-        // end of row
362
-        $html .= EEH_HTML::trx();
363
-        return $html;
364
-    }
365
-
366
-
367
-    /**
368
-     * _item_row
369
-     *
370
-     * @param EE_Line_Item $line_item
371
-     * @param array        $options
372
-     * @param bool         $show_taxes
373
-     * @return string
374
-     * @throws EE_Error
375
-     * @throws ReflectionException
376
-     */
377
-    private function itemRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
378
-    {
379
-        $this->debugLog(' - ' . __FUNCTION__);
380
-        // start of row
381
-        $row_class = $options['odd'] ? 'item odd' : 'item';
382
-        $html      = EEH_HTML::tr('', '', $row_class);
383
-        $obj_name  = $line_item->OBJ_type() ? $line_item->OBJ_type_i18n() . ': ' : '';
384
-        // name && desc
385
-        $name_and_desc = apply_filters(
386
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__name',
387
-            $obj_name . $line_item->name(),
388
-            $line_item
389
-        );
390
-        $name_and_desc .= apply_filters(
391
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
392
-            (
393
-            $options['show_desc']
394
-                ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
395
-                : ''
396
-            ),
397
-            $line_item,
398
-            $options
399
-        );
400
-        $name_and_desc .= $show_taxes ? ' * ' : '';
401
-        $name_and_desc = apply_filters(
402
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy___item_row__name_and_desc',
403
-            $name_and_desc,
404
-            $line_item,
405
-            $options
406
-        );
407
-
408
-        // name td
409
-        $html .= EEH_HTML::td($name_and_desc, '', 'item_l');
410
-        // price td
411
-        if ($line_item->is_percent()) {
412
-            $html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap item_c jst-rght');
413
-        } else {
414
-            $html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'spco-nowrap item_c jst-rght');
415
-        }
416
-        // quantity td
417
-        $html .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
418
-        // $total = $line_item->total() * $line_item->quantity();
419
-        $total = $line_item->total();
420
-        if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
421
-            $this->_events[ $options['event_id'] ] += $total;
422
-        }
423
-        // total td
424
-        $html .= EEH_HTML::td(
425
-            EEH_Template::format_currency($total, false, false),
426
-            '',
427
-            'spco-nowrap item_r jst-rght'
428
-        );
429
-        // end of row
430
-        $html .= EEH_HTML::trx();
431
-        return $html;
432
-    }
433
-
434
-
435
-    /**
436
-     * _sub_item_row
437
-     *
438
-     * @param EE_Line_Item      $line_item
439
-     * @param array             $options
440
-     * @param EE_Line_Item|null $parent_line_item
441
-     * @return string
442
-     * @throws EE_Error
443
-     * @throws ReflectionException
444
-     */
445
-    private function subItemRow(
446
-        EE_Line_Item $line_item,
447
-        array $options = [],
448
-        ?EE_Line_Item $parent_line_item = null
449
-    ): string {
450
-        if (
451
-            $parent_line_item instanceof EE_Line_Item
452
-            && $line_item->children() === []
453
-            && $line_item->name() === $parent_line_item->name()
454
-            && apply_filters(
455
-                'FHEE__EE_SPCO_Line_Item_Display_Strategy___sub_item_row__hide_main_sub_line_item',
456
-                true
457
-            )
458
-        ) {
459
-            return '';
460
-        }
461
-        $this->debugLog(' - ' . __FUNCTION__);
462
-        // start of row
463
-        $html = EEH_HTML::tr('', '', 'item sub-item-row');
464
-        // name && desc
465
-        $name_and_desc = EEH_HTML::span('', '', 'sub-item-row-bullet dashicons dashicons-arrow-right');
466
-        $name_and_desc .= $line_item->name();
467
-        $name_and_desc .= $options['show_desc']
468
-            ? '<span class="line-sub-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
469
-            : '';
470
-        // name td
471
-        $html .= EEH_HTML::td($name_and_desc, '', 'item_l sub-item');
472
-        $qty  = $parent_line_item instanceof EE_Line_Item ? $parent_line_item->quantity() : 1;
473
-        // discount/surcharge td
474
-        if ($line_item->is_percent()) {
475
-            $html .= EEH_HTML::td(
476
-                EEH_Template::format_currency(
477
-                    $line_item->total() / $qty,
478
-                    false,
479
-                    false
480
-                ),
481
-                '',
482
-                'spco-nowrap item_c jst-rght'
483
-            );
484
-        } else {
485
-            $html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'spco-nowrap item_c jst-rght');
486
-        }
487
-        // no quantity td
488
-        $html .= EEH_HTML::td();
489
-        // no total td
490
-        $html .= EEH_HTML::td();
491
-        // end of row
492
-        $html .= EEH_HTML::trx();
493
-        return apply_filters(
494
-            'FHEE__EE_SPCO_Line_Item_Display_Strategy___sub_item_row__html',
495
-            $html,
496
-            $line_item,
497
-            $options,
498
-            $parent_line_item
499
-        );
500
-    }
501
-
502
-
503
-    /**
504
-     * _tax_row
505
-     *
506
-     * @param EE_Line_Item $line_item
507
-     * @param array        $options
508
-     * @return string
509
-     * @throws EE_Error
510
-     * @throws ReflectionException
511
-     */
512
-    private function taxRow(EE_Line_Item $line_item, array $options = []): string
513
-    {
514
-        $this->debugLog(' - ' . __FUNCTION__);
515
-        $total = $line_item->total();
516
-        if (! $total) {
517
-            return '';
518
-        }
519
-        // start of row
520
-        $html = EEH_HTML::tr('', 'item sub-item tax-total');
521
-        // name && desc
522
-        $name_and_desc = $line_item->name();
523
-        $name_and_desc .= '<span class="smaller-text lt-grey-text" style="margin:0 0 0 2em;">';
524
-        $name_and_desc .= esc_html__(' * taxable items', 'event_espresso');
525
-        $name_and_desc .= '</span>';
526
-        $name_and_desc .= $options['show_desc'] ? '<br/>' . $line_item->desc() : '';
527
-        // name td
528
-        $html .= EEH_HTML::td( /*__FUNCTION__ .*/
529
-            $name_and_desc,
530
-            '',
531
-            'item_l sub-item'
532
-        );
533
-        // percent td
534
-        $html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap jst-rght');
535
-        // empty td (price)
536
-        $html .= EEH_HTML::td(EEH_HTML::nbsp());
537
-        // total td
538
-        $html .= EEH_HTML::td(
539
-            EEH_Template::format_currency($total, false, false),
540
-            '',
541
-            'spco-nowrap item_r jst-rght'
542
-        );
543
-        // end of row
544
-        $html .= EEH_HTML::trx();
545
-        return $html;
546
-    }
547
-
548
-
549
-    /**
550
-     * _total_row
551
-     *
552
-     * @param EE_Line_Item $line_item
553
-     * @param string       $text
554
-     * @return string
555
-     * @throws EE_Error
556
-     * @throws ReflectionException
557
-     */
558
-    private function totalTaxRow(EE_Line_Item $line_item, string $text = ''): string
559
-    {
560
-        $this->debugLog(' - ' . __FUNCTION__);
561
-        $html = '';
562
-        if ($line_item->total()) {
563
-            // start of row
564
-            $html = EEH_HTML::tr('', '', 'total_tr odd');
565
-            // total td
566
-            $html .= EEH_HTML::td(
567
-                $text,
568
-                '',
569
-                'total_currency total jst-rght',
570
-                '',
571
-                ' colspan="2"'
572
-            );
573
-            // empty td (price)
574
-            $html .= EEH_HTML::td(EEH_HTML::nbsp());
575
-            // total td
576
-            $html .= EEH_HTML::td(
577
-                EEH_Template::format_currency($line_item->total(), false, false),
578
-                '',
579
-                'spco-nowrap total jst-rght'
580
-            );
581
-            // end of row
582
-            $html .= EEH_HTML::trx();
583
-        }
584
-        return $html;
585
-    }
586
-
587
-
588
-    /**
589
-     * _total_row
590
-     *
591
-     * @param EE_Line_Item $line_item
592
-     * @param string       $text
593
-     * @param array        $options
594
-     * @return string
595
-     * @throws EE_Error
596
-     * @throws ReflectionException
597
-     */
598
-    private function subTotalRow(EE_Line_Item $line_item, string $text = '', array $options = []): string
599
-    {
600
-        $this->debugLog(' - ' . __FUNCTION__);
601
-        $html = '';
602
-        if ($line_item->total()) {
603
-            // start of row
604
-            $html = EEH_HTML::tr('', '', 'total_tr odd');
605
-            // total td
606
-            $html .= EEH_HTML::td(
607
-                $text,
608
-                '',
609
-                'total_currency total jst-rght',
610
-                '',
611
-                ' colspan="3"'
612
-            );
613
-            // total td
614
-            $html .= EEH_HTML::td(
615
-                EEH_Template::format_currency($options['sub_total'], false, false),
616
-                '',
617
-                'spco-nowrap total jst-rght'
618
-            );
619
-            // end of row
620
-            $html .= EEH_HTML::trx();
621
-        }
622
-        return $html;
623
-    }
624
-
625
-
626
-    /**
627
-     * _total_row
628
-     *
629
-     * @param EE_Line_Item $line_item
630
-     * @param string       $text
631
-     * @return string
632
-     * @throws EE_Error
633
-     * @throws ReflectionException
634
-     */
635
-    private function totalRow(EE_Line_Item $line_item, string $text = ''): string
636
-    {
637
-        $this->debugLog(' - ' . __FUNCTION__);
638
-        // start of row
639
-        $html = EEH_HTML::tr('', '', 'spco-grand-total total_tr odd');
640
-        // total td
641
-        $html .= EEH_HTML::td($text, '', 'total_currency total jst-rght', '', ' colspan="3"');
642
-        // total td
643
-        $html .= EEH_HTML::td(
644
-            EEH_Template::format_currency($line_item->total(), false, false),
645
-            '',
646
-            'spco-nowrap total jst-rght'
647
-        );
648
-        // end of row
649
-        $html .= EEH_HTML::trx();
650
-        return $html;
651
-    }
652
-
653
-
654
-    /**
655
-     * _payments_and_amount_owing_rows
656
-     *
657
-     * @param EE_Line_Item $line_item
658
-     * @param array        $options
659
-     * @return string
660
-     * @throws EE_Error
661
-     * @throws ReflectionException
662
-     */
663
-    private function paymentsAndAmountOwingRows(EE_Line_Item $line_item, array $options = []): string
664
-    {
665
-        $this->debugLog(' - ' . __FUNCTION__);
666
-        $html        = '';
667
-        $owing       = $line_item->total();
668
-        $transaction = EEM_Transaction::instance()->get_one_by_ID($line_item->TXN_ID());
669
-        if ($transaction instanceof EE_Transaction) {
670
-            $registration_payments = [];
671
-            $registrations         = ! empty($options['registrations'])
672
-                ? $options['registrations']
673
-                : $transaction->registrations();
674
-            foreach ($registrations as $registration) {
675
-                if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
676
-                    $registration_payments += $registration->registration_payments();
677
-                }
678
-            }
679
-            if (! empty($registration_payments)) {
680
-                foreach ($registration_payments as $registration_payment) {
681
-                    if ($registration_payment instanceof EE_Registration_Payment) {
682
-                        $owing        -= $registration_payment->amount();
683
-                        $payment      = $registration_payment->payment();
684
-                        $payment_desc = '';
685
-                        if ($payment instanceof EE_Payment) {
686
-                            $payment_desc = sprintf(
687
-                                esc_html__('Payment%1$s Received: %2$s', 'event_espresso'),
688
-                                $payment->txn_id_chq_nmbr() !== ''
689
-                                    ? ' <span class="small-text">(#' . $payment->txn_id_chq_nmbr() . ')</span> '
690
-                                    : '',
691
-                                $payment->timestamp()
692
-                            );
693
-                        }
694
-                        // start of row
695
-                        $html .= EEH_HTML::tr('', '', 'total_tr odd');
696
-                        // payment desc
697
-                        $html .= EEH_HTML::td($payment_desc, '', '', '', ' colspan="3"');
698
-                        // total td
699
-                        $html .= EEH_HTML::td(
700
-                            EEH_Template::format_currency(
701
-                                $registration_payment->amount(),
702
-                                false,
703
-                                false
704
-                            ),
705
-                            '',
706
-                            'spco-nowrap total jst-rght'
707
-                        );
708
-                        // end of row
709
-                        $html .= EEH_HTML::trx();
710
-                    }
711
-                }
712
-                if ($line_item->total()) {
713
-                    // start of row
714
-                    $html .= EEH_HTML::tr('', '', 'total_tr odd');
715
-                    // total td
716
-                    $html .= EEH_HTML::td(
717
-                        esc_html__('Amount Owing', 'event_espresso'),
718
-                        '',
719
-                        'total_currency total jst-rght',
720
-                        '',
721
-                        ' colspan="3"'
722
-                    );
723
-                    // total td
724
-                    $html .= EEH_HTML::td(
725
-                        EEH_Template::format_currency($owing, false, false),
726
-                        '',
727
-                        'spco-nowrap total jst-rght'
728
-                    );
729
-                    // end of row
730
-                    $html .= EEH_HTML::trx();
731
-                }
732
-            }
733
-        }
734
-        $this->_grand_total = $owing;
735
-        return $html;
736
-    }
16
+	use DebugDisplay;
17
+
18
+	protected bool $prices_include_taxes = false;
19
+
20
+	/**
21
+	 * array of events
22
+	 *
23
+	 * @type EE_Line_Item[] $_events
24
+	 */
25
+	private array $_events = [];
26
+
27
+	/**
28
+	 * whether to display the taxes row or not
29
+	 *
30
+	 * @type bool $_show_taxes
31
+	 */
32
+	private bool $_show_taxes = false;
33
+
34
+	/**
35
+	 * html for any tax rows
36
+	 *
37
+	 * @type string $_show_taxes
38
+	 */
39
+	private string $_taxes_html = '';
40
+
41
+	/**
42
+	 * total amount including tax we can bill for at this time
43
+	 *
44
+	 * @type float $_grand_total
45
+	 */
46
+	private float $_grand_total = 0.00;
47
+
48
+	/**
49
+	 * total number of items being billed for
50
+	 *
51
+	 * @type int $_total_items
52
+	 */
53
+	private int $_total_items = 0;
54
+
55
+	private bool $debug = false;   //  true  false
56
+
57
+
58
+	public function __construct()
59
+	{
60
+		$this->prices_include_taxes = EE_Registry::instance()->CFG->tax_settings->prices_displayed_including_taxes;
61
+		$this->initializeDebugDisplay();
62
+	}
63
+
64
+
65
+	/**
66
+	 * @return float
67
+	 */
68
+	public function grand_total(): float
69
+	{
70
+		return $this->_grand_total;
71
+	}
72
+
73
+
74
+	/**
75
+	 * @return int
76
+	 */
77
+	public function total_items(): int
78
+	{
79
+		return $this->_total_items;
80
+	}
81
+
82
+
83
+	/**
84
+	 * @param EE_Line_Item      $line_item
85
+	 * @param array             $options
86
+	 * @param EE_Line_Item|null $parent_line_item
87
+	 * @return string
88
+	 * @throws EE_Error
89
+	 * @throws ReflectionException
90
+	 */
91
+	public function display_line_item(
92
+		EE_Line_Item $line_item,
93
+		$options = [],
94
+		?EE_Line_Item $parent_line_item = null
95
+	): string {
96
+		$html = '';
97
+		// set some default options and merge with incoming
98
+		$options += [
99
+			'show_desc' => true,  //    true        false
100
+			'odd'       => false,
101
+		];
102
+
103
+		$this->debugLog('', 0);
104
+		$this->debugLog(__FUNCTION__);
105
+		$this->debugLog($line_item->name() . ': ' . $line_item->code() . ' (' . $line_item->type() . ')');
106
+		if ($line_item->type() === EEM_Line_Item::type_total) {
107
+			$this->debugLog('******************************************************************', 2);
108
+		}
109
+
110
+		switch ($line_item->type()) {
111
+			case EEM_Line_Item::type_line_item:
112
+				$sub_taxes         = EEH_Line_Item::get_nearest_descendant_of_type(
113
+					$line_item,
114
+					EEM_Line_Item::type_sub_tax
115
+				);
116
+				$show_taxes = $line_item->is_taxable() || $sub_taxes;
117
+				$this->_show_taxes = $show_taxes ? true : $this->_show_taxes;
118
+				$html              .= $line_item->OBJ_type() === 'Ticket'
119
+					? $this->ticketRow($line_item, $options, $show_taxes)
120
+					: $this->itemRow($line_item, $options, $show_taxes);
121
+				if (
122
+					apply_filters(
123
+						'FHEE__EE_SPCO_Line_Item_Display_Strategy__display_line_item__display_sub_line_items',
124
+						true
125
+					)
126
+				) {
127
+					// got any kids?
128
+					$child_line_items = $line_item->children();
129
+					if ($child_line_items) {
130
+						foreach ($child_line_items as $child_line_item) {
131
+							$html .= $this->display_line_item($child_line_item, $options, $line_item);
132
+						}
133
+					}
134
+				}
135
+				break;
136
+
137
+			case EEM_Line_Item::type_sub_line_item:
138
+				$html .= $this->subItemRow($line_item, $options, $parent_line_item);
139
+				break;
140
+
141
+			case EEM_Line_Item::type_sub_tax:
142
+				$this->_show_taxes = true;
143
+				break;
144
+
145
+			case EEM_Line_Item::type_sub_total:
146
+				static $sub_total = 0;
147
+				$event_sub_total = 0;
148
+				$text            = esc_html__('Sub-Total', 'event_espresso');
149
+				if ($line_item->OBJ_type() === 'Event') {
150
+					$options['event_id'] = $line_item->OBJ_ID();
151
+					if (! isset($this->_events[ $options['event_id'] ])) {
152
+						$this->_events[ $options['event_id'] ] = 0;
153
+						$event                                 = EEM_Event::instance()->get_one_by_ID(
154
+							$options['event_id']
155
+						);
156
+						$event_name                            = $event instanceof EE_Event ? $event->name() . ' ' : '';
157
+						// if event has default reg status of Not Approved, then don't display info on it
158
+						if (
159
+							$event instanceof EE_Event
160
+							&& $event->default_registration_status() === RegStatus::AWAITING_REVIEW
161
+						) {
162
+							// unless display is forced
163
+							$display_event = false;
164
+							// unless there are registrations for it that are returning to pay
165
+							if (isset($options['registrations']) && is_array($options['registrations'])) {
166
+								foreach ($options['registrations'] as $registration) {
167
+									if (! $registration instanceof EE_Registration) {
168
+										continue;
169
+									}
170
+									$display_event = $registration->event_ID() === $options['event_id']
171
+									&& $registration->status_ID() !== RegStatus::AWAITING_REVIEW
172
+										? true
173
+										: $display_event;
174
+								}
175
+							}
176
+							if (! $display_event) {
177
+								return '';
178
+							}
179
+						}
180
+						$this->_events[ $options['event_id'] ] = 0;
181
+
182
+						$html .= $this->eventRow($line_item);
183
+						$text = $event_name . esc_html__('Event Sub-Total', 'event_espresso');
184
+					}
185
+				}
186
+				$child_line_items = $line_item->children();
187
+				// loop thru children
188
+				foreach ($child_line_items as $child_line_item) {
189
+					// recursively feed children back into this method
190
+					$html .= $this->display_line_item($child_line_item, $options, $line_item);
191
+				}
192
+				if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
193
+					$event_sub_total += $this->_events[ $options['event_id'] ];
194
+				}
195
+				$sub_total += $event_sub_total;
196
+				$this->debugLog(' = count($child_line_items): ' . count($child_line_items), 3);
197
+				$this->debugLog(' = count($this->_events): ' . count($this->_events), 3);
198
+				if (
199
+					(
200
+						// event subtotals
201
+						$line_item->code() !== 'pre-tax-subtotal'
202
+						&& count($child_line_items) > 1
203
+					) || (
204
+						// pre-tax subtotals
205
+						$line_item->code() === 'pre-tax-subtotal'
206
+						&& count($this->_events) > 1
207
+					)
208
+				) {
209
+					$options['sub_total'] = $line_item->OBJ_type() === 'Event' ? $event_sub_total : $sub_total;
210
+					$html                 .= $this->subTotalRow($line_item, $text, $options);
211
+				} else {
212
+					$this->debugLog('NO EVENT SUBTOTAL', 3);
213
+				}
214
+				break;
215
+
216
+			case EEM_Line_Item::type_tax:
217
+				if ($this->_show_taxes) {
218
+					$this->_taxes_html .= $this->taxRow($line_item, $options);
219
+				}
220
+				break;
221
+
222
+			case EEM_Line_Item::type_tax_sub_total:
223
+				if ($this->_show_taxes) {
224
+					$child_line_items = $line_item->children();
225
+					// loop thru children
226
+					foreach ($child_line_items as $child_line_item) {
227
+						// recursively feed children back into this method
228
+						$html .= $this->display_line_item($child_line_item, $options, $line_item);
229
+					}
230
+					if ($child_line_items) {
231
+						$this->_taxes_html .= $this->totalTaxRow(
232
+							$line_item,
233
+							esc_html__('Tax Total', 'event_espresso')
234
+						);
235
+					}
236
+				}
237
+				break;
238
+
239
+			case EEM_Line_Item::type_total:
240
+				// get all child line items
241
+				$children = $line_item->children();
242
+				// loop thru all non-tax child line items
243
+				foreach ($children as $child_line_item) {
244
+					if ($child_line_item->type() !== EEM_Line_Item::type_tax_sub_total) {
245
+						// recursively feed children back into this method
246
+						$html .= $this->display_line_item($child_line_item, $options, $line_item);
247
+					}
248
+				}
249
+				// now loop thru  tax child line items
250
+				foreach ($children as $child_line_item) {
251
+					if ($child_line_item->type() === EEM_Line_Item::type_tax_sub_total) {
252
+						// recursively feed children back into this method
253
+						$html .= $this->display_line_item($child_line_item, $options, $line_item);
254
+					}
255
+				}
256
+				$html .= $this->_taxes_html;
257
+				$html .= $this->totalRow($line_item, esc_html__('Total', 'event_espresso'));
258
+				$html .= $this->paymentsAndAmountOwingRows($line_item, $options);
259
+				break;
260
+		}
261
+		return $html;
262
+	}
263
+
264
+
265
+	/**
266
+	 * _event_row - basically a Heading row displayed once above each event's ticket rows
267
+	 *
268
+	 * @param EE_Line_Item $line_item
269
+	 * @return string
270
+	 * @throws EE_Error
271
+	 * @throws ReflectionException
272
+	 */
273
+	private function eventRow(EE_Line_Item $line_item): string
274
+	{
275
+		$this->debugLog(' - ' . __FUNCTION__);
276
+		// start of row
277
+		$html = EEH_HTML::tr('', 'event-cart-total-row', 'total_tr odd');
278
+		// event name td
279
+		$html .= EEH_HTML::td(
280
+			EEH_HTML::strong($line_item->name()),
281
+			'',
282
+			'event-header',
283
+			'',
284
+			' colspan="4"'
285
+		);
286
+		// end of row
287
+		$html .= EEH_HTML::trx();
288
+		return $html;
289
+	}
290
+
291
+
292
+	/**
293
+	 * _ticket_row
294
+	 *
295
+	 * @param EE_Line_Item $line_item
296
+	 * @param array        $options
297
+	 * @param bool         $show_taxes
298
+	 * @return string
299
+	 * @throws EE_Error
300
+	 * @throws ReflectionException
301
+	 */
302
+	private function ticketRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
303
+	{
304
+		$this->debugLog(' - ' . __FUNCTION__);
305
+		// start of row
306
+		$row_class = $options['odd'] ? 'item odd' : 'item';
307
+		$html      = EEH_HTML::tr('', '', $row_class);
308
+		// name && desc
309
+		$name_and_desc = apply_filters(
310
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__name',
311
+			$line_item->name(),
312
+			$line_item
313
+		);
314
+		$name_and_desc .= apply_filters(
315
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
316
+			$options['show_desc']
317
+				? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
318
+				: '',
319
+			$line_item,
320
+			$options
321
+		);
322
+		$name_and_desc .= $show_taxes ? ' * ' : '';
323
+		$name_and_desc = apply_filters(
324
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__name_and_desc',
325
+			$name_and_desc,
326
+			$line_item,
327
+			$options
328
+		);
329
+
330
+		// name td
331
+		$html .= EEH_HTML::td(
332
+			$name_and_desc,
333
+			'',
334
+			'item_l'
335
+		);
336
+		// price td
337
+		$price = apply_filters(
338
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__price',
339
+			$line_item->unit_price_no_code(),
340
+			$line_item
341
+		);
342
+		$html  .= EEH_HTML::td($price, '', 'spco-nowrap item_c jst-rght');
343
+		// quantity td
344
+		$html               .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
345
+		$this->_total_items += $line_item->quantity();
346
+		// determine total for line item
347
+		$total = apply_filters(
348
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy___ticket_row__total',
349
+			$line_item->pretaxTotal(),
350
+			$line_item
351
+		);
352
+		if (isset($this->_events[ $options['event_id'] ])) {
353
+			$this->_events[ $options['event_id'] ] += $total;
354
+		}
355
+		// total td
356
+		$html .= EEH_HTML::td(
357
+			EEH_Template::format_currency($total, false, false),
358
+			'',
359
+			'spco-nowrap item_r jst-rght'
360
+		);
361
+		// end of row
362
+		$html .= EEH_HTML::trx();
363
+		return $html;
364
+	}
365
+
366
+
367
+	/**
368
+	 * _item_row
369
+	 *
370
+	 * @param EE_Line_Item $line_item
371
+	 * @param array        $options
372
+	 * @param bool         $show_taxes
373
+	 * @return string
374
+	 * @throws EE_Error
375
+	 * @throws ReflectionException
376
+	 */
377
+	private function itemRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
378
+	{
379
+		$this->debugLog(' - ' . __FUNCTION__);
380
+		// start of row
381
+		$row_class = $options['odd'] ? 'item odd' : 'item';
382
+		$html      = EEH_HTML::tr('', '', $row_class);
383
+		$obj_name  = $line_item->OBJ_type() ? $line_item->OBJ_type_i18n() . ': ' : '';
384
+		// name && desc
385
+		$name_and_desc = apply_filters(
386
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__name',
387
+			$obj_name . $line_item->name(),
388
+			$line_item
389
+		);
390
+		$name_and_desc .= apply_filters(
391
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
392
+			(
393
+			$options['show_desc']
394
+				? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
395
+				: ''
396
+			),
397
+			$line_item,
398
+			$options
399
+		);
400
+		$name_and_desc .= $show_taxes ? ' * ' : '';
401
+		$name_and_desc = apply_filters(
402
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy___item_row__name_and_desc',
403
+			$name_and_desc,
404
+			$line_item,
405
+			$options
406
+		);
407
+
408
+		// name td
409
+		$html .= EEH_HTML::td($name_and_desc, '', 'item_l');
410
+		// price td
411
+		if ($line_item->is_percent()) {
412
+			$html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap item_c jst-rght');
413
+		} else {
414
+			$html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'spco-nowrap item_c jst-rght');
415
+		}
416
+		// quantity td
417
+		$html .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
418
+		// $total = $line_item->total() * $line_item->quantity();
419
+		$total = $line_item->total();
420
+		if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
421
+			$this->_events[ $options['event_id'] ] += $total;
422
+		}
423
+		// total td
424
+		$html .= EEH_HTML::td(
425
+			EEH_Template::format_currency($total, false, false),
426
+			'',
427
+			'spco-nowrap item_r jst-rght'
428
+		);
429
+		// end of row
430
+		$html .= EEH_HTML::trx();
431
+		return $html;
432
+	}
433
+
434
+
435
+	/**
436
+	 * _sub_item_row
437
+	 *
438
+	 * @param EE_Line_Item      $line_item
439
+	 * @param array             $options
440
+	 * @param EE_Line_Item|null $parent_line_item
441
+	 * @return string
442
+	 * @throws EE_Error
443
+	 * @throws ReflectionException
444
+	 */
445
+	private function subItemRow(
446
+		EE_Line_Item $line_item,
447
+		array $options = [],
448
+		?EE_Line_Item $parent_line_item = null
449
+	): string {
450
+		if (
451
+			$parent_line_item instanceof EE_Line_Item
452
+			&& $line_item->children() === []
453
+			&& $line_item->name() === $parent_line_item->name()
454
+			&& apply_filters(
455
+				'FHEE__EE_SPCO_Line_Item_Display_Strategy___sub_item_row__hide_main_sub_line_item',
456
+				true
457
+			)
458
+		) {
459
+			return '';
460
+		}
461
+		$this->debugLog(' - ' . __FUNCTION__);
462
+		// start of row
463
+		$html = EEH_HTML::tr('', '', 'item sub-item-row');
464
+		// name && desc
465
+		$name_and_desc = EEH_HTML::span('', '', 'sub-item-row-bullet dashicons dashicons-arrow-right');
466
+		$name_and_desc .= $line_item->name();
467
+		$name_and_desc .= $options['show_desc']
468
+			? '<span class="line-sub-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
469
+			: '';
470
+		// name td
471
+		$html .= EEH_HTML::td($name_and_desc, '', 'item_l sub-item');
472
+		$qty  = $parent_line_item instanceof EE_Line_Item ? $parent_line_item->quantity() : 1;
473
+		// discount/surcharge td
474
+		if ($line_item->is_percent()) {
475
+			$html .= EEH_HTML::td(
476
+				EEH_Template::format_currency(
477
+					$line_item->total() / $qty,
478
+					false,
479
+					false
480
+				),
481
+				'',
482
+				'spco-nowrap item_c jst-rght'
483
+			);
484
+		} else {
485
+			$html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'spco-nowrap item_c jst-rght');
486
+		}
487
+		// no quantity td
488
+		$html .= EEH_HTML::td();
489
+		// no total td
490
+		$html .= EEH_HTML::td();
491
+		// end of row
492
+		$html .= EEH_HTML::trx();
493
+		return apply_filters(
494
+			'FHEE__EE_SPCO_Line_Item_Display_Strategy___sub_item_row__html',
495
+			$html,
496
+			$line_item,
497
+			$options,
498
+			$parent_line_item
499
+		);
500
+	}
501
+
502
+
503
+	/**
504
+	 * _tax_row
505
+	 *
506
+	 * @param EE_Line_Item $line_item
507
+	 * @param array        $options
508
+	 * @return string
509
+	 * @throws EE_Error
510
+	 * @throws ReflectionException
511
+	 */
512
+	private function taxRow(EE_Line_Item $line_item, array $options = []): string
513
+	{
514
+		$this->debugLog(' - ' . __FUNCTION__);
515
+		$total = $line_item->total();
516
+		if (! $total) {
517
+			return '';
518
+		}
519
+		// start of row
520
+		$html = EEH_HTML::tr('', 'item sub-item tax-total');
521
+		// name && desc
522
+		$name_and_desc = $line_item->name();
523
+		$name_and_desc .= '<span class="smaller-text lt-grey-text" style="margin:0 0 0 2em;">';
524
+		$name_and_desc .= esc_html__(' * taxable items', 'event_espresso');
525
+		$name_and_desc .= '</span>';
526
+		$name_and_desc .= $options['show_desc'] ? '<br/>' . $line_item->desc() : '';
527
+		// name td
528
+		$html .= EEH_HTML::td( /*__FUNCTION__ .*/
529
+			$name_and_desc,
530
+			'',
531
+			'item_l sub-item'
532
+		);
533
+		// percent td
534
+		$html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap jst-rght');
535
+		// empty td (price)
536
+		$html .= EEH_HTML::td(EEH_HTML::nbsp());
537
+		// total td
538
+		$html .= EEH_HTML::td(
539
+			EEH_Template::format_currency($total, false, false),
540
+			'',
541
+			'spco-nowrap item_r jst-rght'
542
+		);
543
+		// end of row
544
+		$html .= EEH_HTML::trx();
545
+		return $html;
546
+	}
547
+
548
+
549
+	/**
550
+	 * _total_row
551
+	 *
552
+	 * @param EE_Line_Item $line_item
553
+	 * @param string       $text
554
+	 * @return string
555
+	 * @throws EE_Error
556
+	 * @throws ReflectionException
557
+	 */
558
+	private function totalTaxRow(EE_Line_Item $line_item, string $text = ''): string
559
+	{
560
+		$this->debugLog(' - ' . __FUNCTION__);
561
+		$html = '';
562
+		if ($line_item->total()) {
563
+			// start of row
564
+			$html = EEH_HTML::tr('', '', 'total_tr odd');
565
+			// total td
566
+			$html .= EEH_HTML::td(
567
+				$text,
568
+				'',
569
+				'total_currency total jst-rght',
570
+				'',
571
+				' colspan="2"'
572
+			);
573
+			// empty td (price)
574
+			$html .= EEH_HTML::td(EEH_HTML::nbsp());
575
+			// total td
576
+			$html .= EEH_HTML::td(
577
+				EEH_Template::format_currency($line_item->total(), false, false),
578
+				'',
579
+				'spco-nowrap total jst-rght'
580
+			);
581
+			// end of row
582
+			$html .= EEH_HTML::trx();
583
+		}
584
+		return $html;
585
+	}
586
+
587
+
588
+	/**
589
+	 * _total_row
590
+	 *
591
+	 * @param EE_Line_Item $line_item
592
+	 * @param string       $text
593
+	 * @param array        $options
594
+	 * @return string
595
+	 * @throws EE_Error
596
+	 * @throws ReflectionException
597
+	 */
598
+	private function subTotalRow(EE_Line_Item $line_item, string $text = '', array $options = []): string
599
+	{
600
+		$this->debugLog(' - ' . __FUNCTION__);
601
+		$html = '';
602
+		if ($line_item->total()) {
603
+			// start of row
604
+			$html = EEH_HTML::tr('', '', 'total_tr odd');
605
+			// total td
606
+			$html .= EEH_HTML::td(
607
+				$text,
608
+				'',
609
+				'total_currency total jst-rght',
610
+				'',
611
+				' colspan="3"'
612
+			);
613
+			// total td
614
+			$html .= EEH_HTML::td(
615
+				EEH_Template::format_currency($options['sub_total'], false, false),
616
+				'',
617
+				'spco-nowrap total jst-rght'
618
+			);
619
+			// end of row
620
+			$html .= EEH_HTML::trx();
621
+		}
622
+		return $html;
623
+	}
624
+
625
+
626
+	/**
627
+	 * _total_row
628
+	 *
629
+	 * @param EE_Line_Item $line_item
630
+	 * @param string       $text
631
+	 * @return string
632
+	 * @throws EE_Error
633
+	 * @throws ReflectionException
634
+	 */
635
+	private function totalRow(EE_Line_Item $line_item, string $text = ''): string
636
+	{
637
+		$this->debugLog(' - ' . __FUNCTION__);
638
+		// start of row
639
+		$html = EEH_HTML::tr('', '', 'spco-grand-total total_tr odd');
640
+		// total td
641
+		$html .= EEH_HTML::td($text, '', 'total_currency total jst-rght', '', ' colspan="3"');
642
+		// total td
643
+		$html .= EEH_HTML::td(
644
+			EEH_Template::format_currency($line_item->total(), false, false),
645
+			'',
646
+			'spco-nowrap total jst-rght'
647
+		);
648
+		// end of row
649
+		$html .= EEH_HTML::trx();
650
+		return $html;
651
+	}
652
+
653
+
654
+	/**
655
+	 * _payments_and_amount_owing_rows
656
+	 *
657
+	 * @param EE_Line_Item $line_item
658
+	 * @param array        $options
659
+	 * @return string
660
+	 * @throws EE_Error
661
+	 * @throws ReflectionException
662
+	 */
663
+	private function paymentsAndAmountOwingRows(EE_Line_Item $line_item, array $options = []): string
664
+	{
665
+		$this->debugLog(' - ' . __FUNCTION__);
666
+		$html        = '';
667
+		$owing       = $line_item->total();
668
+		$transaction = EEM_Transaction::instance()->get_one_by_ID($line_item->TXN_ID());
669
+		if ($transaction instanceof EE_Transaction) {
670
+			$registration_payments = [];
671
+			$registrations         = ! empty($options['registrations'])
672
+				? $options['registrations']
673
+				: $transaction->registrations();
674
+			foreach ($registrations as $registration) {
675
+				if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
676
+					$registration_payments += $registration->registration_payments();
677
+				}
678
+			}
679
+			if (! empty($registration_payments)) {
680
+				foreach ($registration_payments as $registration_payment) {
681
+					if ($registration_payment instanceof EE_Registration_Payment) {
682
+						$owing        -= $registration_payment->amount();
683
+						$payment      = $registration_payment->payment();
684
+						$payment_desc = '';
685
+						if ($payment instanceof EE_Payment) {
686
+							$payment_desc = sprintf(
687
+								esc_html__('Payment%1$s Received: %2$s', 'event_espresso'),
688
+								$payment->txn_id_chq_nmbr() !== ''
689
+									? ' <span class="small-text">(#' . $payment->txn_id_chq_nmbr() . ')</span> '
690
+									: '',
691
+								$payment->timestamp()
692
+							);
693
+						}
694
+						// start of row
695
+						$html .= EEH_HTML::tr('', '', 'total_tr odd');
696
+						// payment desc
697
+						$html .= EEH_HTML::td($payment_desc, '', '', '', ' colspan="3"');
698
+						// total td
699
+						$html .= EEH_HTML::td(
700
+							EEH_Template::format_currency(
701
+								$registration_payment->amount(),
702
+								false,
703
+								false
704
+							),
705
+							'',
706
+							'spco-nowrap total jst-rght'
707
+						);
708
+						// end of row
709
+						$html .= EEH_HTML::trx();
710
+					}
711
+				}
712
+				if ($line_item->total()) {
713
+					// start of row
714
+					$html .= EEH_HTML::tr('', '', 'total_tr odd');
715
+					// total td
716
+					$html .= EEH_HTML::td(
717
+						esc_html__('Amount Owing', 'event_espresso'),
718
+						'',
719
+						'total_currency total jst-rght',
720
+						'',
721
+						' colspan="3"'
722
+					);
723
+					// total td
724
+					$html .= EEH_HTML::td(
725
+						EEH_Template::format_currency($owing, false, false),
726
+						'',
727
+						'spco-nowrap total jst-rght'
728
+					);
729
+					// end of row
730
+					$html .= EEH_HTML::trx();
731
+				}
732
+			}
733
+		}
734
+		$this->_grand_total = $owing;
735
+		return $html;
736
+	}
737 737
 }
Please login to merge, or discard this patch.
Spacing   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -52,7 +52,7 @@  discard block
 block discarded – undo
52 52
      */
53 53
     private int $_total_items = 0;
54 54
 
55
-    private bool $debug = false;   //  true  false
55
+    private bool $debug = false; //  true  false
56 56
 
57 57
 
58 58
     public function __construct()
@@ -96,26 +96,26 @@  discard block
 block discarded – undo
96 96
         $html = '';
97 97
         // set some default options and merge with incoming
98 98
         $options += [
99
-            'show_desc' => true,  //    true        false
99
+            'show_desc' => true, //    true        false
100 100
             'odd'       => false,
101 101
         ];
102 102
 
103 103
         $this->debugLog('', 0);
104 104
         $this->debugLog(__FUNCTION__);
105
-        $this->debugLog($line_item->name() . ': ' . $line_item->code() . ' (' . $line_item->type() . ')');
105
+        $this->debugLog($line_item->name().': '.$line_item->code().' ('.$line_item->type().')');
106 106
         if ($line_item->type() === EEM_Line_Item::type_total) {
107 107
             $this->debugLog('******************************************************************', 2);
108 108
         }
109 109
 
110 110
         switch ($line_item->type()) {
111 111
             case EEM_Line_Item::type_line_item:
112
-                $sub_taxes         = EEH_Line_Item::get_nearest_descendant_of_type(
112
+                $sub_taxes = EEH_Line_Item::get_nearest_descendant_of_type(
113 113
                     $line_item,
114 114
                     EEM_Line_Item::type_sub_tax
115 115
                 );
116 116
                 $show_taxes = $line_item->is_taxable() || $sub_taxes;
117 117
                 $this->_show_taxes = $show_taxes ? true : $this->_show_taxes;
118
-                $html              .= $line_item->OBJ_type() === 'Ticket'
118
+                $html .= $line_item->OBJ_type() === 'Ticket'
119 119
                     ? $this->ticketRow($line_item, $options, $show_taxes)
120 120
                     : $this->itemRow($line_item, $options, $show_taxes);
121 121
                 if (
@@ -148,12 +148,12 @@  discard block
 block discarded – undo
148 148
                 $text            = esc_html__('Sub-Total', 'event_espresso');
149 149
                 if ($line_item->OBJ_type() === 'Event') {
150 150
                     $options['event_id'] = $line_item->OBJ_ID();
151
-                    if (! isset($this->_events[ $options['event_id'] ])) {
152
-                        $this->_events[ $options['event_id'] ] = 0;
151
+                    if ( ! isset($this->_events[$options['event_id']])) {
152
+                        $this->_events[$options['event_id']] = 0;
153 153
                         $event                                 = EEM_Event::instance()->get_one_by_ID(
154 154
                             $options['event_id']
155 155
                         );
156
-                        $event_name                            = $event instanceof EE_Event ? $event->name() . ' ' : '';
156
+                        $event_name                            = $event instanceof EE_Event ? $event->name().' ' : '';
157 157
                         // if event has default reg status of Not Approved, then don't display info on it
158 158
                         if (
159 159
                             $event instanceof EE_Event
@@ -164,7 +164,7 @@  discard block
 block discarded – undo
164 164
                             // unless there are registrations for it that are returning to pay
165 165
                             if (isset($options['registrations']) && is_array($options['registrations'])) {
166 166
                                 foreach ($options['registrations'] as $registration) {
167
-                                    if (! $registration instanceof EE_Registration) {
167
+                                    if ( ! $registration instanceof EE_Registration) {
168 168
                                         continue;
169 169
                                     }
170 170
                                     $display_event = $registration->event_ID() === $options['event_id']
@@ -173,14 +173,14 @@  discard block
 block discarded – undo
173 173
                                         : $display_event;
174 174
                                 }
175 175
                             }
176
-                            if (! $display_event) {
176
+                            if ( ! $display_event) {
177 177
                                 return '';
178 178
                             }
179 179
                         }
180
-                        $this->_events[ $options['event_id'] ] = 0;
180
+                        $this->_events[$options['event_id']] = 0;
181 181
 
182 182
                         $html .= $this->eventRow($line_item);
183
-                        $text = $event_name . esc_html__('Event Sub-Total', 'event_espresso');
183
+                        $text = $event_name.esc_html__('Event Sub-Total', 'event_espresso');
184 184
                     }
185 185
                 }
186 186
                 $child_line_items = $line_item->children();
@@ -189,12 +189,12 @@  discard block
 block discarded – undo
189 189
                     // recursively feed children back into this method
190 190
                     $html .= $this->display_line_item($child_line_item, $options, $line_item);
191 191
                 }
192
-                if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
193
-                    $event_sub_total += $this->_events[ $options['event_id'] ];
192
+                if (isset($options['event_id'], $this->_events[$options['event_id']])) {
193
+                    $event_sub_total += $this->_events[$options['event_id']];
194 194
                 }
195 195
                 $sub_total += $event_sub_total;
196
-                $this->debugLog(' = count($child_line_items): ' . count($child_line_items), 3);
197
-                $this->debugLog(' = count($this->_events): ' . count($this->_events), 3);
196
+                $this->debugLog(' = count($child_line_items): '.count($child_line_items), 3);
197
+                $this->debugLog(' = count($this->_events): '.count($this->_events), 3);
198 198
                 if (
199 199
                     (
200 200
                         // event subtotals
@@ -207,7 +207,7 @@  discard block
 block discarded – undo
207 207
                     )
208 208
                 ) {
209 209
                     $options['sub_total'] = $line_item->OBJ_type() === 'Event' ? $event_sub_total : $sub_total;
210
-                    $html                 .= $this->subTotalRow($line_item, $text, $options);
210
+                    $html .= $this->subTotalRow($line_item, $text, $options);
211 211
                 } else {
212 212
                     $this->debugLog('NO EVENT SUBTOTAL', 3);
213 213
                 }
@@ -272,7 +272,7 @@  discard block
 block discarded – undo
272 272
      */
273 273
     private function eventRow(EE_Line_Item $line_item): string
274 274
     {
275
-        $this->debugLog(' - ' . __FUNCTION__);
275
+        $this->debugLog(' - '.__FUNCTION__);
276 276
         // start of row
277 277
         $html = EEH_HTML::tr('', 'event-cart-total-row', 'total_tr odd');
278 278
         // event name td
@@ -301,7 +301,7 @@  discard block
 block discarded – undo
301 301
      */
302 302
     private function ticketRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
303 303
     {
304
-        $this->debugLog(' - ' . __FUNCTION__);
304
+        $this->debugLog(' - '.__FUNCTION__);
305 305
         // start of row
306 306
         $row_class = $options['odd'] ? 'item odd' : 'item';
307 307
         $html      = EEH_HTML::tr('', '', $row_class);
@@ -314,7 +314,7 @@  discard block
 block discarded – undo
314 314
         $name_and_desc .= apply_filters(
315 315
             'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
316 316
             $options['show_desc']
317
-                ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
317
+                ? '<span class="line-item-desc-spn smaller-text">: '.$line_item->desc().'</span>'
318 318
                 : '',
319 319
             $line_item,
320 320
             $options
@@ -339,7 +339,7 @@  discard block
 block discarded – undo
339 339
             $line_item->unit_price_no_code(),
340 340
             $line_item
341 341
         );
342
-        $html  .= EEH_HTML::td($price, '', 'spco-nowrap item_c jst-rght');
342
+        $html .= EEH_HTML::td($price, '', 'spco-nowrap item_c jst-rght');
343 343
         // quantity td
344 344
         $html               .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
345 345
         $this->_total_items += $line_item->quantity();
@@ -349,8 +349,8 @@  discard block
 block discarded – undo
349 349
             $line_item->pretaxTotal(),
350 350
             $line_item
351 351
         );
352
-        if (isset($this->_events[ $options['event_id'] ])) {
353
-            $this->_events[ $options['event_id'] ] += $total;
352
+        if (isset($this->_events[$options['event_id']])) {
353
+            $this->_events[$options['event_id']] += $total;
354 354
         }
355 355
         // total td
356 356
         $html .= EEH_HTML::td(
@@ -376,22 +376,22 @@  discard block
 block discarded – undo
376 376
      */
377 377
     private function itemRow(EE_Line_Item $line_item, array $options = [], bool $show_taxes = false): string
378 378
     {
379
-        $this->debugLog(' - ' . __FUNCTION__);
379
+        $this->debugLog(' - '.__FUNCTION__);
380 380
         // start of row
381 381
         $row_class = $options['odd'] ? 'item odd' : 'item';
382 382
         $html      = EEH_HTML::tr('', '', $row_class);
383
-        $obj_name  = $line_item->OBJ_type() ? $line_item->OBJ_type_i18n() . ': ' : '';
383
+        $obj_name  = $line_item->OBJ_type() ? $line_item->OBJ_type_i18n().': ' : '';
384 384
         // name && desc
385 385
         $name_and_desc = apply_filters(
386 386
             'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__name',
387
-            $obj_name . $line_item->name(),
387
+            $obj_name.$line_item->name(),
388 388
             $line_item
389 389
         );
390 390
         $name_and_desc .= apply_filters(
391 391
             'FHEE__EE_SPCO_Line_Item_Display_Strategy__item_row__desc',
392 392
             (
393 393
             $options['show_desc']
394
-                ? '<span class="line-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
394
+                ? '<span class="line-item-desc-spn smaller-text">: '.$line_item->desc().'</span>'
395 395
                 : ''
396 396
             ),
397 397
             $line_item,
@@ -409,7 +409,7 @@  discard block
 block discarded – undo
409 409
         $html .= EEH_HTML::td($name_and_desc, '', 'item_l');
410 410
         // price td
411 411
         if ($line_item->is_percent()) {
412
-            $html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap item_c jst-rght');
412
+            $html .= EEH_HTML::td($line_item->percent().'%', '', 'spco-nowrap item_c jst-rght');
413 413
         } else {
414 414
             $html .= EEH_HTML::td($line_item->unit_price_no_code(), '', 'spco-nowrap item_c jst-rght');
415 415
         }
@@ -417,8 +417,8 @@  discard block
 block discarded – undo
417 417
         $html .= EEH_HTML::td($line_item->quantity(), '', 'spco-nowrap item_l jst-rght');
418 418
         // $total = $line_item->total() * $line_item->quantity();
419 419
         $total = $line_item->total();
420
-        if (isset($options['event_id'], $this->_events[ $options['event_id'] ])) {
421
-            $this->_events[ $options['event_id'] ] += $total;
420
+        if (isset($options['event_id'], $this->_events[$options['event_id']])) {
421
+            $this->_events[$options['event_id']] += $total;
422 422
         }
423 423
         // total td
424 424
         $html .= EEH_HTML::td(
@@ -458,18 +458,18 @@  discard block
 block discarded – undo
458 458
         ) {
459 459
             return '';
460 460
         }
461
-        $this->debugLog(' - ' . __FUNCTION__);
461
+        $this->debugLog(' - '.__FUNCTION__);
462 462
         // start of row
463 463
         $html = EEH_HTML::tr('', '', 'item sub-item-row');
464 464
         // name && desc
465 465
         $name_and_desc = EEH_HTML::span('', '', 'sub-item-row-bullet dashicons dashicons-arrow-right');
466 466
         $name_and_desc .= $line_item->name();
467 467
         $name_and_desc .= $options['show_desc']
468
-            ? '<span class="line-sub-item-desc-spn smaller-text">: ' . $line_item->desc() . '</span>'
468
+            ? '<span class="line-sub-item-desc-spn smaller-text">: '.$line_item->desc().'</span>'
469 469
             : '';
470 470
         // name td
471 471
         $html .= EEH_HTML::td($name_and_desc, '', 'item_l sub-item');
472
-        $qty  = $parent_line_item instanceof EE_Line_Item ? $parent_line_item->quantity() : 1;
472
+        $qty = $parent_line_item instanceof EE_Line_Item ? $parent_line_item->quantity() : 1;
473 473
         // discount/surcharge td
474 474
         if ($line_item->is_percent()) {
475 475
             $html .= EEH_HTML::td(
@@ -511,9 +511,9 @@  discard block
 block discarded – undo
511 511
      */
512 512
     private function taxRow(EE_Line_Item $line_item, array $options = []): string
513 513
     {
514
-        $this->debugLog(' - ' . __FUNCTION__);
514
+        $this->debugLog(' - '.__FUNCTION__);
515 515
         $total = $line_item->total();
516
-        if (! $total) {
516
+        if ( ! $total) {
517 517
             return '';
518 518
         }
519 519
         // start of row
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
         $name_and_desc .= '<span class="smaller-text lt-grey-text" style="margin:0 0 0 2em;">';
524 524
         $name_and_desc .= esc_html__(' * taxable items', 'event_espresso');
525 525
         $name_and_desc .= '</span>';
526
-        $name_and_desc .= $options['show_desc'] ? '<br/>' . $line_item->desc() : '';
526
+        $name_and_desc .= $options['show_desc'] ? '<br/>'.$line_item->desc() : '';
527 527
         // name td
528 528
         $html .= EEH_HTML::td( /*__FUNCTION__ .*/
529 529
             $name_and_desc,
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
             'item_l sub-item'
532 532
         );
533 533
         // percent td
534
-        $html .= EEH_HTML::td($line_item->percent() . '%', '', 'spco-nowrap jst-rght');
534
+        $html .= EEH_HTML::td($line_item->percent().'%', '', 'spco-nowrap jst-rght');
535 535
         // empty td (price)
536 536
         $html .= EEH_HTML::td(EEH_HTML::nbsp());
537 537
         // total td
@@ -557,7 +557,7 @@  discard block
 block discarded – undo
557 557
      */
558 558
     private function totalTaxRow(EE_Line_Item $line_item, string $text = ''): string
559 559
     {
560
-        $this->debugLog(' - ' . __FUNCTION__);
560
+        $this->debugLog(' - '.__FUNCTION__);
561 561
         $html = '';
562 562
         if ($line_item->total()) {
563 563
             // start of row
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
      */
598 598
     private function subTotalRow(EE_Line_Item $line_item, string $text = '', array $options = []): string
599 599
     {
600
-        $this->debugLog(' - ' . __FUNCTION__);
600
+        $this->debugLog(' - '.__FUNCTION__);
601 601
         $html = '';
602 602
         if ($line_item->total()) {
603 603
             // start of row
@@ -634,7 +634,7 @@  discard block
 block discarded – undo
634 634
      */
635 635
     private function totalRow(EE_Line_Item $line_item, string $text = ''): string
636 636
     {
637
-        $this->debugLog(' - ' . __FUNCTION__);
637
+        $this->debugLog(' - '.__FUNCTION__);
638 638
         // start of row
639 639
         $html = EEH_HTML::tr('', '', 'spco-grand-total total_tr odd');
640 640
         // total td
@@ -662,7 +662,7 @@  discard block
 block discarded – undo
662 662
      */
663 663
     private function paymentsAndAmountOwingRows(EE_Line_Item $line_item, array $options = []): string
664 664
     {
665
-        $this->debugLog(' - ' . __FUNCTION__);
665
+        $this->debugLog(' - '.__FUNCTION__);
666 666
         $html        = '';
667 667
         $owing       = $line_item->total();
668 668
         $transaction = EEM_Transaction::instance()->get_one_by_ID($line_item->TXN_ID());
@@ -676,17 +676,17 @@  discard block
 block discarded – undo
676 676
                     $registration_payments += $registration->registration_payments();
677 677
                 }
678 678
             }
679
-            if (! empty($registration_payments)) {
679
+            if ( ! empty($registration_payments)) {
680 680
                 foreach ($registration_payments as $registration_payment) {
681 681
                     if ($registration_payment instanceof EE_Registration_Payment) {
682
-                        $owing        -= $registration_payment->amount();
682
+                        $owing -= $registration_payment->amount();
683 683
                         $payment      = $registration_payment->payment();
684 684
                         $payment_desc = '';
685 685
                         if ($payment instanceof EE_Payment) {
686 686
                             $payment_desc = sprintf(
687 687
                                 esc_html__('Payment%1$s Received: %2$s', 'event_espresso'),
688 688
                                 $payment->txn_id_chq_nmbr() !== ''
689
-                                    ? ' <span class="small-text">(#' . $payment->txn_id_chq_nmbr() . ')</span> '
689
+                                    ? ' <span class="small-text">(#'.$payment->txn_id_chq_nmbr().')</span> '
690 690
                                     : '',
691 691
                                 $payment->timestamp()
692 692
                             );
Please login to merge, or discard this patch.
core/db_classes/EE_Registration.class.php 1 patch
Indentation   +2597 added lines, -2597 removed lines patch added patch discarded remove patch
@@ -19,2601 +19,2601 @@
 block discarded – undo
19 19
  */
20 20
 class EE_Registration extends EE_Soft_Delete_Base_Class implements EEI_Registration, EEI_Admin_Links
21 21
 {
22
-    /**
23
-     * extra meta key for tracking reg status os trashed registrations
24
-     *
25
-     * @type string
26
-     */
27
-    public const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
28
-
29
-    /**
30
-     * extra meta key for tracking if registration has reserved ticket
31
-     *
32
-     * @type string
33
-     */
34
-    public const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
35
-
36
-    /**
37
-     * extra meta key for tracking registration cancellations
38
-     *
39
-     * @type string
40
-     */
41
-    public const META_KEY_REG_STATUS_CHANGE = 'registration_status_change';
42
-
43
-
44
-    /**
45
-     * @param array  $props_n_values          incoming values
46
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
47
-     *                                        used.)
48
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
49
-     *                                        date_format and the second value is the time format
50
-     * @return EE_Registration
51
-     * @throws EE_Error
52
-     * @throws InvalidArgumentException
53
-     * @throws InvalidDataTypeException
54
-     * @throws InvalidInterfaceException
55
-     * @throws ReflectionException
56
-     */
57
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
58
-    {
59
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
60
-        return $has_object
61
-            ?: new self($props_n_values, false, $timezone, $date_formats);
62
-    }
63
-
64
-
65
-    /**
66
-     * @param array  $props_n_values  incoming values from the database
67
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
68
-     *                                the website will be used.
69
-     * @return EE_Registration
70
-     * @throws EE_Error
71
-     * @throws InvalidArgumentException
72
-     * @throws InvalidDataTypeException
73
-     * @throws InvalidInterfaceException
74
-     * @throws ReflectionException
75
-     */
76
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
77
-    {
78
-        return new self($props_n_values, true, $timezone);
79
-    }
80
-
81
-
82
-    /**
83
-     *        Set Event ID
84
-     *
85
-     * @param int $EVT_ID Event ID
86
-     * @throws DomainException
87
-     * @throws EE_Error
88
-     * @throws EntityNotFoundException
89
-     * @throws InvalidArgumentException
90
-     * @throws InvalidDataTypeException
91
-     * @throws InvalidInterfaceException
92
-     * @throws ReflectionException
93
-     * @throws RuntimeException
94
-     * @throws UnexpectedEntityException
95
-     */
96
-    public function set_event($EVT_ID = 0)
97
-    {
98
-        $this->set('EVT_ID', $EVT_ID);
99
-    }
100
-
101
-
102
-    /**
103
-     * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
104
-     * be routed to internal methods
105
-     *
106
-     * @param string $field_name
107
-     * @param mixed  $field_value
108
-     * @param bool   $use_default
109
-     * @throws DomainException
110
-     * @throws EE_Error
111
-     * @throws EntityNotFoundException
112
-     * @throws InvalidArgumentException
113
-     * @throws InvalidDataTypeException
114
-     * @throws InvalidInterfaceException
115
-     * @throws ReflectionException
116
-     * @throws RuntimeException
117
-     * @throws UnexpectedEntityException
118
-     */
119
-    public function set($field_name, $field_value, $use_default = false)
120
-    {
121
-        switch ($field_name) {
122
-            case 'REG_code':
123
-                if (! empty($field_value) && ! $this->reg_code()) {
124
-                    $this->set_reg_code($field_value, $use_default);
125
-                }
126
-                break;
127
-            case 'STS_ID':
128
-                $this->set_status((string) $field_value, $use_default);
129
-                break;
130
-            default:
131
-                parent::set($field_name, $field_value, $use_default);
132
-        }
133
-    }
134
-
135
-
136
-    /**
137
-     * Set Status ID
138
-     * updates the registration status and ALSO...
139
-     * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
140
-     * calls release_registration_space() if the reg status changes FROM approved to any other reg status
141
-     *
142
-     * @param string                $new_STS_ID
143
-     * @param boolean               $use_default
144
-     * @param ContextInterface|null $context
145
-     * @return bool
146
-     * @throws DomainException
147
-     * @throws EE_Error
148
-     * @throws EntityNotFoundException
149
-     * @throws InvalidArgumentException
150
-     * @throws InvalidDataTypeException
151
-     * @throws InvalidInterfaceException
152
-     * @throws ReflectionException
153
-     * @throws RuntimeException
154
-     * @throws UnexpectedEntityException
155
-     */
156
-    public function set_status(
157
-        string $new_STS_ID = '',
158
-        bool $use_default = false,
159
-        ?ContextInterface $context = null
160
-    ): bool {
161
-        // get current REG_Status
162
-        $old_STS_ID = $this->status_ID();
163
-        $new_STS_ID = (string) apply_filters(
164
-            'AFEE__EE_Registration__set_status__new_STS_ID',
165
-            $new_STS_ID,
166
-            $context,
167
-            $this
168
-        );
169
-        // it's still good to allow the parent set method to have a say
170
-        parent::set('STS_ID', (! empty($new_STS_ID) ? $new_STS_ID : null), $use_default);
171
-        // if status has changed
172
-        if (
173
-            $old_STS_ID !== $new_STS_ID // and that status has actually changed
174
-            && ! empty($old_STS_ID) // and that old status is actually set
175
-            && ! empty($new_STS_ID) // as well as the new status
176
-            && $this->ID() // ensure registration is in the db
177
-        ) {
178
-            // THEN handle other changes that occur when reg status changes
179
-            // TO approved
180
-            if ($new_STS_ID === RegStatus::APPROVED) {
181
-                // reserve a space by incrementing ticket and datetime sold values
182
-                $this->reserveRegistrationSpace();
183
-                do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
184
-                // OR FROM  approved
185
-            } elseif ($old_STS_ID === RegStatus::APPROVED) {
186
-                // release a space by decrementing ticket and datetime sold values
187
-                $this->releaseRegistrationSpace();
188
-                do_action(
189
-                    'AHEE__EE_Registration__set_status__from_approved',
190
-                    $this,
191
-                    $old_STS_ID,
192
-                    $new_STS_ID,
193
-                    $context
194
-                );
195
-            }
196
-            $this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
197
-            if ($this->statusChangeUpdatesTransaction($context)) {
198
-                $this->updateTransactionAfterStatusChange();
199
-            }
200
-            do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
201
-        }
202
-        return ! empty($new_STS_ID);
203
-    }
204
-
205
-
206
-    /**
207
-     * update REGs and TXN when cancelled or declined registrations involved
208
-     *
209
-     * @param string                $new_STS_ID
210
-     * @param string                $old_STS_ID
211
-     * @param ContextInterface|null $context
212
-     * @throws EE_Error
213
-     * @throws InvalidArgumentException
214
-     * @throws InvalidDataTypeException
215
-     * @throws InvalidInterfaceException
216
-     * @throws ReflectionException
217
-     * @throws RuntimeException
218
-     */
219
-    private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ?ContextInterface $context = null)
220
-    {
221
-        // these reg statuses should not be considered in any calculations involving monies owing
222
-        $closed_reg_statuses = EEM_Registration::closed_reg_statuses();
223
-        // true if registration has been cancelled or declined
224
-        $this->updateIfCanceled(
225
-            $closed_reg_statuses,
226
-            $new_STS_ID,
227
-            $old_STS_ID,
228
-            $context
229
-        );
230
-        $this->updateIfReinstated(
231
-            $closed_reg_statuses,
232
-            $new_STS_ID,
233
-            $old_STS_ID,
234
-            $context
235
-        );
236
-    }
237
-
238
-
239
-    /**
240
-     * update REGs and TXN when cancelled or declined registrations involved
241
-     *
242
-     * @param array                 $closed_reg_statuses
243
-     * @param string                $new_STS_ID
244
-     * @param string                $old_STS_ID
245
-     * @param ContextInterface|null $context
246
-     * @throws EE_Error
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     * @throws ReflectionException
251
-     * @throws RuntimeException
252
-     */
253
-    private function updateIfCanceled(
254
-        array $closed_reg_statuses,
255
-        $new_STS_ID,
256
-        $old_STS_ID,
257
-        ?ContextInterface $context = null
258
-    ) {
259
-        // true if registration has been cancelled or declined
260
-        if (
261
-            in_array($new_STS_ID, $closed_reg_statuses, true)
262
-            && ! in_array($old_STS_ID, $closed_reg_statuses, true)
263
-        ) {
264
-            /** @type EE_Registration_Processor $registration_processor */
265
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
266
-            /** @type EE_Transaction_Processor $transaction_processor */
267
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
268
-            // cancelled or declined registration
269
-            $registration_processor->update_registration_after_being_canceled_or_declined(
270
-                $this,
271
-                $closed_reg_statuses
272
-            );
273
-            $transaction_processor->update_transaction_after_canceled_or_declined_registration(
274
-                $this,
275
-                $closed_reg_statuses,
276
-                false
277
-            );
278
-            do_action(
279
-                'AHEE__EE_Registration__set_status__canceled_or_declined',
280
-                $this,
281
-                $old_STS_ID,
282
-                $new_STS_ID,
283
-                $context
284
-            );
285
-        }
286
-    }
287
-
288
-
289
-    /**
290
-     * update REGs and TXN when cancelled or declined registrations involved
291
-     *
292
-     * @param array                 $closed_reg_statuses
293
-     * @param string                $new_STS_ID
294
-     * @param string                $old_STS_ID
295
-     * @param ContextInterface|null $context
296
-     * @throws EE_Error
297
-     * @throws InvalidArgumentException
298
-     * @throws InvalidDataTypeException
299
-     * @throws InvalidInterfaceException
300
-     * @throws ReflectionException
301
-     * @throws RuntimeException
302
-     */
303
-    private function updateIfReinstated(
304
-        array $closed_reg_statuses,
305
-        $new_STS_ID,
306
-        $old_STS_ID,
307
-        ?ContextInterface $context = null
308
-    ) {
309
-        // true if reinstating cancelled or declined registration
310
-        if (
311
-            in_array($old_STS_ID, $closed_reg_statuses, true)
312
-            && ! in_array($new_STS_ID, $closed_reg_statuses, true)
313
-        ) {
314
-            /** @type EE_Registration_Processor $registration_processor */
315
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
316
-            /** @type EE_Transaction_Processor $transaction_processor */
317
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
318
-            // reinstating cancelled or declined registration
319
-            $registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
320
-                $this,
321
-                $closed_reg_statuses
322
-            );
323
-            $transaction_processor->update_transaction_after_reinstating_canceled_registration(
324
-                $this,
325
-                $closed_reg_statuses,
326
-                false
327
-            );
328
-            do_action(
329
-                'AHEE__EE_Registration__set_status__after_reinstated',
330
-                $this,
331
-                $old_STS_ID,
332
-                $new_STS_ID,
333
-                $context
334
-            );
335
-        }
336
-    }
337
-
338
-
339
-    /**
340
-     * @param ContextInterface|null $context
341
-     * @return bool
342
-     */
343
-    private function statusChangeUpdatesTransaction(?ContextInterface $context = null)
344
-    {
345
-        $contexts_that_do_not_update_transaction = (array) apply_filters(
346
-            'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
347
-            ['spco_reg_step_attendee_information_process_registrations'],
348
-            $context,
349
-            $this
350
-        );
351
-        return ! (
352
-            $context instanceof ContextInterface
353
-            && in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
354
-        );
355
-    }
356
-
357
-
358
-    /**
359
-     * @throws EE_Error
360
-     * @throws EntityNotFoundException
361
-     * @throws InvalidArgumentException
362
-     * @throws InvalidDataTypeException
363
-     * @throws InvalidInterfaceException
364
-     * @throws ReflectionException
365
-     * @throws RuntimeException
366
-     */
367
-    private function updateTransactionAfterStatusChange()
368
-    {
369
-        /** @type EE_Transaction_Payments $transaction_payments */
370
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
371
-        $transaction_payments->recalculate_transaction_total($this->transaction(), false);
372
-        $this->transaction()->update_status_based_on_total_paid();
373
-    }
374
-
375
-
376
-    /**
377
-     * get Status ID
378
-     *
379
-     * @throws EE_Error
380
-     * @throws InvalidArgumentException
381
-     * @throws InvalidDataTypeException
382
-     * @throws InvalidInterfaceException
383
-     * @throws ReflectionException
384
-     */
385
-    public function status_ID()
386
-    {
387
-        return $this->get('STS_ID');
388
-    }
389
-
390
-
391
-    /**
392
-     * Gets the ticket this registration is for
393
-     *
394
-     * @param boolean $include_archived whether to include archived tickets or not.
395
-     * @return EE_Ticket|EE_Base_Class
396
-     * @throws EE_Error
397
-     * @throws InvalidArgumentException
398
-     * @throws InvalidDataTypeException
399
-     * @throws InvalidInterfaceException
400
-     * @throws ReflectionException
401
-     */
402
-    public function ticket($include_archived = true)
403
-    {
404
-        return EEM_Ticket::instance()->get_one_by_ID($this->ticket_ID());
405
-    }
406
-
407
-
408
-    /**
409
-     * Gets the event this registration is for
410
-     *
411
-     * @return EE_Event
412
-     * @throws EE_Error
413
-     * @throws EntityNotFoundException
414
-     * @throws InvalidArgumentException
415
-     * @throws InvalidDataTypeException
416
-     * @throws InvalidInterfaceException
417
-     * @throws ReflectionException
418
-     */
419
-    public function event(): EE_Event
420
-    {
421
-        $event = $this->event_obj();
422
-        if (! $event instanceof EE_Event) {
423
-            throw new EntityNotFoundException('Event ID', $this->event_ID());
424
-        }
425
-        return $event;
426
-    }
427
-
428
-
429
-    /**
430
-     * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
431
-     * with the author of the event this registration is for.
432
-     *
433
-     * @return int
434
-     * @throws EE_Error
435
-     * @throws EntityNotFoundException
436
-     * @throws InvalidArgumentException
437
-     * @throws InvalidDataTypeException
438
-     * @throws InvalidInterfaceException
439
-     * @throws ReflectionException
440
-     * @since 4.5.0
441
-     */
442
-    public function wp_user(): int
443
-    {
444
-        return $this->event()->wp_user();
445
-    }
446
-
447
-
448
-    /**
449
-     * increments this registration's related ticket sold and corresponding datetime sold values
450
-     *
451
-     * @return void
452
-     * @throws DomainException
453
-     * @throws EE_Error
454
-     * @throws EntityNotFoundException
455
-     * @throws InvalidArgumentException
456
-     * @throws InvalidDataTypeException
457
-     * @throws InvalidInterfaceException
458
-     * @throws ReflectionException
459
-     * @throws UnexpectedEntityException
460
-     */
461
-    private function reserveRegistrationSpace()
462
-    {
463
-        // reserved ticket and datetime counts will be decremented as sold counts are incremented
464
-        // so stop tracking that this reg has a ticket reserved
465
-        $this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
466
-        $ticket = $this->ticket();
467
-        $ticket->increaseSold();
468
-        // possibly set event status to sold out
469
-        $this->event()->perform_sold_out_status_check();
470
-    }
471
-
472
-
473
-    /**
474
-     * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
475
-     *
476
-     * @return void
477
-     * @throws DomainException
478
-     * @throws EE_Error
479
-     * @throws EntityNotFoundException
480
-     * @throws InvalidArgumentException
481
-     * @throws InvalidDataTypeException
482
-     * @throws InvalidInterfaceException
483
-     * @throws ReflectionException
484
-     * @throws UnexpectedEntityException
485
-     */
486
-    private function releaseRegistrationSpace()
487
-    {
488
-        $ticket = $this->ticket();
489
-        $ticket->decreaseSold();
490
-        // possibly change event status from sold out back to previous status
491
-        $this->event()->perform_sold_out_status_check();
492
-    }
493
-
494
-
495
-    /**
496
-     * tracks this registration's ticket reservation in extra meta
497
-     * and can increment related ticket reserved and corresponding datetime reserved values
498
-     *
499
-     * @param bool   $update_ticket if true, will increment ticket and datetime reserved count
500
-     * @param string $source
501
-     * @return void
502
-     * @throws EE_Error
503
-     * @throws InvalidArgumentException
504
-     * @throws InvalidDataTypeException
505
-     * @throws InvalidInterfaceException
506
-     * @throws ReflectionException
507
-     */
508
-    public function reserve_ticket($update_ticket = false, $source = 'unknown')
509
-    {
510
-        // only reserve ticket if space is not currently reserved
511
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
512
-            $reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
513
-            if ($reserved && $update_ticket) {
514
-                $ticket = $this->ticket();
515
-                $ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
516
-                // comment out extra meta tracking
517
-                // $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
518
-                $ticket->save();
519
-            }
520
-        }
521
-    }
522
-
523
-
524
-    /**
525
-     * stops tracking this registration's ticket reservation in extra meta
526
-     * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
527
-     *
528
-     * @param bool   $update_ticket if true, will decrement ticket and datetime reserved count
529
-     * @param string $source
530
-     * @return void
531
-     * @throws EE_Error
532
-     * @throws InvalidArgumentException
533
-     * @throws InvalidDataTypeException
534
-     * @throws InvalidInterfaceException
535
-     * @throws ReflectionException
536
-     */
537
-    public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
538
-    {
539
-        // only release ticket if space is currently reserved
540
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
541
-            $released = $this->delete_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
542
-            if ($released && $update_ticket) {
543
-                $ticket = $this->ticket();
544
-                $ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
545
-                // comment out extra meta tracking
546
-                // $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
547
-            }
548
-        }
549
-    }
550
-
551
-
552
-    /**
553
-     * Set Attendee ID
554
-     *
555
-     * @param int $ATT_ID Attendee ID
556
-     * @throws DomainException
557
-     * @throws EE_Error
558
-     * @throws EntityNotFoundException
559
-     * @throws InvalidArgumentException
560
-     * @throws InvalidDataTypeException
561
-     * @throws InvalidInterfaceException
562
-     * @throws ReflectionException
563
-     * @throws RuntimeException
564
-     * @throws UnexpectedEntityException
565
-     */
566
-    public function set_attendee_id($ATT_ID = 0)
567
-    {
568
-        $this->set('ATT_ID', $ATT_ID);
569
-    }
570
-
571
-
572
-    /**
573
-     *        Set Transaction ID
574
-     *
575
-     * @param int $TXN_ID Transaction ID
576
-     * @throws DomainException
577
-     * @throws EE_Error
578
-     * @throws EntityNotFoundException
579
-     * @throws InvalidArgumentException
580
-     * @throws InvalidDataTypeException
581
-     * @throws InvalidInterfaceException
582
-     * @throws ReflectionException
583
-     * @throws RuntimeException
584
-     * @throws UnexpectedEntityException
585
-     */
586
-    public function set_transaction_id($TXN_ID = 0)
587
-    {
588
-        $this->set('TXN_ID', $TXN_ID);
589
-    }
590
-
591
-
592
-    /**
593
-     *        Set Session
594
-     *
595
-     * @param string $REG_session PHP Session ID
596
-     * @throws DomainException
597
-     * @throws EE_Error
598
-     * @throws EntityNotFoundException
599
-     * @throws InvalidArgumentException
600
-     * @throws InvalidDataTypeException
601
-     * @throws InvalidInterfaceException
602
-     * @throws ReflectionException
603
-     * @throws RuntimeException
604
-     * @throws UnexpectedEntityException
605
-     */
606
-    public function set_session($REG_session = '')
607
-    {
608
-        $this->set('REG_session', $REG_session);
609
-    }
610
-
611
-
612
-    /**
613
-     *        Set Registration URL Link
614
-     *
615
-     * @param string $REG_url_link Registration URL Link
616
-     * @throws DomainException
617
-     * @throws EE_Error
618
-     * @throws EntityNotFoundException
619
-     * @throws InvalidArgumentException
620
-     * @throws InvalidDataTypeException
621
-     * @throws InvalidInterfaceException
622
-     * @throws ReflectionException
623
-     * @throws RuntimeException
624
-     * @throws UnexpectedEntityException
625
-     */
626
-    public function set_reg_url_link($REG_url_link = '')
627
-    {
628
-        $this->set('REG_url_link', $REG_url_link);
629
-    }
630
-
631
-
632
-    /**
633
-     *        Set Attendee Counter
634
-     *
635
-     * @param int $REG_count Primary Attendee
636
-     * @throws DomainException
637
-     * @throws EE_Error
638
-     * @throws EntityNotFoundException
639
-     * @throws InvalidArgumentException
640
-     * @throws InvalidDataTypeException
641
-     * @throws InvalidInterfaceException
642
-     * @throws ReflectionException
643
-     * @throws RuntimeException
644
-     * @throws UnexpectedEntityException
645
-     */
646
-    public function set_count($REG_count = 1)
647
-    {
648
-        $this->set('REG_count', $REG_count);
649
-    }
650
-
651
-
652
-    /**
653
-     *        Set Group Size
654
-     *
655
-     * @param boolean $REG_group_size Group Registration
656
-     * @throws DomainException
657
-     * @throws EE_Error
658
-     * @throws EntityNotFoundException
659
-     * @throws InvalidArgumentException
660
-     * @throws InvalidDataTypeException
661
-     * @throws InvalidInterfaceException
662
-     * @throws ReflectionException
663
-     * @throws RuntimeException
664
-     * @throws UnexpectedEntityException
665
-     */
666
-    public function set_group_size($REG_group_size = false)
667
-    {
668
-        $this->set('REG_group_size', $REG_group_size);
669
-    }
670
-
671
-
672
-    /**
673
-     *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
674
-     *    RegStatus::AWAITING_REVIEW
675
-     *
676
-     * @return        boolean
677
-     * @throws EE_Error
678
-     * @throws InvalidArgumentException
679
-     * @throws InvalidDataTypeException
680
-     * @throws InvalidInterfaceException
681
-     * @throws ReflectionException
682
-     */
683
-    public function is_not_approved()
684
-    {
685
-        return $this->status_ID() === RegStatus::AWAITING_REVIEW;
686
-    }
687
-
688
-
689
-    /**
690
-     *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
691
-     *    RegStatus::PENDING_PAYMENT
692
-     *
693
-     * @return        boolean
694
-     * @throws EE_Error
695
-     * @throws InvalidArgumentException
696
-     * @throws InvalidDataTypeException
697
-     * @throws InvalidInterfaceException
698
-     * @throws ReflectionException
699
-     */
700
-    public function is_pending_payment()
701
-    {
702
-        return $this->status_ID() === RegStatus::PENDING_PAYMENT;
703
-    }
704
-
705
-
706
-    /**
707
-     *    is_approved -  convenience method that returns TRUE if REG status ID == RegStatus::APPROVED
708
-     *
709
-     * @return        boolean
710
-     * @throws EE_Error
711
-     * @throws InvalidArgumentException
712
-     * @throws InvalidDataTypeException
713
-     * @throws InvalidInterfaceException
714
-     * @throws ReflectionException
715
-     */
716
-    public function is_approved()
717
-    {
718
-        return $this->status_ID() === RegStatus::APPROVED;
719
-    }
720
-
721
-
722
-    /**
723
-     *    is_cancelled -  convenience method that returns TRUE if REG status ID == RegStatus::CANCELLED
724
-     *
725
-     * @return        boolean
726
-     * @throws EE_Error
727
-     * @throws InvalidArgumentException
728
-     * @throws InvalidDataTypeException
729
-     * @throws InvalidInterfaceException
730
-     * @throws ReflectionException
731
-     */
732
-    public function is_cancelled()
733
-    {
734
-        return $this->status_ID() === RegStatus::CANCELLED;
735
-    }
736
-
737
-
738
-    /**
739
-     *    is_declined -  convenience method that returns TRUE if REG status ID == RegStatus::DECLINED
740
-     *
741
-     * @return        boolean
742
-     * @throws EE_Error
743
-     * @throws InvalidArgumentException
744
-     * @throws InvalidDataTypeException
745
-     * @throws InvalidInterfaceException
746
-     * @throws ReflectionException
747
-     */
748
-    public function is_declined()
749
-    {
750
-        return $this->status_ID() === RegStatus::DECLINED;
751
-    }
752
-
753
-
754
-    /**
755
-     *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
756
-     *    RegStatus::INCOMPLETE
757
-     *
758
-     * @return        boolean
759
-     * @throws EE_Error
760
-     * @throws InvalidArgumentException
761
-     * @throws InvalidDataTypeException
762
-     * @throws InvalidInterfaceException
763
-     * @throws ReflectionException
764
-     */
765
-    public function is_incomplete()
766
-    {
767
-        return $this->status_ID() === RegStatus::INCOMPLETE;
768
-    }
769
-
770
-
771
-    /**
772
-     *        Set Registration Date
773
-     *
774
-     * @param mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
775
-     *                                                 Date
776
-     * @throws DomainException
777
-     * @throws EE_Error
778
-     * @throws EntityNotFoundException
779
-     * @throws InvalidArgumentException
780
-     * @throws InvalidDataTypeException
781
-     * @throws InvalidInterfaceException
782
-     * @throws ReflectionException
783
-     * @throws RuntimeException
784
-     * @throws UnexpectedEntityException
785
-     */
786
-    public function set_reg_date($REG_date = false)
787
-    {
788
-        $this->set('REG_date', $REG_date);
789
-    }
790
-
791
-
792
-    /**
793
-     *    Set final price owing for this registration after all ticket/price modifications
794
-     *
795
-     * @param float $REG_final_price
796
-     * @throws DomainException
797
-     * @throws EE_Error
798
-     * @throws EntityNotFoundException
799
-     * @throws InvalidArgumentException
800
-     * @throws InvalidDataTypeException
801
-     * @throws InvalidInterfaceException
802
-     * @throws ReflectionException
803
-     * @throws RuntimeException
804
-     * @throws UnexpectedEntityException
805
-     */
806
-    public function set_final_price($REG_final_price = 0.00)
807
-    {
808
-        $this->set('REG_final_price', $REG_final_price);
809
-    }
810
-
811
-
812
-    /**
813
-     *    Set amount paid towards this registration's final price
814
-     *
815
-     * @param float|int|string $REG_paid
816
-     * @throws DomainException
817
-     * @throws EE_Error
818
-     * @throws EntityNotFoundException
819
-     * @throws InvalidArgumentException
820
-     * @throws InvalidDataTypeException
821
-     * @throws InvalidInterfaceException
822
-     * @throws ReflectionException
823
-     * @throws RuntimeException
824
-     * @throws UnexpectedEntityException
825
-     */
826
-    public function set_paid($REG_paid = 0.00)
827
-    {
828
-        $this->set('REG_paid', (float) $REG_paid);
829
-    }
830
-
831
-
832
-    /**
833
-     *        Attendee Is Going
834
-     *
835
-     * @param boolean $REG_att_is_going Attendee Is Going
836
-     * @throws DomainException
837
-     * @throws EE_Error
838
-     * @throws EntityNotFoundException
839
-     * @throws InvalidArgumentException
840
-     * @throws InvalidDataTypeException
841
-     * @throws InvalidInterfaceException
842
-     * @throws ReflectionException
843
-     * @throws RuntimeException
844
-     * @throws UnexpectedEntityException
845
-     */
846
-    public function set_att_is_going($REG_att_is_going = false)
847
-    {
848
-        $this->set('REG_att_is_going', $REG_att_is_going);
849
-    }
850
-
851
-
852
-    /**
853
-     * Gets the related attendee
854
-     *
855
-     * @return EE_Attendee|EE_Base_Class
856
-     * @throws EE_Error
857
-     * @throws InvalidArgumentException
858
-     * @throws InvalidDataTypeException
859
-     * @throws InvalidInterfaceException
860
-     * @throws ReflectionException
861
-     */
862
-    public function attendee()
863
-    {
864
-        return EEM_Attendee::instance()->get_one_by_ID($this->attendee_ID());
865
-    }
866
-
867
-
868
-    /**
869
-     * Gets the name of the attendee.
870
-     *
871
-     * @param bool $apply_html_entities set to true if you want to use HTML entities.
872
-     * @return string
873
-     * @throws EE_Error
874
-     * @throws InvalidArgumentException
875
-     * @throws InvalidDataTypeException
876
-     * @throws InvalidInterfaceException
877
-     * @throws ReflectionException
878
-     * @since 4.10.12.p
879
-     */
880
-    public function attendeeName($apply_html_entities = false)
881
-    {
882
-        $attendee = $this->attendee();
883
-        if ($attendee instanceof EE_Attendee) {
884
-            $attendee_name = $attendee->full_name($apply_html_entities);
885
-        } else {
886
-            $attendee_name = esc_html__('Unknown', 'event_espresso');
887
-        }
888
-        return $attendee_name;
889
-    }
890
-
891
-
892
-    /**
893
-     *        get Event ID
894
-     */
895
-    public function event_ID()
896
-    {
897
-        return $this->get('EVT_ID');
898
-    }
899
-
900
-
901
-    /**
902
-     *        get Event ID
903
-     */
904
-    public function event_name()
905
-    {
906
-        $event = $this->event_obj();
907
-        if ($event) {
908
-            return $event->name();
909
-        } else {
910
-            return null;
911
-        }
912
-    }
913
-
914
-
915
-    /**
916
-     * Fetches the event this registration is for
917
-     *
918
-     * @return EE_Base_Class|EE_Event
919
-     * @throws EE_Error
920
-     * @throws InvalidArgumentException
921
-     * @throws InvalidDataTypeException
922
-     * @throws InvalidInterfaceException
923
-     * @throws ReflectionException
924
-     */
925
-    public function event_obj()
926
-    {
927
-        return EEM_Event::instance()->get_one_by_ID($this->event_ID());
928
-    }
929
-
930
-
931
-    /**
932
-     *        get Attendee ID
933
-     */
934
-    public function attendee_ID()
935
-    {
936
-        return $this->get('ATT_ID');
937
-    }
938
-
939
-
940
-    /**
941
-     *        get PHP Session ID
942
-     */
943
-    public function session_ID()
944
-    {
945
-        return $this->get('REG_session');
946
-    }
947
-
948
-
949
-    /**
950
-     * Gets the string which represents the URL trigger for the receipt template in the message template system.
951
-     *
952
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
953
-     * @return string
954
-     * @throws DomainException
955
-     * @throws InvalidArgumentException
956
-     * @throws InvalidDataTypeException
957
-     * @throws InvalidInterfaceException
958
-     */
959
-    public function receipt_url($messenger = 'html')
960
-    {
961
-        return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
962
-    }
963
-
964
-
965
-    /**
966
-     * Gets the string which represents the URL trigger for the invoice template in the message template system.
967
-     *
968
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
969
-     * @return string
970
-     * @throws DomainException
971
-     * @throws InvalidArgumentException
972
-     * @throws InvalidDataTypeException
973
-     * @throws InvalidInterfaceException
974
-     */
975
-    public function invoice_url($messenger = 'html')
976
-    {
977
-        return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
978
-    }
979
-
980
-
981
-    /**
982
-     * get Registration URL Link
983
-     *
984
-     * @return string
985
-     * @throws EE_Error
986
-     * @throws InvalidArgumentException
987
-     * @throws InvalidDataTypeException
988
-     * @throws InvalidInterfaceException
989
-     * @throws ReflectionException
990
-     */
991
-    public function reg_url_link()
992
-    {
993
-        return (string) $this->get('REG_url_link');
994
-    }
995
-
996
-
997
-    /**
998
-     * Echoes out invoice_url()
999
-     *
1000
-     * @param string $type 'download','launch', or 'html' (default is 'launch')
1001
-     * @return void
1002
-     * @throws DomainException
1003
-     * @throws EE_Error
1004
-     * @throws InvalidArgumentException
1005
-     * @throws InvalidDataTypeException
1006
-     * @throws InvalidInterfaceException
1007
-     * @throws ReflectionException
1008
-     */
1009
-    public function e_invoice_url($type = 'launch')
1010
-    {
1011
-        echo esc_url_raw($this->invoice_url($type));
1012
-    }
1013
-
1014
-
1015
-    /**
1016
-     * Echoes out payment_overview_url
1017
-     */
1018
-    public function e_payment_overview_url()
1019
-    {
1020
-        echo esc_url_raw($this->payment_overview_url());
1021
-    }
1022
-
1023
-
1024
-    /**
1025
-     * Gets the URL for the checkout payment options reg step
1026
-     * with this registration's REG_url_link added as a query parameter
1027
-     *
1028
-     * @param bool $clear_session Set to true when you want to clear the session on revisiting the
1029
-     *                            payment overview url.
1030
-     * @return string
1031
-     * @throws EE_Error
1032
-     * @throws InvalidArgumentException
1033
-     * @throws InvalidDataTypeException
1034
-     * @throws InvalidInterfaceException
1035
-     * @throws ReflectionException
1036
-     */
1037
-    public function payment_overview_url($clear_session = false)
1038
-    {
1039
-        return add_query_arg(
1040
-            (array) apply_filters(
1041
-                'FHEE__EE_Registration__payment_overview_url__query_args',
1042
-                [
1043
-                    'e_reg_url_link' => $this->reg_url_link(),
1044
-                    'step'           => 'payment_options',
1045
-                    'revisit'        => true,
1046
-                    'clear_session'  => (bool) $clear_session,
1047
-                ],
1048
-                $this
1049
-            ),
1050
-            EE_Registry::instance()->CFG->core->reg_page_url()
1051
-        );
1052
-    }
1053
-
1054
-
1055
-    /**
1056
-     * Gets the URL for the checkout attendee information reg step
1057
-     * with this registration's REG_url_link added as a query parameter
1058
-     *
1059
-     * @return string
1060
-     * @throws EE_Error
1061
-     * @throws InvalidArgumentException
1062
-     * @throws InvalidDataTypeException
1063
-     * @throws InvalidInterfaceException
1064
-     * @throws ReflectionException
1065
-     */
1066
-    public function edit_attendee_information_url()
1067
-    {
1068
-        return add_query_arg(
1069
-            (array) apply_filters(
1070
-                'FHEE__EE_Registration__edit_attendee_information_url__query_args',
1071
-                [
1072
-                    'e_reg_url_link' => $this->reg_url_link(),
1073
-                    'step'           => 'attendee_information',
1074
-                    'revisit'        => true,
1075
-                ],
1076
-                $this
1077
-            ),
1078
-            EE_Registry::instance()->CFG->core->reg_page_url()
1079
-        );
1080
-    }
1081
-
1082
-
1083
-    /**
1084
-     * Simply generates and returns the appropriate admin_url link to edit this registration
1085
-     *
1086
-     * @return string
1087
-     * @throws EE_Error
1088
-     * @throws InvalidArgumentException
1089
-     * @throws InvalidDataTypeException
1090
-     * @throws InvalidInterfaceException
1091
-     * @throws ReflectionException
1092
-     */
1093
-    public function get_admin_edit_url()
1094
-    {
1095
-        return EEH_URL::add_query_args_and_nonce(
1096
-            [
1097
-                'page'    => 'espresso_registrations',
1098
-                'action'  => 'view_registration',
1099
-                '_REG_ID' => $this->ID(),
1100
-            ],
1101
-            admin_url('admin.php')
1102
-        );
1103
-    }
1104
-
1105
-
1106
-    /**
1107
-     * is_primary_registrant?
1108
-     *
1109
-     * @throws EE_Error
1110
-     * @throws InvalidArgumentException
1111
-     * @throws InvalidDataTypeException
1112
-     * @throws InvalidInterfaceException
1113
-     * @throws ReflectionException
1114
-     */
1115
-    public function is_primary_registrant()
1116
-    {
1117
-        return (int) $this->get('REG_count') === 1;
1118
-    }
1119
-
1120
-
1121
-    /**
1122
-     * This returns the primary registration object for this registration group (which may be this object).
1123
-     *
1124
-     * @return EE_Registration
1125
-     * @throws EE_Error
1126
-     * @throws InvalidArgumentException
1127
-     * @throws InvalidDataTypeException
1128
-     * @throws InvalidInterfaceException
1129
-     * @throws ReflectionException
1130
-     */
1131
-    public function get_primary_registration()
1132
-    {
1133
-        if ($this->is_primary_registrant()) {
1134
-            return $this;
1135
-        }
1136
-
1137
-        // k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1138
-        /** @var EE_Registration $primary_registrant */
1139
-        $primary_registrant = EEM_Registration::instance()->get_one(
1140
-            [
1141
-                [
1142
-                    'TXN_ID'    => $this->transaction_ID(),
1143
-                    'REG_count' => 1,
1144
-                ],
1145
-            ]
1146
-        );
1147
-        return $primary_registrant;
1148
-    }
1149
-
1150
-
1151
-    /**
1152
-     * get  Attendee Number
1153
-     *
1154
-     * @throws EE_Error
1155
-     * @throws InvalidArgumentException
1156
-     * @throws InvalidDataTypeException
1157
-     * @throws InvalidInterfaceException
1158
-     * @throws ReflectionException
1159
-     */
1160
-    public function count()
1161
-    {
1162
-        return $this->get('REG_count');
1163
-    }
1164
-
1165
-
1166
-    /**
1167
-     * get Group Size
1168
-     *
1169
-     * @throws EE_Error
1170
-     * @throws InvalidArgumentException
1171
-     * @throws InvalidDataTypeException
1172
-     * @throws InvalidInterfaceException
1173
-     * @throws ReflectionException
1174
-     */
1175
-    public function group_size()
1176
-    {
1177
-        return $this->get('REG_group_size');
1178
-    }
1179
-
1180
-
1181
-    /**
1182
-     * get Registration Date
1183
-     *
1184
-     * @throws EE_Error
1185
-     * @throws InvalidArgumentException
1186
-     * @throws InvalidDataTypeException
1187
-     * @throws InvalidInterfaceException
1188
-     * @throws ReflectionException
1189
-     */
1190
-    public function date()
1191
-    {
1192
-        return $this->get('REG_date');
1193
-    }
1194
-
1195
-
1196
-    /**
1197
-     * gets a pretty date
1198
-     *
1199
-     * @param string $date_format
1200
-     * @param string $time_format
1201
-     * @return string
1202
-     * @throws EE_Error
1203
-     * @throws InvalidArgumentException
1204
-     * @throws InvalidDataTypeException
1205
-     * @throws InvalidInterfaceException
1206
-     * @throws ReflectionException
1207
-     */
1208
-    public function pretty_date($date_format = null, $time_format = null)
1209
-    {
1210
-        return $this->get_datetime('REG_date', $date_format, $time_format);
1211
-    }
1212
-
1213
-
1214
-    /**
1215
-     * final_price
1216
-     * the registration's share of the transaction total, so that the
1217
-     * sum of all the transaction's REG_final_prices equal the transaction's total
1218
-     *
1219
-     * @return float
1220
-     * @throws EE_Error
1221
-     * @throws InvalidArgumentException
1222
-     * @throws InvalidDataTypeException
1223
-     * @throws InvalidInterfaceException
1224
-     * @throws ReflectionException
1225
-     */
1226
-    public function final_price(): float
1227
-    {
1228
-        return (float) $this->get('REG_final_price');
1229
-    }
1230
-
1231
-
1232
-    /**
1233
-     * pretty_final_price
1234
-     *  final price as formatted string, with correct decimal places and currency symbol
1235
-     *
1236
-     * @param string|null $schema
1237
-     *      Schemas:
1238
-     *      'localized_float': "3,023.00"
1239
-     *      'no_currency_code': "$3,023.00"
1240
-     *      null: "$3,023.00<span>USD</span>"
1241
-     * @return string
1242
-     * @throws EE_Error
1243
-     * @throws InvalidArgumentException
1244
-     * @throws InvalidDataTypeException
1245
-     * @throws InvalidInterfaceException
1246
-     * @throws ReflectionException
1247
-     */
1248
-    public function pretty_final_price(?string $schema = null)
1249
-    {
1250
-        return $this->get_pretty('REG_final_price', $schema);
1251
-    }
1252
-
1253
-
1254
-    /**
1255
-     * get paid (yeah)
1256
-     *
1257
-     * @return float
1258
-     * @throws EE_Error
1259
-     * @throws InvalidArgumentException
1260
-     * @throws InvalidDataTypeException
1261
-     * @throws InvalidInterfaceException
1262
-     * @throws ReflectionException
1263
-     */
1264
-    public function paid(): float
1265
-    {
1266
-        return (float) $this->get('REG_paid');
1267
-    }
1268
-
1269
-
1270
-    /**
1271
-     * pretty_paid
1272
-     *
1273
-     * @param string|null $schema
1274
-     *      Schemas:
1275
-     *      'localized_float': "3,023.00"
1276
-     *      'no_currency_code': "$3,023.00"
1277
-     *      null: "$3,023.00<span>USD</span>"
1278
-     * @return float
1279
-     * @throws EE_Error
1280
-     * @throws InvalidArgumentException
1281
-     * @throws InvalidDataTypeException
1282
-     * @throws InvalidInterfaceException
1283
-     * @throws ReflectionException
1284
-     */
1285
-    public function pretty_paid(?string $schema = null)
1286
-    {
1287
-        return $this->get_pretty('REG_paid', $schema);
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * owes_monies_and_can_pay
1293
-     * whether this registration has monies owing and it's' status allows payment
1294
-     *
1295
-     * @param array $requires_payment list of registration statuses that allow a registrant to make a payment
1296
-     * @return bool
1297
-     * @throws EE_Error
1298
-     * @throws InvalidArgumentException
1299
-     * @throws InvalidDataTypeException
1300
-     * @throws InvalidInterfaceException
1301
-     * @throws ReflectionException
1302
-     */
1303
-    public function owes_monies_and_can_pay(array $requires_payment = []): bool
1304
-    {
1305
-        // these reg statuses require payment (if event is not free)
1306
-        $requires_payment = ! empty($requires_payment)
1307
-            ? $requires_payment
1308
-            : EEM_Registration::reg_statuses_that_allow_payment();
1309
-        if (
1310
-            $this->final_price() !== 0.0 &&
1311
-            $this->final_price() !== $this->paid() &&
1312
-            in_array($this->status_ID(), $requires_payment)
1313
-        ) {
1314
-            return true;
1315
-        }
1316
-        return false;
1317
-    }
1318
-
1319
-
1320
-    /**
1321
-     * Prints out the return value of $this->pretty_status()
1322
-     *
1323
-     * @param bool $show_icons
1324
-     * @return void
1325
-     * @throws EE_Error
1326
-     * @throws InvalidArgumentException
1327
-     * @throws InvalidDataTypeException
1328
-     * @throws InvalidInterfaceException
1329
-     * @throws ReflectionException
1330
-     */
1331
-    public function e_pretty_status($show_icons = false)
1332
-    {
1333
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     * Returns a nice version of the status for displaying to customers
1339
-     *
1340
-     * @param bool $show_icons
1341
-     * @return string
1342
-     * @throws EE_Error
1343
-     * @throws InvalidArgumentException
1344
-     * @throws InvalidDataTypeException
1345
-     * @throws InvalidInterfaceException
1346
-     * @throws ReflectionException
1347
-     */
1348
-    public function pretty_status($show_icons = false)
1349
-    {
1350
-        $status = EEM_Status::instance()->localized_status(
1351
-            [$this->status_ID() => esc_html__('unknown', 'event_espresso')],
1352
-            false,
1353
-            'sentence'
1354
-        );
1355
-        $icon   = '';
1356
-        switch ($this->status_ID()) {
1357
-            case RegStatus::APPROVED:
1358
-                $icon = $show_icons
1359
-                    ? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1360
-                    : '';
1361
-                break;
1362
-            case RegStatus::PENDING_PAYMENT:
1363
-                $icon = $show_icons
1364
-                    ? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1365
-                    : '';
1366
-                break;
1367
-            case RegStatus::AWAITING_REVIEW:
1368
-                $icon = $show_icons
1369
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1370
-                    : '';
1371
-                break;
1372
-            case RegStatus::CANCELLED:
1373
-                $icon = $show_icons
1374
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1375
-                    : '';
1376
-                break;
1377
-            case RegStatus::INCOMPLETE:
1378
-                $icon = $show_icons
1379
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1380
-                    : '';
1381
-                break;
1382
-            case RegStatus::DECLINED:
1383
-                $icon = $show_icons
1384
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1385
-                    : '';
1386
-                break;
1387
-            case RegStatus::WAIT_LIST:
1388
-                $icon = $show_icons
1389
-                    ? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1390
-                    : '';
1391
-                break;
1392
-        }
1393
-        return $icon . $status[ $this->status_ID() ];
1394
-    }
1395
-
1396
-
1397
-    /**
1398
-     *        get Attendee Is Going
1399
-     */
1400
-    public function att_is_going()
1401
-    {
1402
-        return $this->get('REG_att_is_going');
1403
-    }
1404
-
1405
-
1406
-    /**
1407
-     * Gets related answers
1408
-     *
1409
-     * @param array $query_params @see
1410
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1411
-     * @return EE_Answer[]|EE_Base_Class[]
1412
-     * @throws EE_Error
1413
-     * @throws InvalidArgumentException
1414
-     * @throws InvalidDataTypeException
1415
-     * @throws InvalidInterfaceException
1416
-     * @throws ReflectionException
1417
-     */
1418
-    public function answers($query_params = [])
1419
-    {
1420
-        return $this->get_many_related('Answer', $query_params);
1421
-    }
1422
-
1423
-
1424
-    /**
1425
-     * Gets the registration's answer value to the specified question
1426
-     * (either the question's ID or a question object)
1427
-     *
1428
-     * @param EE_Question|int $question
1429
-     * @param bool            $pretty_value
1430
-     * @return array|string if pretty_value= true, the result will always be a string
1431
-     * (because the answer might be an array of answer values, so passing pretty_value=true
1432
-     * will convert it into some kind of string)
1433
-     * @throws EE_Error
1434
-     * @throws InvalidArgumentException
1435
-     * @throws InvalidDataTypeException
1436
-     * @throws InvalidInterfaceException
1437
-     */
1438
-    public function answer_value_to_question($question, $pretty_value = true)
1439
-    {
1440
-        $question_id = EEM_Question::instance()->ensure_is_ID($question);
1441
-        return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1442
-    }
1443
-
1444
-
1445
-    /**
1446
-     * question_groups
1447
-     * returns an array of EE_Question_Group objects for this registration
1448
-     *
1449
-     * @return EE_Question_Group[]
1450
-     * @throws EE_Error
1451
-     * @throws InvalidArgumentException
1452
-     * @throws InvalidDataTypeException
1453
-     * @throws InvalidInterfaceException
1454
-     * @throws ReflectionException
1455
-     */
1456
-    public function question_groups()
1457
-    {
1458
-        return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1459
-    }
1460
-
1461
-
1462
-    /**
1463
-     * count_question_groups
1464
-     * returns a count of the number of EE_Question_Group objects for this registration
1465
-     *
1466
-     * @return int
1467
-     * @throws EE_Error
1468
-     * @throws EntityNotFoundException
1469
-     * @throws InvalidArgumentException
1470
-     * @throws InvalidDataTypeException
1471
-     * @throws InvalidInterfaceException
1472
-     * @throws ReflectionException
1473
-     */
1474
-    public function count_question_groups()
1475
-    {
1476
-        return EEM_Event::instance()->count_related(
1477
-            $this->event_ID(),
1478
-            'Question_Group',
1479
-            [
1480
-                [
1481
-                    'Event_Question_Group.'
1482
-                    . EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1483
-                ],
1484
-            ]
1485
-        );
1486
-    }
1487
-
1488
-
1489
-    /**
1490
-     * Returns the registration date in the 'standard' string format
1491
-     * (function may be improved in the future to allow for different formats and timezones)
1492
-     *
1493
-     * @return string
1494
-     * @throws EE_Error
1495
-     * @throws InvalidArgumentException
1496
-     * @throws InvalidDataTypeException
1497
-     * @throws InvalidInterfaceException
1498
-     * @throws ReflectionException
1499
-     */
1500
-    public function reg_date()
1501
-    {
1502
-        return $this->get_datetime('REG_date');
1503
-    }
1504
-
1505
-
1506
-    /**
1507
-     * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1508
-     * the ticket this registration purchased, or the datetime they have registered
1509
-     * to attend)
1510
-     *
1511
-     * @return EE_Base_Class|EE_Datetime_Ticket
1512
-     * @throws EE_Error
1513
-     * @throws InvalidArgumentException
1514
-     * @throws InvalidDataTypeException
1515
-     * @throws InvalidInterfaceException
1516
-     * @throws ReflectionException
1517
-     */
1518
-    public function datetime_ticket()
1519
-    {
1520
-        return $this->get_first_related('Datetime_Ticket');
1521
-    }
1522
-
1523
-
1524
-    /**
1525
-     * Sets the registration's datetime_ticket.
1526
-     *
1527
-     * @param EE_Datetime_Ticket $datetime_ticket
1528
-     * @return EE_Base_Class|EE_Datetime_Ticket
1529
-     * @throws EE_Error
1530
-     * @throws InvalidArgumentException
1531
-     * @throws InvalidDataTypeException
1532
-     * @throws InvalidInterfaceException
1533
-     * @throws ReflectionException
1534
-     */
1535
-    public function set_datetime_ticket($datetime_ticket)
1536
-    {
1537
-        return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1538
-    }
1539
-
1540
-
1541
-    /**
1542
-     * Gets deleted
1543
-     *
1544
-     * @return bool
1545
-     * @throws EE_Error
1546
-     * @throws InvalidArgumentException
1547
-     * @throws InvalidDataTypeException
1548
-     * @throws InvalidInterfaceException
1549
-     * @throws ReflectionException
1550
-     */
1551
-    public function deleted()
1552
-    {
1553
-        return $this->get('REG_deleted');
1554
-    }
1555
-
1556
-
1557
-    /**
1558
-     * Sets deleted
1559
-     *
1560
-     * @param boolean $deleted
1561
-     * @return void
1562
-     * @throws DomainException
1563
-     * @throws EE_Error
1564
-     * @throws EntityNotFoundException
1565
-     * @throws InvalidArgumentException
1566
-     * @throws InvalidDataTypeException
1567
-     * @throws InvalidInterfaceException
1568
-     * @throws ReflectionException
1569
-     * @throws RuntimeException
1570
-     * @throws UnexpectedEntityException
1571
-     */
1572
-    public function set_deleted($deleted)
1573
-    {
1574
-        if ($deleted) {
1575
-            $this->delete();
1576
-        } else {
1577
-            $this->restore();
1578
-        }
1579
-    }
1580
-
1581
-
1582
-    /**
1583
-     * Get the status object of this object
1584
-     *
1585
-     * @return EE_Base_Class|EE_Status
1586
-     * @throws EE_Error
1587
-     * @throws InvalidArgumentException
1588
-     * @throws InvalidDataTypeException
1589
-     * @throws InvalidInterfaceException
1590
-     * @throws ReflectionException
1591
-     */
1592
-    public function status_obj()
1593
-    {
1594
-        return $this->get_first_related('Status');
1595
-    }
1596
-
1597
-
1598
-    /**
1599
-     * Returns the number of times this registration has checked into any of the datetimes it's available for
1600
-     *
1601
-     * @return int
1602
-     * @throws EE_Error
1603
-     * @throws InvalidArgumentException
1604
-     * @throws InvalidDataTypeException
1605
-     * @throws InvalidInterfaceException
1606
-     * @throws ReflectionException
1607
-     */
1608
-    public function count_checkins()
1609
-    {
1610
-        return $this->get_model()->count_related($this, 'Checkin');
1611
-    }
1612
-
1613
-
1614
-    /**
1615
-     * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1616
-     * registration is for.  Note, this is ONLY checked in (does not include checked out)
1617
-     *
1618
-     * @return int
1619
-     * @throws EE_Error
1620
-     * @throws InvalidArgumentException
1621
-     * @throws InvalidDataTypeException
1622
-     * @throws InvalidInterfaceException
1623
-     * @throws ReflectionException
1624
-     */
1625
-    public function count_checkins_not_checkedout()
1626
-    {
1627
-        return $this->get_model()->count_related($this, 'Checkin', [['CHK_in' => 1]]);
1628
-    }
1629
-
1630
-
1631
-    /**
1632
-     * The purpose of this method is simply to check whether this registration can check in to the given datetime.
1633
-     *
1634
-     * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1635
-     * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1636
-     *                                          consider registration status as well as datetime access.
1637
-     * @return bool
1638
-     * @throws EE_Error
1639
-     * @throws InvalidArgumentException
1640
-     * @throws InvalidDataTypeException
1641
-     * @throws InvalidInterfaceException
1642
-     * @throws ReflectionException
1643
-     */
1644
-    public function can_checkin($DTT_OR_ID, $check_approved = true)
1645
-    {
1646
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1647
-        // first check registration status
1648
-        if (! $DTT_ID || ($check_approved && ! $this->is_approved())) {
1649
-            return false;
1650
-        }
1651
-        // is there a datetime ticket that matches this dtt_ID?
1652
-        if (
1653
-            ! (EEM_Datetime_Ticket::instance()->exists(
1654
-                [
1655
-                    [
1656
-                        'TKT_ID' => $this->get('TKT_ID'),
1657
-                        'DTT_ID' => $DTT_ID,
1658
-                    ],
1659
-                ]
1660
-            ))
1661
-        ) {
1662
-            return false;
1663
-        }
1664
-
1665
-        // final check is against TKT_uses
1666
-        return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1667
-    }
1668
-
1669
-
1670
-    /**
1671
-     * This method verifies whether the user can check in for the given datetime considering the max uses value set on
1672
-     * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1673
-     * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1674
-     * then return false.  Otherwise return true.
1675
-     *
1676
-     * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1677
-     * @return bool true means can check in.  false means cannot check in.
1678
-     * @throws EE_Error
1679
-     * @throws InvalidArgumentException
1680
-     * @throws InvalidDataTypeException
1681
-     * @throws InvalidInterfaceException
1682
-     * @throws ReflectionException
1683
-     */
1684
-    public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1685
-    {
1686
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1687
-
1688
-        if (! $DTT_ID) {
1689
-            return false;
1690
-        }
1691
-
1692
-        $max_uses = $this->ticket() instanceof EE_Ticket
1693
-            ? $this->ticket()->uses()
1694
-            : EE_INF;
1695
-
1696
-        // if max uses is not set or equals infinity then return true
1697
-        // because it's not a factor for whether user can check in or not.
1698
-        if (! $max_uses || $max_uses === EE_INF) {
1699
-            return true;
1700
-        }
1701
-
1702
-        // does this datetime have a check-in record?  If so, then the dtt count has already been verified so we can just
1703
-        // go ahead and toggle.
1704
-        if (EEM_Checkin::instance()->exists([['REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID]])) {
1705
-            return true;
1706
-        }
1707
-
1708
-        // made it here so the last check is whether the number of check-ins per unique datetime on this registration
1709
-        // disallows further check-ins.
1710
-        $count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1711
-            [
1712
-                [
1713
-                    'REG_ID' => $this->ID(),
1714
-                    'CHK_in' => true,
1715
-                ],
1716
-            ],
1717
-            'DTT_ID',
1718
-            true
1719
-        );
1720
-        // check-ins have already reached their max number of uses
1721
-        // so registrant can NOT check in
1722
-        if ($count_unique_dtt_checkins >= $max_uses) {
1723
-            EE_Error::add_error(
1724
-                esc_html__(
1725
-                    'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1726
-                    'event_espresso'
1727
-                ),
1728
-                __FILE__,
1729
-                __FUNCTION__,
1730
-                __LINE__
1731
-            );
1732
-            return false;
1733
-        }
1734
-        return true;
1735
-    }
1736
-
1737
-
1738
-    /**
1739
-     * toggle Check-in status for this registration
1740
-     * Check-ins are toggled in the following order:
1741
-     * never checked in -> checked in
1742
-     * checked in -> checked out
1743
-     * checked out -> checked in
1744
-     *
1745
-     * @param int  $DTT_ID  include specific datetime to toggle Check-in for.
1746
-     *                      If not included or null, then it is assumed latest datetime is being toggled.
1747
-     * @param bool $verify  If true then can_checkin() is used to verify whether the person
1748
-     *                      can be checked in or not.  Otherwise this forces change in check-in status.
1749
-     * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1750
-     * @throws EE_Error
1751
-     * @throws InvalidArgumentException
1752
-     * @throws InvalidDataTypeException
1753
-     * @throws InvalidInterfaceException
1754
-     * @throws ReflectionException
1755
-     */
1756
-    public function toggle_checkin_status($DTT_ID = null, $verify = false)
1757
-    {
1758
-        if (empty($DTT_ID)) {
1759
-            $datetime = $this->get_latest_related_datetime();
1760
-            $DTT_ID   = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1761
-            // verify the registration can check in for the given DTT_ID
1762
-        } elseif (! $this->can_checkin($DTT_ID, $verify)) {
1763
-            EE_Error::add_error(
1764
-                sprintf(
1765
-                    esc_html__(
1766
-                        'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1767
-                        'event_espresso'
1768
-                    ),
1769
-                    $this->ID(),
1770
-                    $DTT_ID
1771
-                ),
1772
-                __FILE__,
1773
-                __FUNCTION__,
1774
-                __LINE__
1775
-            );
1776
-            return false;
1777
-        }
1778
-        $status_paths = [
1779
-            EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1780
-            EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1781
-            EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1782
-        ];
1783
-        // start by getting the current status so we know what status we'll be changing to.
1784
-        $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1785
-        $status_to  = $status_paths[ $cur_status ];
1786
-        // database only records true for checked IN or false for checked OUT
1787
-        // no record ( null ) means checked in NEVER, but we obviously don't save that
1788
-        $new_status = $status_to === EE_Checkin::status_checked_in;
1789
-        // add relation - note Check-ins are always creating new rows
1790
-        // because we are keeping track of Check-ins over time.
1791
-        // Eventually we'll probably want to show a list table
1792
-        // for the individual Check-ins so that they can be managed.
1793
-        $checkin = EE_Checkin::new_instance(
1794
-            [
1795
-                'REG_ID' => $this->ID(),
1796
-                'DTT_ID' => $DTT_ID,
1797
-                'CHK_in' => $new_status,
1798
-            ]
1799
-        );
1800
-        // if the record could not be saved then return false
1801
-        if ($checkin->save() === 0) {
1802
-            if (WP_DEBUG) {
1803
-                global $wpdb;
1804
-                $error = sprintf(
1805
-                    esc_html__(
1806
-                        'Registration check in update failed because of the following database error: %1$s%2$s',
1807
-                        'event_espresso'
1808
-                    ),
1809
-                    '<br />',
1810
-                    $wpdb->last_error
1811
-                );
1812
-            } else {
1813
-                $error = esc_html__(
1814
-                    'Registration check in update failed because of an unknown database error',
1815
-                    'event_espresso'
1816
-                );
1817
-            }
1818
-            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1819
-            return false;
1820
-        }
1821
-        // Fire a checked_in and checkout_out action.
1822
-        $checked_status = $status_to === EE_Checkin::status_checked_in
1823
-            ? 'checked_in'
1824
-            : 'checked_out';
1825
-        do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1826
-        return $status_to;
1827
-    }
1828
-
1829
-
1830
-    /**
1831
-     * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1832
-     * "Latest" is defined by the `DTT_EVT_start` column.
1833
-     *
1834
-     * @return EE_Datetime|null
1835
-     * @throws EE_Error
1836
-     * @throws InvalidArgumentException
1837
-     * @throws InvalidDataTypeException
1838
-     * @throws InvalidInterfaceException
1839
-     * @throws ReflectionException
1840
-     */
1841
-    public function get_latest_related_datetime(): ?EE_Datetime
1842
-    {
1843
-        return EEM_Datetime::instance()->get_one(
1844
-            [
1845
-                [
1846
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1847
-                ],
1848
-                'order_by' => ['DTT_EVT_start' => 'DESC'],
1849
-            ]
1850
-        );
1851
-    }
1852
-
1853
-
1854
-    /**
1855
-     * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1856
-     * "Earliest" is defined by the `DTT_EVT_start` column.
1857
-     *
1858
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1859
-     * @throws EE_Error
1860
-     * @throws InvalidArgumentException
1861
-     * @throws InvalidDataTypeException
1862
-     * @throws InvalidInterfaceException
1863
-     * @throws ReflectionException
1864
-     */
1865
-    public function get_earliest_related_datetime()
1866
-    {
1867
-        return EEM_Datetime::instance()->get_one(
1868
-            [
1869
-                [
1870
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1871
-                ],
1872
-                'order_by' => ['DTT_EVT_start' => 'ASC'],
1873
-            ]
1874
-        );
1875
-    }
1876
-
1877
-
1878
-    /**
1879
-     * This method simply returns the check-in status for this registration and the given datetime.
1880
-     * If neither the datetime nor the check-in values are provided as arguments,
1881
-     * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1882
-     *
1883
-     * @param int|null        $DTT_ID  The ID of the datetime we're checking against
1884
-     *                                 (if empty we'll get the primary datetime for
1885
-     *                                 this registration (via event) and use its ID);
1886
-     * @param EE_Checkin|null $checkin If present, we use the given check-in object rather than the dtt_id.
1887
-     * @return int                     Integer representing Check-in status.
1888
-     * @throws EE_Error
1889
-     * @throws ReflectionException
1890
-     */
1891
-    public function check_in_status_for_datetime(?int $DTT_ID = 0, ?EE_Checkin $checkin = null): int
1892
-    {
1893
-        if ($checkin instanceof EE_Checkin) {
1894
-            return $checkin->status();
1895
-        }
1896
-
1897
-        if (! $DTT_ID) {
1898
-            return EE_Checkin::status_invalid;
1899
-        }
1900
-
1901
-        $checkin_query_params = [
1902
-            0          => ['DTT_ID' => $DTT_ID],
1903
-            'order_by' => ['CHK_timestamp' => 'DESC'],
1904
-        ];
1905
-
1906
-        $checkin = $this->get_first_related(
1907
-            'Checkin',
1908
-            $checkin_query_params
1909
-        );
1910
-        return $checkin instanceof EE_Checkin ? $checkin->status() : EE_Checkin::status_checked_never;
1911
-    }
1912
-
1913
-
1914
-    /**
1915
-     * This method returns a localized message for the toggled Check-in message.
1916
-     *
1917
-     * @param int|null $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1918
-     *                         then it is assumed Check-in for primary datetime was toggled.
1919
-     * @param bool     $error  This just flags that you want an error message returned. This is put in so that the error
1920
-     *                         message can be customized with the attendee name.
1921
-     * @return string internationalized message
1922
-     * @throws EE_Error
1923
-     * @throws ReflectionException
1924
-     */
1925
-    public function get_checkin_msg(?int $DTT_ID, bool $error = false): string
1926
-    {
1927
-        // let's get the attendee first so we can include the name of the attendee
1928
-        $attendee = $this->attendee();
1929
-        if ($attendee instanceof EE_Attendee) {
1930
-            if ($error) {
1931
-                return sprintf(
1932
-                    esc_html__("%s's check-in status was not changed.", "event_espresso"),
1933
-                    $attendee->full_name()
1934
-                );
1935
-            }
1936
-            $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1937
-            // what is the status message going to be?
1938
-            switch ($cur_status) {
1939
-                case EE_Checkin::status_checked_never:
1940
-                    return sprintf(
1941
-                        esc_html__('%s has been removed from Check-in records', 'event_espresso'),
1942
-                        $attendee->full_name()
1943
-                    );
1944
-                case EE_Checkin::status_checked_in:
1945
-                    return sprintf(esc_html__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1946
-                case EE_Checkin::status_checked_out:
1947
-                    return sprintf(esc_html__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1948
-            }
1949
-        }
1950
-        return esc_html__('The check-in status could not be determined.', 'event_espresso');
1951
-    }
1952
-
1953
-
1954
-    /**
1955
-     * Returns the related EE_Transaction to this registration
1956
-     *
1957
-     * @return EE_Transaction
1958
-     * @throws EE_Error
1959
-     * @throws EntityNotFoundException
1960
-     * @throws ReflectionException
1961
-     */
1962
-    public function transaction(): EE_Transaction
1963
-    {
1964
-        $TXN_ID = $this->transaction_ID();
1965
-        $transaction = $TXN_ID
1966
-            ? EEM_Transaction::instance()->get_one_by_ID($TXN_ID)
1967
-            : $this->get_one_from_cache('Transaction');
1968
-        if (! $transaction instanceof \EE_Transaction) {
1969
-            throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1970
-        }
1971
-        return $transaction;
1972
-    }
1973
-
1974
-
1975
-    /**
1976
-     * get Registration Code
1977
-     *
1978
-     * @return string
1979
-     * @throws EE_Error
1980
-     * @throws InvalidArgumentException
1981
-     * @throws InvalidDataTypeException
1982
-     * @throws InvalidInterfaceException
1983
-     * @throws ReflectionException
1984
-     */
1985
-    public function reg_code(): string
1986
-    {
1987
-        return $this->get('REG_code')
1988
-            ?: '';
1989
-    }
1990
-
1991
-
1992
-    /**
1993
-     * @return mixed
1994
-     * @throws EE_Error
1995
-     * @throws InvalidArgumentException
1996
-     * @throws InvalidDataTypeException
1997
-     * @throws InvalidInterfaceException
1998
-     * @throws ReflectionException
1999
-     */
2000
-    public function transaction_ID()
2001
-    {
2002
-        return $this->get('TXN_ID');
2003
-    }
2004
-
2005
-
2006
-    /**
2007
-     * @return int
2008
-     * @throws EE_Error
2009
-     * @throws InvalidArgumentException
2010
-     * @throws InvalidDataTypeException
2011
-     * @throws InvalidInterfaceException
2012
-     * @throws ReflectionException
2013
-     */
2014
-    public function ticket_ID()
2015
-    {
2016
-        return $this->get('TKT_ID');
2017
-    }
2018
-
2019
-
2020
-    /**
2021
-     * Set Registration Code
2022
-     *
2023
-     * @param RegCode|string $REG_code Registration Code
2024
-     * @param boolean        $use_default
2025
-     * @throws EE_Error
2026
-     * @throws InvalidArgumentException
2027
-     * @throws InvalidDataTypeException
2028
-     * @throws InvalidInterfaceException
2029
-     * @throws ReflectionException
2030
-     */
2031
-    public function set_reg_code($REG_code, bool $use_default = false)
2032
-    {
2033
-        if (! $this->reg_code()) {
2034
-            parent::set('REG_code', $REG_code, $use_default);
2035
-        } elseif (empty($REG_code)) {
2036
-            EE_Error::add_error(
2037
-                esc_html__('REG_code can not be empty.', 'event_espresso'),
2038
-                __FILE__,
2039
-                __FUNCTION__,
2040
-                __LINE__
2041
-            );
2042
-        } else {
2043
-            EE_Error::doing_it_wrong(
2044
-                __CLASS__ . '::' . __FUNCTION__,
2045
-                esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
2046
-                '4.6.0'
2047
-            );
2048
-        }
2049
-    }
2050
-
2051
-
2052
-    /**
2053
-     * Returns all other registrations in the same group as this registrant who have the same ticket option.
2054
-     * Note, if you want to just get all registrations in the same transaction (group), use:
2055
-     *    $registration->transaction()->registrations();
2056
-     *
2057
-     * @return EE_Registration[] or empty array if this isn't a group registration.
2058
-     * @throws EE_Error
2059
-     * @throws InvalidArgumentException
2060
-     * @throws InvalidDataTypeException
2061
-     * @throws InvalidInterfaceException
2062
-     * @throws ReflectionException
2063
-     * @since 4.5.0
2064
-     */
2065
-    public function get_all_other_registrations_in_group(bool $with_same_ticket = true): array
2066
-    {
2067
-        if ($this->group_size() < 2) {
2068
-            return [];
2069
-        }
2070
-
2071
-        $query[0] = [
2072
-            'TXN_ID' => $this->transaction_ID(),
2073
-            'REG_ID' => ['!=', $this->ID()],
2074
-        ];
2075
-
2076
-        if ($with_same_ticket) {
2077
-            $query[0]['TKT_ID'] = $this->ticket_ID();
2078
-        }
2079
-        /** @var EE_Registration[] $registrations */
2080
-        $registrations = $this->get_model()->get_all($query);
2081
-        return $registrations;
2082
-    }
2083
-
2084
-
2085
-    /**
2086
-     * Return the link to the admin details for the object.
2087
-     *
2088
-     * @return string
2089
-     * @throws EE_Error
2090
-     * @throws InvalidArgumentException
2091
-     * @throws InvalidDataTypeException
2092
-     * @throws InvalidInterfaceException
2093
-     * @throws ReflectionException
2094
-     */
2095
-    public function get_admin_details_link()
2096
-    {
2097
-        EE_Registry::instance()->load_helper('URL');
2098
-        return EEH_URL::add_query_args_and_nonce(
2099
-            [
2100
-                'page'    => 'espresso_registrations',
2101
-                'action'  => 'view_registration',
2102
-                '_REG_ID' => $this->ID(),
2103
-            ],
2104
-            admin_url('admin.php')
2105
-        );
2106
-    }
2107
-
2108
-
2109
-    /**
2110
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
2111
-     *
2112
-     * @return string
2113
-     * @throws EE_Error
2114
-     * @throws InvalidArgumentException
2115
-     * @throws InvalidDataTypeException
2116
-     * @throws InvalidInterfaceException
2117
-     * @throws ReflectionException
2118
-     */
2119
-    public function get_admin_edit_link()
2120
-    {
2121
-        return $this->get_admin_details_link();
2122
-    }
2123
-
2124
-
2125
-    /**
2126
-     * Returns the link to a settings page for the object.
2127
-     *
2128
-     * @return string
2129
-     * @throws EE_Error
2130
-     * @throws InvalidArgumentException
2131
-     * @throws InvalidDataTypeException
2132
-     * @throws InvalidInterfaceException
2133
-     * @throws ReflectionException
2134
-     */
2135
-    public function get_admin_settings_link()
2136
-    {
2137
-        return $this->get_admin_details_link();
2138
-    }
2139
-
2140
-
2141
-    /**
2142
-     * Returns the link to the "overview" for the object (typically the "list table" view).
2143
-     *
2144
-     * @return string
2145
-     * @throws EE_Error
2146
-     * @throws InvalidArgumentException
2147
-     * @throws InvalidDataTypeException
2148
-     * @throws InvalidInterfaceException
2149
-     * @throws ReflectionException
2150
-     */
2151
-    public function get_admin_overview_link()
2152
-    {
2153
-        EE_Registry::instance()->load_helper('URL');
2154
-        return EEH_URL::add_query_args_and_nonce(
2155
-            [
2156
-                'page' => 'espresso_registrations',
2157
-            ],
2158
-            admin_url('admin.php')
2159
-        );
2160
-    }
2161
-
2162
-
2163
-    /**
2164
-     * @param array $query_params
2165
-     * @return EE_Base_Class[]|EE_Registration[]
2166
-     * @throws EE_Error
2167
-     * @throws InvalidArgumentException
2168
-     * @throws InvalidDataTypeException
2169
-     * @throws InvalidInterfaceException
2170
-     * @throws ReflectionException
2171
-     */
2172
-    public function payments($query_params = [])
2173
-    {
2174
-        return $this->get_many_related('Payment', $query_params);
2175
-    }
2176
-
2177
-
2178
-    /**
2179
-     * @param array $query_params
2180
-     * @return EE_Base_Class[]|EE_Registration_Payment[]
2181
-     * @throws EE_Error
2182
-     * @throws InvalidArgumentException
2183
-     * @throws InvalidDataTypeException
2184
-     * @throws InvalidInterfaceException
2185
-     * @throws ReflectionException
2186
-     */
2187
-    public function registration_payments($query_params = [])
2188
-    {
2189
-        return $this->get_many_related('Registration_Payment', $query_params);
2190
-    }
2191
-
2192
-
2193
-    /**
2194
-     * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
2195
-     * Note: if there are no payments on the registration there will be no payment method returned.
2196
-     *
2197
-     * @return EE_Payment|EE_Payment_Method|null
2198
-     * @throws EE_Error
2199
-     * @throws InvalidArgumentException
2200
-     * @throws InvalidDataTypeException
2201
-     * @throws InvalidInterfaceException
2202
-     */
2203
-    public function payment_method()
2204
-    {
2205
-        return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
2206
-    }
2207
-
2208
-
2209
-    /**
2210
-     * @return \EE_Line_Item
2211
-     * @throws EE_Error
2212
-     * @throws EntityNotFoundException
2213
-     * @throws InvalidArgumentException
2214
-     * @throws InvalidDataTypeException
2215
-     * @throws InvalidInterfaceException
2216
-     * @throws ReflectionException
2217
-     */
2218
-    public function ticket_line_item()
2219
-    {
2220
-        $ticket            = $this->ticket();
2221
-        $transaction       = $this->transaction();
2222
-        $line_item         = null;
2223
-        $ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
2224
-            $transaction->total_line_item(),
2225
-            'Ticket',
2226
-            [$ticket->ID()]
2227
-        );
2228
-        foreach ($ticket_line_items as $ticket_line_item) {
2229
-            if (
2230
-                $ticket_line_item instanceof \EE_Line_Item
2231
-                && $ticket_line_item->OBJ_type() === 'Ticket'
2232
-                && $ticket_line_item->OBJ_ID() === $ticket->ID()
2233
-            ) {
2234
-                $line_item = $ticket_line_item;
2235
-                break;
2236
-            }
2237
-        }
2238
-        if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
2239
-            throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
2240
-        }
2241
-        return $line_item;
2242
-    }
2243
-
2244
-
2245
-    /**
2246
-     * Soft Deletes this model object.
2247
-     *
2248
-     * @return int
2249
-     * @throws DomainException
2250
-     * @throws EE_Error
2251
-     * @throws EntityNotFoundException
2252
-     * @throws InvalidArgumentException
2253
-     * @throws InvalidDataTypeException
2254
-     * @throws InvalidInterfaceException
2255
-     * @throws ReflectionException
2256
-     * @throws RuntimeException
2257
-     * @throws UnexpectedEntityException
2258
-     */
2259
-    public function delete()
2260
-    {
2261
-        if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
2262
-            $this->set_status(
2263
-                RegStatus::CANCELLED,
2264
-                false,
2265
-                new Context(
2266
-                    __METHOD__,
2267
-                    esc_html__('Executed when a registration is trashed.', 'event_espresso')
2268
-                )
2269
-            );
2270
-        }
2271
-        return parent::delete();
2272
-    }
2273
-
2274
-
2275
-    /**
2276
-     * Restores whatever the previous status was on a registration before it was trashed (if possible)
2277
-     *
2278
-     * @return int
2279
-     * @throws DomainException
2280
-     * @throws EE_Error
2281
-     * @throws EntityNotFoundException
2282
-     * @throws InvalidArgumentException
2283
-     * @throws InvalidDataTypeException
2284
-     * @throws InvalidInterfaceException
2285
-     * @throws ReflectionException
2286
-     * @throws RuntimeException
2287
-     * @throws UnexpectedEntityException
2288
-     */
2289
-    public function restore(): int
2290
-    {
2291
-        $previous_status = $this->get_extra_meta(
2292
-            EE_Registration::PRE_TRASH_REG_STATUS_KEY,
2293
-            true,
2294
-            RegStatus::CANCELLED
2295
-        );
2296
-        if ($previous_status) {
2297
-            $this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
2298
-            $this->set_status(
2299
-                $previous_status,
2300
-                false,
2301
-                new Context(
2302
-                    __METHOD__,
2303
-                    esc_html__('Executed when a trashed registration is restored.', 'event_espresso')
2304
-                )
2305
-            );
2306
-        }
2307
-        return parent::restore();
2308
-    }
2309
-
2310
-
2311
-    /**
2312
-     * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
2313
-     *
2314
-     * @param boolean $trigger_set_status_logic  EE_Registration::set_status() can trigger additional logic
2315
-     *                                           depending on whether the reg status changes to or from "Approved"
2316
-     * @return boolean whether the Registration status was updated
2317
-     * @throws DomainException
2318
-     * @throws EE_Error
2319
-     * @throws EntityNotFoundException
2320
-     * @throws InvalidArgumentException
2321
-     * @throws InvalidDataTypeException
2322
-     * @throws InvalidInterfaceException
2323
-     * @throws ReflectionException
2324
-     * @throws RuntimeException
2325
-     * @throws UnexpectedEntityException
2326
-     */
2327
-    public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2328
-    {
2329
-        $paid  = $this->paid();
2330
-        $price = $this->final_price();
2331
-        switch (true) {
2332
-            // overpaid or paid
2333
-            case EEH_Money::compare_floats($paid, $price, '>'):
2334
-            case EEH_Money::compare_floats($paid, $price):
2335
-                $new_status = RegStatus::APPROVED;
2336
-                break;
2337
-            //  underpaid
2338
-            case EEH_Money::compare_floats($paid, $price, '<'):
2339
-                $new_status = RegStatus::PENDING_PAYMENT;
2340
-                break;
2341
-            // uhhh Houston...
2342
-            default:
2343
-                throw new RuntimeException(
2344
-                    esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2345
-                );
2346
-        }
2347
-        if ($new_status !== $this->status_ID()) {
2348
-            if ($trigger_set_status_logic) {
2349
-                return $this->set_status(
2350
-                    $new_status,
2351
-                    false,
2352
-                    new Context(
2353
-                        __METHOD__,
2354
-                        esc_html__(
2355
-                            'Executed when the registration status is updated based on total paid.',
2356
-                            'event_espresso'
2357
-                        )
2358
-                    )
2359
-                );
2360
-            }
2361
-            parent::set('STS_ID', $new_status);
2362
-            return true;
2363
-        }
2364
-        return false;
2365
-    }
2366
-
2367
-
2368
-    /*************************** DEPRECATED ***************************/
2369
-
2370
-
2371
-    /**
2372
-     * @deprecated
2373
-     * @since     4.7.0
2374
-     */
2375
-    public function price_paid()
2376
-    {
2377
-        EE_Error::doing_it_wrong(
2378
-            'EE_Registration::price_paid()',
2379
-            esc_html__(
2380
-                'This method is deprecated, please use EE_Registration::final_price() instead.',
2381
-                'event_espresso'
2382
-            ),
2383
-            '4.7.0'
2384
-        );
2385
-        return $this->final_price();
2386
-    }
2387
-
2388
-
2389
-    /**
2390
-     * @param float $REG_final_price
2391
-     * @throws EE_Error
2392
-     * @throws EntityNotFoundException
2393
-     * @throws InvalidArgumentException
2394
-     * @throws InvalidDataTypeException
2395
-     * @throws InvalidInterfaceException
2396
-     * @throws ReflectionException
2397
-     * @throws RuntimeException
2398
-     * @throws DomainException
2399
-     * @deprecated
2400
-     * @since     4.7.0
2401
-     */
2402
-    public function set_price_paid($REG_final_price = 0.00)
2403
-    {
2404
-        EE_Error::doing_it_wrong(
2405
-            'EE_Registration::set_price_paid()',
2406
-            esc_html__(
2407
-                'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2408
-                'event_espresso'
2409
-            ),
2410
-            '4.7.0'
2411
-        );
2412
-        $this->set_final_price($REG_final_price);
2413
-    }
2414
-
2415
-
2416
-    /**
2417
-     * @return string
2418
-     * @throws EE_Error
2419
-     * @throws InvalidArgumentException
2420
-     * @throws InvalidDataTypeException
2421
-     * @throws InvalidInterfaceException
2422
-     * @throws ReflectionException
2423
-     * @deprecated
2424
-     * @since 4.7.0
2425
-     */
2426
-    public function pretty_price_paid()
2427
-    {
2428
-        EE_Error::doing_it_wrong(
2429
-            'EE_Registration::pretty_price_paid()',
2430
-            esc_html__(
2431
-                'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2432
-                'event_espresso'
2433
-            ),
2434
-            '4.7.0'
2435
-        );
2436
-        return $this->pretty_final_price();
2437
-    }
2438
-
2439
-
2440
-    /**
2441
-     * Gets the primary datetime related to this registration via the related Event to this registration
2442
-     *
2443
-     * @return EE_Datetime
2444
-     * @throws EE_Error
2445
-     * @throws EntityNotFoundException
2446
-     * @throws InvalidArgumentException
2447
-     * @throws InvalidDataTypeException
2448
-     * @throws InvalidInterfaceException
2449
-     * @throws ReflectionException
2450
-     * @deprecated 4.9.17
2451
-     */
2452
-    public function get_related_primary_datetime()
2453
-    {
2454
-        EE_Error::doing_it_wrong(
2455
-            __METHOD__,
2456
-            esc_html__(
2457
-                'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2458
-                'event_espresso'
2459
-            ),
2460
-            '4.9.17',
2461
-            '5.0.0'
2462
-        );
2463
-        return $this->event()->primary_datetime();
2464
-    }
2465
-
2466
-
2467
-    /**
2468
-     * Returns the contact's name (or "Unknown" if there is no contact.)
2469
-     *
2470
-     * @return string
2471
-     * @throws EE_Error
2472
-     * @throws InvalidArgumentException
2473
-     * @throws InvalidDataTypeException
2474
-     * @throws InvalidInterfaceException
2475
-     * @throws ReflectionException
2476
-     * @since 4.10.12.p
2477
-     */
2478
-    public function name()
2479
-    {
2480
-        return $this->attendeeName();
2481
-    }
2482
-
2483
-
2484
-    /**
2485
-     * @return bool
2486
-     * @throws EE_Error
2487
-     * @throws ReflectionException
2488
-     */
2489
-    public function wasMoved(): bool
2490
-    {
2491
-        // only need to check 'registration-moved-to' because
2492
-        // the existence of a new REG ID means the registration was moved
2493
-        $reg_moved = $this->get_extra_meta('registration-moved-to', true, []);
2494
-        return isset($reg_moved['NEW_REG_ID']) && $reg_moved['NEW_REG_ID'];
2495
-    }
2496
-
2497
-
2498
-    /**
2499
-     * @param EE_Payment $payment
2500
-     * @param float|null $amount
2501
-     * @return float
2502
-     * @throws EE_Error
2503
-     * @throws ReflectionException
2504
-     * @since 5.0.8.p
2505
-     */
2506
-    public function applyPayment(EE_Payment $payment, ?float $amount = null): float
2507
-    {
2508
-        $payment_amount = $amount ?? $payment->amount();
2509
-        // ensure $payment_amount is NOT negative
2510
-        $payment_amount = (float) abs($payment_amount);
2511
-        $payment_amount = $payment->is_a_refund()
2512
-            ? $this->processRefund($payment_amount)
2513
-            : $this->processPayment($payment_amount);
2514
-        if ($payment_amount) {
2515
-            $reg_payment = EEM_Registration_Payment::instance()->get_one(
2516
-                [['REG_ID' => $this->ID(), 'PAY_ID' => $payment->ID()]]
2517
-            );
2518
-            // if existing registration payment exists
2519
-            if ($reg_payment instanceof EE_Registration_Payment) {
2520
-                // then update that record
2521
-                $reg_payment->set_amount($payment_amount);
2522
-            } else {
2523
-                // or add new relation between registration and payment and set amount
2524
-                $reg_payment = EE_Registration_Payment::new_instance(
2525
-                    [
2526
-                        'REG_ID'     => $this->ID(),
2527
-                        'PAY_ID'     => $payment->ID(),
2528
-                        'RPY_amount' => $payment_amount,
2529
-                    ]
2530
-                );
2531
-            }
2532
-            $reg_payment->save();
2533
-        }
2534
-        return $payment_amount;
2535
-    }
2536
-
2537
-
2538
-    /**
2539
-     * @throws EE_Error
2540
-     * @throws ReflectionException
2541
-     */
2542
-    private function processPayment(float $payment_amount): float
2543
-    {
2544
-        $paid  = $this->paid();
2545
-        $owing = $this->final_price() - $paid;
2546
-        if ($owing <= 0) {
2547
-            return 0.0;
2548
-        }
2549
-        // don't allow payment amount to exceed the incoming amount, OR the amount owing
2550
-        $payment_amount = min($payment_amount, $owing);
2551
-        $paid           = $paid + $payment_amount;
2552
-        // calculate and set new REG_paid
2553
-        $this->set_paid($paid);
2554
-        // make it stick
2555
-        $this->save();
2556
-        return (float) $payment_amount;
2557
-    }
2558
-
2559
-
2560
-    /**
2561
-     * @throws ReflectionException
2562
-     * @throws EE_Error
2563
-     */
2564
-    private function processRefund(float $payment_amount): float
2565
-    {
2566
-        $paid = $this->paid();
2567
-        if ($paid <= 0) {
2568
-            return 0.0;
2569
-        }
2570
-        // don't allow refund amount to exceed the incoming amount, OR the amount paid
2571
-        $payment_amount = min($payment_amount, $paid);
2572
-        // calculate and set new REG_paid
2573
-        $paid = $paid - $payment_amount;
2574
-        $this->set_paid($paid);
2575
-        // make it stick
2576
-        $this->save();
2577
-        // convert payment amount back to a negative value for storage in the db
2578
-        return (float) $payment_amount * -1;
2579
-    }
2580
-
2581
-
2582
-    /**
2583
-     * @return string
2584
-     * @throws EE_Error
2585
-     * @throws ReflectionException
2586
-     * @since 5.0.20.p
2587
-     */
2588
-    public function defaultRegistrationStatus(): string
2589
-    {
2590
-        $default_event_reg_status = $this->event()->default_registration_status();
2591
-        $default_reg_status = (string) apply_filters(
2592
-            'AFEE__EE_Registration__defaultRegistrationStatus__default_reg_status',
2593
-            $default_event_reg_status,
2594
-            $this
2595
-        );
2596
-        return RegStatus::isValidStatus($default_reg_status, false)
2597
-            ? $default_reg_status
2598
-            : $default_event_reg_status;
2599
-    }
2600
-
2601
-
2602
-    /**
2603
-     * @return string
2604
-     * @throws EE_Error
2605
-     * @throws ReflectionException
2606
-     * @since 5.0.30.p
2607
-     */
2608
-    public function cancelRegistrationConfirmationCode(): string
2609
-    {
2610
-        // concatenate all the fields that make up the source string
2611
-        // ex: 944-1084-720-379-7-2024-10-11 18:21:00-626-379-7-2ff6
2612
-        $source_string = $this->ID() . $this->event_ID() . $this->attendee_ID() . $this->ticket_ID();
2613
-        $source_string .= $this->count() . $this->reg_date() . $this->reg_code();
2614
-        // create a hash of the source string, ex: a9c0d28f79b5602a428e386821015420
2615
-        $source_string = md5($source_string);
2616
-        // return the first 4 characters of the hash in uppercase, ex: A9C0
2617
-        return strtoupper(substr($source_string, 0, 4));
2618
-    }
22
+	/**
23
+	 * extra meta key for tracking reg status os trashed registrations
24
+	 *
25
+	 * @type string
26
+	 */
27
+	public const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
28
+
29
+	/**
30
+	 * extra meta key for tracking if registration has reserved ticket
31
+	 *
32
+	 * @type string
33
+	 */
34
+	public const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
35
+
36
+	/**
37
+	 * extra meta key for tracking registration cancellations
38
+	 *
39
+	 * @type string
40
+	 */
41
+	public const META_KEY_REG_STATUS_CHANGE = 'registration_status_change';
42
+
43
+
44
+	/**
45
+	 * @param array  $props_n_values          incoming values
46
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
47
+	 *                                        used.)
48
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
49
+	 *                                        date_format and the second value is the time format
50
+	 * @return EE_Registration
51
+	 * @throws EE_Error
52
+	 * @throws InvalidArgumentException
53
+	 * @throws InvalidDataTypeException
54
+	 * @throws InvalidInterfaceException
55
+	 * @throws ReflectionException
56
+	 */
57
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
58
+	{
59
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
60
+		return $has_object
61
+			?: new self($props_n_values, false, $timezone, $date_formats);
62
+	}
63
+
64
+
65
+	/**
66
+	 * @param array  $props_n_values  incoming values from the database
67
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
68
+	 *                                the website will be used.
69
+	 * @return EE_Registration
70
+	 * @throws EE_Error
71
+	 * @throws InvalidArgumentException
72
+	 * @throws InvalidDataTypeException
73
+	 * @throws InvalidInterfaceException
74
+	 * @throws ReflectionException
75
+	 */
76
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
77
+	{
78
+		return new self($props_n_values, true, $timezone);
79
+	}
80
+
81
+
82
+	/**
83
+	 *        Set Event ID
84
+	 *
85
+	 * @param int $EVT_ID Event ID
86
+	 * @throws DomainException
87
+	 * @throws EE_Error
88
+	 * @throws EntityNotFoundException
89
+	 * @throws InvalidArgumentException
90
+	 * @throws InvalidDataTypeException
91
+	 * @throws InvalidInterfaceException
92
+	 * @throws ReflectionException
93
+	 * @throws RuntimeException
94
+	 * @throws UnexpectedEntityException
95
+	 */
96
+	public function set_event($EVT_ID = 0)
97
+	{
98
+		$this->set('EVT_ID', $EVT_ID);
99
+	}
100
+
101
+
102
+	/**
103
+	 * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
104
+	 * be routed to internal methods
105
+	 *
106
+	 * @param string $field_name
107
+	 * @param mixed  $field_value
108
+	 * @param bool   $use_default
109
+	 * @throws DomainException
110
+	 * @throws EE_Error
111
+	 * @throws EntityNotFoundException
112
+	 * @throws InvalidArgumentException
113
+	 * @throws InvalidDataTypeException
114
+	 * @throws InvalidInterfaceException
115
+	 * @throws ReflectionException
116
+	 * @throws RuntimeException
117
+	 * @throws UnexpectedEntityException
118
+	 */
119
+	public function set($field_name, $field_value, $use_default = false)
120
+	{
121
+		switch ($field_name) {
122
+			case 'REG_code':
123
+				if (! empty($field_value) && ! $this->reg_code()) {
124
+					$this->set_reg_code($field_value, $use_default);
125
+				}
126
+				break;
127
+			case 'STS_ID':
128
+				$this->set_status((string) $field_value, $use_default);
129
+				break;
130
+			default:
131
+				parent::set($field_name, $field_value, $use_default);
132
+		}
133
+	}
134
+
135
+
136
+	/**
137
+	 * Set Status ID
138
+	 * updates the registration status and ALSO...
139
+	 * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
140
+	 * calls release_registration_space() if the reg status changes FROM approved to any other reg status
141
+	 *
142
+	 * @param string                $new_STS_ID
143
+	 * @param boolean               $use_default
144
+	 * @param ContextInterface|null $context
145
+	 * @return bool
146
+	 * @throws DomainException
147
+	 * @throws EE_Error
148
+	 * @throws EntityNotFoundException
149
+	 * @throws InvalidArgumentException
150
+	 * @throws InvalidDataTypeException
151
+	 * @throws InvalidInterfaceException
152
+	 * @throws ReflectionException
153
+	 * @throws RuntimeException
154
+	 * @throws UnexpectedEntityException
155
+	 */
156
+	public function set_status(
157
+		string $new_STS_ID = '',
158
+		bool $use_default = false,
159
+		?ContextInterface $context = null
160
+	): bool {
161
+		// get current REG_Status
162
+		$old_STS_ID = $this->status_ID();
163
+		$new_STS_ID = (string) apply_filters(
164
+			'AFEE__EE_Registration__set_status__new_STS_ID',
165
+			$new_STS_ID,
166
+			$context,
167
+			$this
168
+		);
169
+		// it's still good to allow the parent set method to have a say
170
+		parent::set('STS_ID', (! empty($new_STS_ID) ? $new_STS_ID : null), $use_default);
171
+		// if status has changed
172
+		if (
173
+			$old_STS_ID !== $new_STS_ID // and that status has actually changed
174
+			&& ! empty($old_STS_ID) // and that old status is actually set
175
+			&& ! empty($new_STS_ID) // as well as the new status
176
+			&& $this->ID() // ensure registration is in the db
177
+		) {
178
+			// THEN handle other changes that occur when reg status changes
179
+			// TO approved
180
+			if ($new_STS_ID === RegStatus::APPROVED) {
181
+				// reserve a space by incrementing ticket and datetime sold values
182
+				$this->reserveRegistrationSpace();
183
+				do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
184
+				// OR FROM  approved
185
+			} elseif ($old_STS_ID === RegStatus::APPROVED) {
186
+				// release a space by decrementing ticket and datetime sold values
187
+				$this->releaseRegistrationSpace();
188
+				do_action(
189
+					'AHEE__EE_Registration__set_status__from_approved',
190
+					$this,
191
+					$old_STS_ID,
192
+					$new_STS_ID,
193
+					$context
194
+				);
195
+			}
196
+			$this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
197
+			if ($this->statusChangeUpdatesTransaction($context)) {
198
+				$this->updateTransactionAfterStatusChange();
199
+			}
200
+			do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
201
+		}
202
+		return ! empty($new_STS_ID);
203
+	}
204
+
205
+
206
+	/**
207
+	 * update REGs and TXN when cancelled or declined registrations involved
208
+	 *
209
+	 * @param string                $new_STS_ID
210
+	 * @param string                $old_STS_ID
211
+	 * @param ContextInterface|null $context
212
+	 * @throws EE_Error
213
+	 * @throws InvalidArgumentException
214
+	 * @throws InvalidDataTypeException
215
+	 * @throws InvalidInterfaceException
216
+	 * @throws ReflectionException
217
+	 * @throws RuntimeException
218
+	 */
219
+	private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ?ContextInterface $context = null)
220
+	{
221
+		// these reg statuses should not be considered in any calculations involving monies owing
222
+		$closed_reg_statuses = EEM_Registration::closed_reg_statuses();
223
+		// true if registration has been cancelled or declined
224
+		$this->updateIfCanceled(
225
+			$closed_reg_statuses,
226
+			$new_STS_ID,
227
+			$old_STS_ID,
228
+			$context
229
+		);
230
+		$this->updateIfReinstated(
231
+			$closed_reg_statuses,
232
+			$new_STS_ID,
233
+			$old_STS_ID,
234
+			$context
235
+		);
236
+	}
237
+
238
+
239
+	/**
240
+	 * update REGs and TXN when cancelled or declined registrations involved
241
+	 *
242
+	 * @param array                 $closed_reg_statuses
243
+	 * @param string                $new_STS_ID
244
+	 * @param string                $old_STS_ID
245
+	 * @param ContextInterface|null $context
246
+	 * @throws EE_Error
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 * @throws ReflectionException
251
+	 * @throws RuntimeException
252
+	 */
253
+	private function updateIfCanceled(
254
+		array $closed_reg_statuses,
255
+		$new_STS_ID,
256
+		$old_STS_ID,
257
+		?ContextInterface $context = null
258
+	) {
259
+		// true if registration has been cancelled or declined
260
+		if (
261
+			in_array($new_STS_ID, $closed_reg_statuses, true)
262
+			&& ! in_array($old_STS_ID, $closed_reg_statuses, true)
263
+		) {
264
+			/** @type EE_Registration_Processor $registration_processor */
265
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
266
+			/** @type EE_Transaction_Processor $transaction_processor */
267
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
268
+			// cancelled or declined registration
269
+			$registration_processor->update_registration_after_being_canceled_or_declined(
270
+				$this,
271
+				$closed_reg_statuses
272
+			);
273
+			$transaction_processor->update_transaction_after_canceled_or_declined_registration(
274
+				$this,
275
+				$closed_reg_statuses,
276
+				false
277
+			);
278
+			do_action(
279
+				'AHEE__EE_Registration__set_status__canceled_or_declined',
280
+				$this,
281
+				$old_STS_ID,
282
+				$new_STS_ID,
283
+				$context
284
+			);
285
+		}
286
+	}
287
+
288
+
289
+	/**
290
+	 * update REGs and TXN when cancelled or declined registrations involved
291
+	 *
292
+	 * @param array                 $closed_reg_statuses
293
+	 * @param string                $new_STS_ID
294
+	 * @param string                $old_STS_ID
295
+	 * @param ContextInterface|null $context
296
+	 * @throws EE_Error
297
+	 * @throws InvalidArgumentException
298
+	 * @throws InvalidDataTypeException
299
+	 * @throws InvalidInterfaceException
300
+	 * @throws ReflectionException
301
+	 * @throws RuntimeException
302
+	 */
303
+	private function updateIfReinstated(
304
+		array $closed_reg_statuses,
305
+		$new_STS_ID,
306
+		$old_STS_ID,
307
+		?ContextInterface $context = null
308
+	) {
309
+		// true if reinstating cancelled or declined registration
310
+		if (
311
+			in_array($old_STS_ID, $closed_reg_statuses, true)
312
+			&& ! in_array($new_STS_ID, $closed_reg_statuses, true)
313
+		) {
314
+			/** @type EE_Registration_Processor $registration_processor */
315
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
316
+			/** @type EE_Transaction_Processor $transaction_processor */
317
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
318
+			// reinstating cancelled or declined registration
319
+			$registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
320
+				$this,
321
+				$closed_reg_statuses
322
+			);
323
+			$transaction_processor->update_transaction_after_reinstating_canceled_registration(
324
+				$this,
325
+				$closed_reg_statuses,
326
+				false
327
+			);
328
+			do_action(
329
+				'AHEE__EE_Registration__set_status__after_reinstated',
330
+				$this,
331
+				$old_STS_ID,
332
+				$new_STS_ID,
333
+				$context
334
+			);
335
+		}
336
+	}
337
+
338
+
339
+	/**
340
+	 * @param ContextInterface|null $context
341
+	 * @return bool
342
+	 */
343
+	private function statusChangeUpdatesTransaction(?ContextInterface $context = null)
344
+	{
345
+		$contexts_that_do_not_update_transaction = (array) apply_filters(
346
+			'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
347
+			['spco_reg_step_attendee_information_process_registrations'],
348
+			$context,
349
+			$this
350
+		);
351
+		return ! (
352
+			$context instanceof ContextInterface
353
+			&& in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
354
+		);
355
+	}
356
+
357
+
358
+	/**
359
+	 * @throws EE_Error
360
+	 * @throws EntityNotFoundException
361
+	 * @throws InvalidArgumentException
362
+	 * @throws InvalidDataTypeException
363
+	 * @throws InvalidInterfaceException
364
+	 * @throws ReflectionException
365
+	 * @throws RuntimeException
366
+	 */
367
+	private function updateTransactionAfterStatusChange()
368
+	{
369
+		/** @type EE_Transaction_Payments $transaction_payments */
370
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
371
+		$transaction_payments->recalculate_transaction_total($this->transaction(), false);
372
+		$this->transaction()->update_status_based_on_total_paid();
373
+	}
374
+
375
+
376
+	/**
377
+	 * get Status ID
378
+	 *
379
+	 * @throws EE_Error
380
+	 * @throws InvalidArgumentException
381
+	 * @throws InvalidDataTypeException
382
+	 * @throws InvalidInterfaceException
383
+	 * @throws ReflectionException
384
+	 */
385
+	public function status_ID()
386
+	{
387
+		return $this->get('STS_ID');
388
+	}
389
+
390
+
391
+	/**
392
+	 * Gets the ticket this registration is for
393
+	 *
394
+	 * @param boolean $include_archived whether to include archived tickets or not.
395
+	 * @return EE_Ticket|EE_Base_Class
396
+	 * @throws EE_Error
397
+	 * @throws InvalidArgumentException
398
+	 * @throws InvalidDataTypeException
399
+	 * @throws InvalidInterfaceException
400
+	 * @throws ReflectionException
401
+	 */
402
+	public function ticket($include_archived = true)
403
+	{
404
+		return EEM_Ticket::instance()->get_one_by_ID($this->ticket_ID());
405
+	}
406
+
407
+
408
+	/**
409
+	 * Gets the event this registration is for
410
+	 *
411
+	 * @return EE_Event
412
+	 * @throws EE_Error
413
+	 * @throws EntityNotFoundException
414
+	 * @throws InvalidArgumentException
415
+	 * @throws InvalidDataTypeException
416
+	 * @throws InvalidInterfaceException
417
+	 * @throws ReflectionException
418
+	 */
419
+	public function event(): EE_Event
420
+	{
421
+		$event = $this->event_obj();
422
+		if (! $event instanceof EE_Event) {
423
+			throw new EntityNotFoundException('Event ID', $this->event_ID());
424
+		}
425
+		return $event;
426
+	}
427
+
428
+
429
+	/**
430
+	 * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
431
+	 * with the author of the event this registration is for.
432
+	 *
433
+	 * @return int
434
+	 * @throws EE_Error
435
+	 * @throws EntityNotFoundException
436
+	 * @throws InvalidArgumentException
437
+	 * @throws InvalidDataTypeException
438
+	 * @throws InvalidInterfaceException
439
+	 * @throws ReflectionException
440
+	 * @since 4.5.0
441
+	 */
442
+	public function wp_user(): int
443
+	{
444
+		return $this->event()->wp_user();
445
+	}
446
+
447
+
448
+	/**
449
+	 * increments this registration's related ticket sold and corresponding datetime sold values
450
+	 *
451
+	 * @return void
452
+	 * @throws DomainException
453
+	 * @throws EE_Error
454
+	 * @throws EntityNotFoundException
455
+	 * @throws InvalidArgumentException
456
+	 * @throws InvalidDataTypeException
457
+	 * @throws InvalidInterfaceException
458
+	 * @throws ReflectionException
459
+	 * @throws UnexpectedEntityException
460
+	 */
461
+	private function reserveRegistrationSpace()
462
+	{
463
+		// reserved ticket and datetime counts will be decremented as sold counts are incremented
464
+		// so stop tracking that this reg has a ticket reserved
465
+		$this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
466
+		$ticket = $this->ticket();
467
+		$ticket->increaseSold();
468
+		// possibly set event status to sold out
469
+		$this->event()->perform_sold_out_status_check();
470
+	}
471
+
472
+
473
+	/**
474
+	 * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
475
+	 *
476
+	 * @return void
477
+	 * @throws DomainException
478
+	 * @throws EE_Error
479
+	 * @throws EntityNotFoundException
480
+	 * @throws InvalidArgumentException
481
+	 * @throws InvalidDataTypeException
482
+	 * @throws InvalidInterfaceException
483
+	 * @throws ReflectionException
484
+	 * @throws UnexpectedEntityException
485
+	 */
486
+	private function releaseRegistrationSpace()
487
+	{
488
+		$ticket = $this->ticket();
489
+		$ticket->decreaseSold();
490
+		// possibly change event status from sold out back to previous status
491
+		$this->event()->perform_sold_out_status_check();
492
+	}
493
+
494
+
495
+	/**
496
+	 * tracks this registration's ticket reservation in extra meta
497
+	 * and can increment related ticket reserved and corresponding datetime reserved values
498
+	 *
499
+	 * @param bool   $update_ticket if true, will increment ticket and datetime reserved count
500
+	 * @param string $source
501
+	 * @return void
502
+	 * @throws EE_Error
503
+	 * @throws InvalidArgumentException
504
+	 * @throws InvalidDataTypeException
505
+	 * @throws InvalidInterfaceException
506
+	 * @throws ReflectionException
507
+	 */
508
+	public function reserve_ticket($update_ticket = false, $source = 'unknown')
509
+	{
510
+		// only reserve ticket if space is not currently reserved
511
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
512
+			$reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
513
+			if ($reserved && $update_ticket) {
514
+				$ticket = $this->ticket();
515
+				$ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
516
+				// comment out extra meta tracking
517
+				// $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
518
+				$ticket->save();
519
+			}
520
+		}
521
+	}
522
+
523
+
524
+	/**
525
+	 * stops tracking this registration's ticket reservation in extra meta
526
+	 * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
527
+	 *
528
+	 * @param bool   $update_ticket if true, will decrement ticket and datetime reserved count
529
+	 * @param string $source
530
+	 * @return void
531
+	 * @throws EE_Error
532
+	 * @throws InvalidArgumentException
533
+	 * @throws InvalidDataTypeException
534
+	 * @throws InvalidInterfaceException
535
+	 * @throws ReflectionException
536
+	 */
537
+	public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
538
+	{
539
+		// only release ticket if space is currently reserved
540
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
541
+			$released = $this->delete_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
542
+			if ($released && $update_ticket) {
543
+				$ticket = $this->ticket();
544
+				$ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
545
+				// comment out extra meta tracking
546
+				// $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
547
+			}
548
+		}
549
+	}
550
+
551
+
552
+	/**
553
+	 * Set Attendee ID
554
+	 *
555
+	 * @param int $ATT_ID Attendee ID
556
+	 * @throws DomainException
557
+	 * @throws EE_Error
558
+	 * @throws EntityNotFoundException
559
+	 * @throws InvalidArgumentException
560
+	 * @throws InvalidDataTypeException
561
+	 * @throws InvalidInterfaceException
562
+	 * @throws ReflectionException
563
+	 * @throws RuntimeException
564
+	 * @throws UnexpectedEntityException
565
+	 */
566
+	public function set_attendee_id($ATT_ID = 0)
567
+	{
568
+		$this->set('ATT_ID', $ATT_ID);
569
+	}
570
+
571
+
572
+	/**
573
+	 *        Set Transaction ID
574
+	 *
575
+	 * @param int $TXN_ID Transaction ID
576
+	 * @throws DomainException
577
+	 * @throws EE_Error
578
+	 * @throws EntityNotFoundException
579
+	 * @throws InvalidArgumentException
580
+	 * @throws InvalidDataTypeException
581
+	 * @throws InvalidInterfaceException
582
+	 * @throws ReflectionException
583
+	 * @throws RuntimeException
584
+	 * @throws UnexpectedEntityException
585
+	 */
586
+	public function set_transaction_id($TXN_ID = 0)
587
+	{
588
+		$this->set('TXN_ID', $TXN_ID);
589
+	}
590
+
591
+
592
+	/**
593
+	 *        Set Session
594
+	 *
595
+	 * @param string $REG_session PHP Session ID
596
+	 * @throws DomainException
597
+	 * @throws EE_Error
598
+	 * @throws EntityNotFoundException
599
+	 * @throws InvalidArgumentException
600
+	 * @throws InvalidDataTypeException
601
+	 * @throws InvalidInterfaceException
602
+	 * @throws ReflectionException
603
+	 * @throws RuntimeException
604
+	 * @throws UnexpectedEntityException
605
+	 */
606
+	public function set_session($REG_session = '')
607
+	{
608
+		$this->set('REG_session', $REG_session);
609
+	}
610
+
611
+
612
+	/**
613
+	 *        Set Registration URL Link
614
+	 *
615
+	 * @param string $REG_url_link Registration URL Link
616
+	 * @throws DomainException
617
+	 * @throws EE_Error
618
+	 * @throws EntityNotFoundException
619
+	 * @throws InvalidArgumentException
620
+	 * @throws InvalidDataTypeException
621
+	 * @throws InvalidInterfaceException
622
+	 * @throws ReflectionException
623
+	 * @throws RuntimeException
624
+	 * @throws UnexpectedEntityException
625
+	 */
626
+	public function set_reg_url_link($REG_url_link = '')
627
+	{
628
+		$this->set('REG_url_link', $REG_url_link);
629
+	}
630
+
631
+
632
+	/**
633
+	 *        Set Attendee Counter
634
+	 *
635
+	 * @param int $REG_count Primary Attendee
636
+	 * @throws DomainException
637
+	 * @throws EE_Error
638
+	 * @throws EntityNotFoundException
639
+	 * @throws InvalidArgumentException
640
+	 * @throws InvalidDataTypeException
641
+	 * @throws InvalidInterfaceException
642
+	 * @throws ReflectionException
643
+	 * @throws RuntimeException
644
+	 * @throws UnexpectedEntityException
645
+	 */
646
+	public function set_count($REG_count = 1)
647
+	{
648
+		$this->set('REG_count', $REG_count);
649
+	}
650
+
651
+
652
+	/**
653
+	 *        Set Group Size
654
+	 *
655
+	 * @param boolean $REG_group_size Group Registration
656
+	 * @throws DomainException
657
+	 * @throws EE_Error
658
+	 * @throws EntityNotFoundException
659
+	 * @throws InvalidArgumentException
660
+	 * @throws InvalidDataTypeException
661
+	 * @throws InvalidInterfaceException
662
+	 * @throws ReflectionException
663
+	 * @throws RuntimeException
664
+	 * @throws UnexpectedEntityException
665
+	 */
666
+	public function set_group_size($REG_group_size = false)
667
+	{
668
+		$this->set('REG_group_size', $REG_group_size);
669
+	}
670
+
671
+
672
+	/**
673
+	 *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
674
+	 *    RegStatus::AWAITING_REVIEW
675
+	 *
676
+	 * @return        boolean
677
+	 * @throws EE_Error
678
+	 * @throws InvalidArgumentException
679
+	 * @throws InvalidDataTypeException
680
+	 * @throws InvalidInterfaceException
681
+	 * @throws ReflectionException
682
+	 */
683
+	public function is_not_approved()
684
+	{
685
+		return $this->status_ID() === RegStatus::AWAITING_REVIEW;
686
+	}
687
+
688
+
689
+	/**
690
+	 *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
691
+	 *    RegStatus::PENDING_PAYMENT
692
+	 *
693
+	 * @return        boolean
694
+	 * @throws EE_Error
695
+	 * @throws InvalidArgumentException
696
+	 * @throws InvalidDataTypeException
697
+	 * @throws InvalidInterfaceException
698
+	 * @throws ReflectionException
699
+	 */
700
+	public function is_pending_payment()
701
+	{
702
+		return $this->status_ID() === RegStatus::PENDING_PAYMENT;
703
+	}
704
+
705
+
706
+	/**
707
+	 *    is_approved -  convenience method that returns TRUE if REG status ID == RegStatus::APPROVED
708
+	 *
709
+	 * @return        boolean
710
+	 * @throws EE_Error
711
+	 * @throws InvalidArgumentException
712
+	 * @throws InvalidDataTypeException
713
+	 * @throws InvalidInterfaceException
714
+	 * @throws ReflectionException
715
+	 */
716
+	public function is_approved()
717
+	{
718
+		return $this->status_ID() === RegStatus::APPROVED;
719
+	}
720
+
721
+
722
+	/**
723
+	 *    is_cancelled -  convenience method that returns TRUE if REG status ID == RegStatus::CANCELLED
724
+	 *
725
+	 * @return        boolean
726
+	 * @throws EE_Error
727
+	 * @throws InvalidArgumentException
728
+	 * @throws InvalidDataTypeException
729
+	 * @throws InvalidInterfaceException
730
+	 * @throws ReflectionException
731
+	 */
732
+	public function is_cancelled()
733
+	{
734
+		return $this->status_ID() === RegStatus::CANCELLED;
735
+	}
736
+
737
+
738
+	/**
739
+	 *    is_declined -  convenience method that returns TRUE if REG status ID == RegStatus::DECLINED
740
+	 *
741
+	 * @return        boolean
742
+	 * @throws EE_Error
743
+	 * @throws InvalidArgumentException
744
+	 * @throws InvalidDataTypeException
745
+	 * @throws InvalidInterfaceException
746
+	 * @throws ReflectionException
747
+	 */
748
+	public function is_declined()
749
+	{
750
+		return $this->status_ID() === RegStatus::DECLINED;
751
+	}
752
+
753
+
754
+	/**
755
+	 *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
756
+	 *    RegStatus::INCOMPLETE
757
+	 *
758
+	 * @return        boolean
759
+	 * @throws EE_Error
760
+	 * @throws InvalidArgumentException
761
+	 * @throws InvalidDataTypeException
762
+	 * @throws InvalidInterfaceException
763
+	 * @throws ReflectionException
764
+	 */
765
+	public function is_incomplete()
766
+	{
767
+		return $this->status_ID() === RegStatus::INCOMPLETE;
768
+	}
769
+
770
+
771
+	/**
772
+	 *        Set Registration Date
773
+	 *
774
+	 * @param mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
775
+	 *                                                 Date
776
+	 * @throws DomainException
777
+	 * @throws EE_Error
778
+	 * @throws EntityNotFoundException
779
+	 * @throws InvalidArgumentException
780
+	 * @throws InvalidDataTypeException
781
+	 * @throws InvalidInterfaceException
782
+	 * @throws ReflectionException
783
+	 * @throws RuntimeException
784
+	 * @throws UnexpectedEntityException
785
+	 */
786
+	public function set_reg_date($REG_date = false)
787
+	{
788
+		$this->set('REG_date', $REG_date);
789
+	}
790
+
791
+
792
+	/**
793
+	 *    Set final price owing for this registration after all ticket/price modifications
794
+	 *
795
+	 * @param float $REG_final_price
796
+	 * @throws DomainException
797
+	 * @throws EE_Error
798
+	 * @throws EntityNotFoundException
799
+	 * @throws InvalidArgumentException
800
+	 * @throws InvalidDataTypeException
801
+	 * @throws InvalidInterfaceException
802
+	 * @throws ReflectionException
803
+	 * @throws RuntimeException
804
+	 * @throws UnexpectedEntityException
805
+	 */
806
+	public function set_final_price($REG_final_price = 0.00)
807
+	{
808
+		$this->set('REG_final_price', $REG_final_price);
809
+	}
810
+
811
+
812
+	/**
813
+	 *    Set amount paid towards this registration's final price
814
+	 *
815
+	 * @param float|int|string $REG_paid
816
+	 * @throws DomainException
817
+	 * @throws EE_Error
818
+	 * @throws EntityNotFoundException
819
+	 * @throws InvalidArgumentException
820
+	 * @throws InvalidDataTypeException
821
+	 * @throws InvalidInterfaceException
822
+	 * @throws ReflectionException
823
+	 * @throws RuntimeException
824
+	 * @throws UnexpectedEntityException
825
+	 */
826
+	public function set_paid($REG_paid = 0.00)
827
+	{
828
+		$this->set('REG_paid', (float) $REG_paid);
829
+	}
830
+
831
+
832
+	/**
833
+	 *        Attendee Is Going
834
+	 *
835
+	 * @param boolean $REG_att_is_going Attendee Is Going
836
+	 * @throws DomainException
837
+	 * @throws EE_Error
838
+	 * @throws EntityNotFoundException
839
+	 * @throws InvalidArgumentException
840
+	 * @throws InvalidDataTypeException
841
+	 * @throws InvalidInterfaceException
842
+	 * @throws ReflectionException
843
+	 * @throws RuntimeException
844
+	 * @throws UnexpectedEntityException
845
+	 */
846
+	public function set_att_is_going($REG_att_is_going = false)
847
+	{
848
+		$this->set('REG_att_is_going', $REG_att_is_going);
849
+	}
850
+
851
+
852
+	/**
853
+	 * Gets the related attendee
854
+	 *
855
+	 * @return EE_Attendee|EE_Base_Class
856
+	 * @throws EE_Error
857
+	 * @throws InvalidArgumentException
858
+	 * @throws InvalidDataTypeException
859
+	 * @throws InvalidInterfaceException
860
+	 * @throws ReflectionException
861
+	 */
862
+	public function attendee()
863
+	{
864
+		return EEM_Attendee::instance()->get_one_by_ID($this->attendee_ID());
865
+	}
866
+
867
+
868
+	/**
869
+	 * Gets the name of the attendee.
870
+	 *
871
+	 * @param bool $apply_html_entities set to true if you want to use HTML entities.
872
+	 * @return string
873
+	 * @throws EE_Error
874
+	 * @throws InvalidArgumentException
875
+	 * @throws InvalidDataTypeException
876
+	 * @throws InvalidInterfaceException
877
+	 * @throws ReflectionException
878
+	 * @since 4.10.12.p
879
+	 */
880
+	public function attendeeName($apply_html_entities = false)
881
+	{
882
+		$attendee = $this->attendee();
883
+		if ($attendee instanceof EE_Attendee) {
884
+			$attendee_name = $attendee->full_name($apply_html_entities);
885
+		} else {
886
+			$attendee_name = esc_html__('Unknown', 'event_espresso');
887
+		}
888
+		return $attendee_name;
889
+	}
890
+
891
+
892
+	/**
893
+	 *        get Event ID
894
+	 */
895
+	public function event_ID()
896
+	{
897
+		return $this->get('EVT_ID');
898
+	}
899
+
900
+
901
+	/**
902
+	 *        get Event ID
903
+	 */
904
+	public function event_name()
905
+	{
906
+		$event = $this->event_obj();
907
+		if ($event) {
908
+			return $event->name();
909
+		} else {
910
+			return null;
911
+		}
912
+	}
913
+
914
+
915
+	/**
916
+	 * Fetches the event this registration is for
917
+	 *
918
+	 * @return EE_Base_Class|EE_Event
919
+	 * @throws EE_Error
920
+	 * @throws InvalidArgumentException
921
+	 * @throws InvalidDataTypeException
922
+	 * @throws InvalidInterfaceException
923
+	 * @throws ReflectionException
924
+	 */
925
+	public function event_obj()
926
+	{
927
+		return EEM_Event::instance()->get_one_by_ID($this->event_ID());
928
+	}
929
+
930
+
931
+	/**
932
+	 *        get Attendee ID
933
+	 */
934
+	public function attendee_ID()
935
+	{
936
+		return $this->get('ATT_ID');
937
+	}
938
+
939
+
940
+	/**
941
+	 *        get PHP Session ID
942
+	 */
943
+	public function session_ID()
944
+	{
945
+		return $this->get('REG_session');
946
+	}
947
+
948
+
949
+	/**
950
+	 * Gets the string which represents the URL trigger for the receipt template in the message template system.
951
+	 *
952
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
953
+	 * @return string
954
+	 * @throws DomainException
955
+	 * @throws InvalidArgumentException
956
+	 * @throws InvalidDataTypeException
957
+	 * @throws InvalidInterfaceException
958
+	 */
959
+	public function receipt_url($messenger = 'html')
960
+	{
961
+		return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
962
+	}
963
+
964
+
965
+	/**
966
+	 * Gets the string which represents the URL trigger for the invoice template in the message template system.
967
+	 *
968
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
969
+	 * @return string
970
+	 * @throws DomainException
971
+	 * @throws InvalidArgumentException
972
+	 * @throws InvalidDataTypeException
973
+	 * @throws InvalidInterfaceException
974
+	 */
975
+	public function invoice_url($messenger = 'html')
976
+	{
977
+		return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
978
+	}
979
+
980
+
981
+	/**
982
+	 * get Registration URL Link
983
+	 *
984
+	 * @return string
985
+	 * @throws EE_Error
986
+	 * @throws InvalidArgumentException
987
+	 * @throws InvalidDataTypeException
988
+	 * @throws InvalidInterfaceException
989
+	 * @throws ReflectionException
990
+	 */
991
+	public function reg_url_link()
992
+	{
993
+		return (string) $this->get('REG_url_link');
994
+	}
995
+
996
+
997
+	/**
998
+	 * Echoes out invoice_url()
999
+	 *
1000
+	 * @param string $type 'download','launch', or 'html' (default is 'launch')
1001
+	 * @return void
1002
+	 * @throws DomainException
1003
+	 * @throws EE_Error
1004
+	 * @throws InvalidArgumentException
1005
+	 * @throws InvalidDataTypeException
1006
+	 * @throws InvalidInterfaceException
1007
+	 * @throws ReflectionException
1008
+	 */
1009
+	public function e_invoice_url($type = 'launch')
1010
+	{
1011
+		echo esc_url_raw($this->invoice_url($type));
1012
+	}
1013
+
1014
+
1015
+	/**
1016
+	 * Echoes out payment_overview_url
1017
+	 */
1018
+	public function e_payment_overview_url()
1019
+	{
1020
+		echo esc_url_raw($this->payment_overview_url());
1021
+	}
1022
+
1023
+
1024
+	/**
1025
+	 * Gets the URL for the checkout payment options reg step
1026
+	 * with this registration's REG_url_link added as a query parameter
1027
+	 *
1028
+	 * @param bool $clear_session Set to true when you want to clear the session on revisiting the
1029
+	 *                            payment overview url.
1030
+	 * @return string
1031
+	 * @throws EE_Error
1032
+	 * @throws InvalidArgumentException
1033
+	 * @throws InvalidDataTypeException
1034
+	 * @throws InvalidInterfaceException
1035
+	 * @throws ReflectionException
1036
+	 */
1037
+	public function payment_overview_url($clear_session = false)
1038
+	{
1039
+		return add_query_arg(
1040
+			(array) apply_filters(
1041
+				'FHEE__EE_Registration__payment_overview_url__query_args',
1042
+				[
1043
+					'e_reg_url_link' => $this->reg_url_link(),
1044
+					'step'           => 'payment_options',
1045
+					'revisit'        => true,
1046
+					'clear_session'  => (bool) $clear_session,
1047
+				],
1048
+				$this
1049
+			),
1050
+			EE_Registry::instance()->CFG->core->reg_page_url()
1051
+		);
1052
+	}
1053
+
1054
+
1055
+	/**
1056
+	 * Gets the URL for the checkout attendee information reg step
1057
+	 * with this registration's REG_url_link added as a query parameter
1058
+	 *
1059
+	 * @return string
1060
+	 * @throws EE_Error
1061
+	 * @throws InvalidArgumentException
1062
+	 * @throws InvalidDataTypeException
1063
+	 * @throws InvalidInterfaceException
1064
+	 * @throws ReflectionException
1065
+	 */
1066
+	public function edit_attendee_information_url()
1067
+	{
1068
+		return add_query_arg(
1069
+			(array) apply_filters(
1070
+				'FHEE__EE_Registration__edit_attendee_information_url__query_args',
1071
+				[
1072
+					'e_reg_url_link' => $this->reg_url_link(),
1073
+					'step'           => 'attendee_information',
1074
+					'revisit'        => true,
1075
+				],
1076
+				$this
1077
+			),
1078
+			EE_Registry::instance()->CFG->core->reg_page_url()
1079
+		);
1080
+	}
1081
+
1082
+
1083
+	/**
1084
+	 * Simply generates and returns the appropriate admin_url link to edit this registration
1085
+	 *
1086
+	 * @return string
1087
+	 * @throws EE_Error
1088
+	 * @throws InvalidArgumentException
1089
+	 * @throws InvalidDataTypeException
1090
+	 * @throws InvalidInterfaceException
1091
+	 * @throws ReflectionException
1092
+	 */
1093
+	public function get_admin_edit_url()
1094
+	{
1095
+		return EEH_URL::add_query_args_and_nonce(
1096
+			[
1097
+				'page'    => 'espresso_registrations',
1098
+				'action'  => 'view_registration',
1099
+				'_REG_ID' => $this->ID(),
1100
+			],
1101
+			admin_url('admin.php')
1102
+		);
1103
+	}
1104
+
1105
+
1106
+	/**
1107
+	 * is_primary_registrant?
1108
+	 *
1109
+	 * @throws EE_Error
1110
+	 * @throws InvalidArgumentException
1111
+	 * @throws InvalidDataTypeException
1112
+	 * @throws InvalidInterfaceException
1113
+	 * @throws ReflectionException
1114
+	 */
1115
+	public function is_primary_registrant()
1116
+	{
1117
+		return (int) $this->get('REG_count') === 1;
1118
+	}
1119
+
1120
+
1121
+	/**
1122
+	 * This returns the primary registration object for this registration group (which may be this object).
1123
+	 *
1124
+	 * @return EE_Registration
1125
+	 * @throws EE_Error
1126
+	 * @throws InvalidArgumentException
1127
+	 * @throws InvalidDataTypeException
1128
+	 * @throws InvalidInterfaceException
1129
+	 * @throws ReflectionException
1130
+	 */
1131
+	public function get_primary_registration()
1132
+	{
1133
+		if ($this->is_primary_registrant()) {
1134
+			return $this;
1135
+		}
1136
+
1137
+		// k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1138
+		/** @var EE_Registration $primary_registrant */
1139
+		$primary_registrant = EEM_Registration::instance()->get_one(
1140
+			[
1141
+				[
1142
+					'TXN_ID'    => $this->transaction_ID(),
1143
+					'REG_count' => 1,
1144
+				],
1145
+			]
1146
+		);
1147
+		return $primary_registrant;
1148
+	}
1149
+
1150
+
1151
+	/**
1152
+	 * get  Attendee Number
1153
+	 *
1154
+	 * @throws EE_Error
1155
+	 * @throws InvalidArgumentException
1156
+	 * @throws InvalidDataTypeException
1157
+	 * @throws InvalidInterfaceException
1158
+	 * @throws ReflectionException
1159
+	 */
1160
+	public function count()
1161
+	{
1162
+		return $this->get('REG_count');
1163
+	}
1164
+
1165
+
1166
+	/**
1167
+	 * get Group Size
1168
+	 *
1169
+	 * @throws EE_Error
1170
+	 * @throws InvalidArgumentException
1171
+	 * @throws InvalidDataTypeException
1172
+	 * @throws InvalidInterfaceException
1173
+	 * @throws ReflectionException
1174
+	 */
1175
+	public function group_size()
1176
+	{
1177
+		return $this->get('REG_group_size');
1178
+	}
1179
+
1180
+
1181
+	/**
1182
+	 * get Registration Date
1183
+	 *
1184
+	 * @throws EE_Error
1185
+	 * @throws InvalidArgumentException
1186
+	 * @throws InvalidDataTypeException
1187
+	 * @throws InvalidInterfaceException
1188
+	 * @throws ReflectionException
1189
+	 */
1190
+	public function date()
1191
+	{
1192
+		return $this->get('REG_date');
1193
+	}
1194
+
1195
+
1196
+	/**
1197
+	 * gets a pretty date
1198
+	 *
1199
+	 * @param string $date_format
1200
+	 * @param string $time_format
1201
+	 * @return string
1202
+	 * @throws EE_Error
1203
+	 * @throws InvalidArgumentException
1204
+	 * @throws InvalidDataTypeException
1205
+	 * @throws InvalidInterfaceException
1206
+	 * @throws ReflectionException
1207
+	 */
1208
+	public function pretty_date($date_format = null, $time_format = null)
1209
+	{
1210
+		return $this->get_datetime('REG_date', $date_format, $time_format);
1211
+	}
1212
+
1213
+
1214
+	/**
1215
+	 * final_price
1216
+	 * the registration's share of the transaction total, so that the
1217
+	 * sum of all the transaction's REG_final_prices equal the transaction's total
1218
+	 *
1219
+	 * @return float
1220
+	 * @throws EE_Error
1221
+	 * @throws InvalidArgumentException
1222
+	 * @throws InvalidDataTypeException
1223
+	 * @throws InvalidInterfaceException
1224
+	 * @throws ReflectionException
1225
+	 */
1226
+	public function final_price(): float
1227
+	{
1228
+		return (float) $this->get('REG_final_price');
1229
+	}
1230
+
1231
+
1232
+	/**
1233
+	 * pretty_final_price
1234
+	 *  final price as formatted string, with correct decimal places and currency symbol
1235
+	 *
1236
+	 * @param string|null $schema
1237
+	 *      Schemas:
1238
+	 *      'localized_float': "3,023.00"
1239
+	 *      'no_currency_code': "$3,023.00"
1240
+	 *      null: "$3,023.00<span>USD</span>"
1241
+	 * @return string
1242
+	 * @throws EE_Error
1243
+	 * @throws InvalidArgumentException
1244
+	 * @throws InvalidDataTypeException
1245
+	 * @throws InvalidInterfaceException
1246
+	 * @throws ReflectionException
1247
+	 */
1248
+	public function pretty_final_price(?string $schema = null)
1249
+	{
1250
+		return $this->get_pretty('REG_final_price', $schema);
1251
+	}
1252
+
1253
+
1254
+	/**
1255
+	 * get paid (yeah)
1256
+	 *
1257
+	 * @return float
1258
+	 * @throws EE_Error
1259
+	 * @throws InvalidArgumentException
1260
+	 * @throws InvalidDataTypeException
1261
+	 * @throws InvalidInterfaceException
1262
+	 * @throws ReflectionException
1263
+	 */
1264
+	public function paid(): float
1265
+	{
1266
+		return (float) $this->get('REG_paid');
1267
+	}
1268
+
1269
+
1270
+	/**
1271
+	 * pretty_paid
1272
+	 *
1273
+	 * @param string|null $schema
1274
+	 *      Schemas:
1275
+	 *      'localized_float': "3,023.00"
1276
+	 *      'no_currency_code': "$3,023.00"
1277
+	 *      null: "$3,023.00<span>USD</span>"
1278
+	 * @return float
1279
+	 * @throws EE_Error
1280
+	 * @throws InvalidArgumentException
1281
+	 * @throws InvalidDataTypeException
1282
+	 * @throws InvalidInterfaceException
1283
+	 * @throws ReflectionException
1284
+	 */
1285
+	public function pretty_paid(?string $schema = null)
1286
+	{
1287
+		return $this->get_pretty('REG_paid', $schema);
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * owes_monies_and_can_pay
1293
+	 * whether this registration has monies owing and it's' status allows payment
1294
+	 *
1295
+	 * @param array $requires_payment list of registration statuses that allow a registrant to make a payment
1296
+	 * @return bool
1297
+	 * @throws EE_Error
1298
+	 * @throws InvalidArgumentException
1299
+	 * @throws InvalidDataTypeException
1300
+	 * @throws InvalidInterfaceException
1301
+	 * @throws ReflectionException
1302
+	 */
1303
+	public function owes_monies_and_can_pay(array $requires_payment = []): bool
1304
+	{
1305
+		// these reg statuses require payment (if event is not free)
1306
+		$requires_payment = ! empty($requires_payment)
1307
+			? $requires_payment
1308
+			: EEM_Registration::reg_statuses_that_allow_payment();
1309
+		if (
1310
+			$this->final_price() !== 0.0 &&
1311
+			$this->final_price() !== $this->paid() &&
1312
+			in_array($this->status_ID(), $requires_payment)
1313
+		) {
1314
+			return true;
1315
+		}
1316
+		return false;
1317
+	}
1318
+
1319
+
1320
+	/**
1321
+	 * Prints out the return value of $this->pretty_status()
1322
+	 *
1323
+	 * @param bool $show_icons
1324
+	 * @return void
1325
+	 * @throws EE_Error
1326
+	 * @throws InvalidArgumentException
1327
+	 * @throws InvalidDataTypeException
1328
+	 * @throws InvalidInterfaceException
1329
+	 * @throws ReflectionException
1330
+	 */
1331
+	public function e_pretty_status($show_icons = false)
1332
+	{
1333
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 * Returns a nice version of the status for displaying to customers
1339
+	 *
1340
+	 * @param bool $show_icons
1341
+	 * @return string
1342
+	 * @throws EE_Error
1343
+	 * @throws InvalidArgumentException
1344
+	 * @throws InvalidDataTypeException
1345
+	 * @throws InvalidInterfaceException
1346
+	 * @throws ReflectionException
1347
+	 */
1348
+	public function pretty_status($show_icons = false)
1349
+	{
1350
+		$status = EEM_Status::instance()->localized_status(
1351
+			[$this->status_ID() => esc_html__('unknown', 'event_espresso')],
1352
+			false,
1353
+			'sentence'
1354
+		);
1355
+		$icon   = '';
1356
+		switch ($this->status_ID()) {
1357
+			case RegStatus::APPROVED:
1358
+				$icon = $show_icons
1359
+					? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1360
+					: '';
1361
+				break;
1362
+			case RegStatus::PENDING_PAYMENT:
1363
+				$icon = $show_icons
1364
+					? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1365
+					: '';
1366
+				break;
1367
+			case RegStatus::AWAITING_REVIEW:
1368
+				$icon = $show_icons
1369
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1370
+					: '';
1371
+				break;
1372
+			case RegStatus::CANCELLED:
1373
+				$icon = $show_icons
1374
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1375
+					: '';
1376
+				break;
1377
+			case RegStatus::INCOMPLETE:
1378
+				$icon = $show_icons
1379
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1380
+					: '';
1381
+				break;
1382
+			case RegStatus::DECLINED:
1383
+				$icon = $show_icons
1384
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1385
+					: '';
1386
+				break;
1387
+			case RegStatus::WAIT_LIST:
1388
+				$icon = $show_icons
1389
+					? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1390
+					: '';
1391
+				break;
1392
+		}
1393
+		return $icon . $status[ $this->status_ID() ];
1394
+	}
1395
+
1396
+
1397
+	/**
1398
+	 *        get Attendee Is Going
1399
+	 */
1400
+	public function att_is_going()
1401
+	{
1402
+		return $this->get('REG_att_is_going');
1403
+	}
1404
+
1405
+
1406
+	/**
1407
+	 * Gets related answers
1408
+	 *
1409
+	 * @param array $query_params @see
1410
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1411
+	 * @return EE_Answer[]|EE_Base_Class[]
1412
+	 * @throws EE_Error
1413
+	 * @throws InvalidArgumentException
1414
+	 * @throws InvalidDataTypeException
1415
+	 * @throws InvalidInterfaceException
1416
+	 * @throws ReflectionException
1417
+	 */
1418
+	public function answers($query_params = [])
1419
+	{
1420
+		return $this->get_many_related('Answer', $query_params);
1421
+	}
1422
+
1423
+
1424
+	/**
1425
+	 * Gets the registration's answer value to the specified question
1426
+	 * (either the question's ID or a question object)
1427
+	 *
1428
+	 * @param EE_Question|int $question
1429
+	 * @param bool            $pretty_value
1430
+	 * @return array|string if pretty_value= true, the result will always be a string
1431
+	 * (because the answer might be an array of answer values, so passing pretty_value=true
1432
+	 * will convert it into some kind of string)
1433
+	 * @throws EE_Error
1434
+	 * @throws InvalidArgumentException
1435
+	 * @throws InvalidDataTypeException
1436
+	 * @throws InvalidInterfaceException
1437
+	 */
1438
+	public function answer_value_to_question($question, $pretty_value = true)
1439
+	{
1440
+		$question_id = EEM_Question::instance()->ensure_is_ID($question);
1441
+		return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1442
+	}
1443
+
1444
+
1445
+	/**
1446
+	 * question_groups
1447
+	 * returns an array of EE_Question_Group objects for this registration
1448
+	 *
1449
+	 * @return EE_Question_Group[]
1450
+	 * @throws EE_Error
1451
+	 * @throws InvalidArgumentException
1452
+	 * @throws InvalidDataTypeException
1453
+	 * @throws InvalidInterfaceException
1454
+	 * @throws ReflectionException
1455
+	 */
1456
+	public function question_groups()
1457
+	{
1458
+		return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1459
+	}
1460
+
1461
+
1462
+	/**
1463
+	 * count_question_groups
1464
+	 * returns a count of the number of EE_Question_Group objects for this registration
1465
+	 *
1466
+	 * @return int
1467
+	 * @throws EE_Error
1468
+	 * @throws EntityNotFoundException
1469
+	 * @throws InvalidArgumentException
1470
+	 * @throws InvalidDataTypeException
1471
+	 * @throws InvalidInterfaceException
1472
+	 * @throws ReflectionException
1473
+	 */
1474
+	public function count_question_groups()
1475
+	{
1476
+		return EEM_Event::instance()->count_related(
1477
+			$this->event_ID(),
1478
+			'Question_Group',
1479
+			[
1480
+				[
1481
+					'Event_Question_Group.'
1482
+					. EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1483
+				],
1484
+			]
1485
+		);
1486
+	}
1487
+
1488
+
1489
+	/**
1490
+	 * Returns the registration date in the 'standard' string format
1491
+	 * (function may be improved in the future to allow for different formats and timezones)
1492
+	 *
1493
+	 * @return string
1494
+	 * @throws EE_Error
1495
+	 * @throws InvalidArgumentException
1496
+	 * @throws InvalidDataTypeException
1497
+	 * @throws InvalidInterfaceException
1498
+	 * @throws ReflectionException
1499
+	 */
1500
+	public function reg_date()
1501
+	{
1502
+		return $this->get_datetime('REG_date');
1503
+	}
1504
+
1505
+
1506
+	/**
1507
+	 * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1508
+	 * the ticket this registration purchased, or the datetime they have registered
1509
+	 * to attend)
1510
+	 *
1511
+	 * @return EE_Base_Class|EE_Datetime_Ticket
1512
+	 * @throws EE_Error
1513
+	 * @throws InvalidArgumentException
1514
+	 * @throws InvalidDataTypeException
1515
+	 * @throws InvalidInterfaceException
1516
+	 * @throws ReflectionException
1517
+	 */
1518
+	public function datetime_ticket()
1519
+	{
1520
+		return $this->get_first_related('Datetime_Ticket');
1521
+	}
1522
+
1523
+
1524
+	/**
1525
+	 * Sets the registration's datetime_ticket.
1526
+	 *
1527
+	 * @param EE_Datetime_Ticket $datetime_ticket
1528
+	 * @return EE_Base_Class|EE_Datetime_Ticket
1529
+	 * @throws EE_Error
1530
+	 * @throws InvalidArgumentException
1531
+	 * @throws InvalidDataTypeException
1532
+	 * @throws InvalidInterfaceException
1533
+	 * @throws ReflectionException
1534
+	 */
1535
+	public function set_datetime_ticket($datetime_ticket)
1536
+	{
1537
+		return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1538
+	}
1539
+
1540
+
1541
+	/**
1542
+	 * Gets deleted
1543
+	 *
1544
+	 * @return bool
1545
+	 * @throws EE_Error
1546
+	 * @throws InvalidArgumentException
1547
+	 * @throws InvalidDataTypeException
1548
+	 * @throws InvalidInterfaceException
1549
+	 * @throws ReflectionException
1550
+	 */
1551
+	public function deleted()
1552
+	{
1553
+		return $this->get('REG_deleted');
1554
+	}
1555
+
1556
+
1557
+	/**
1558
+	 * Sets deleted
1559
+	 *
1560
+	 * @param boolean $deleted
1561
+	 * @return void
1562
+	 * @throws DomainException
1563
+	 * @throws EE_Error
1564
+	 * @throws EntityNotFoundException
1565
+	 * @throws InvalidArgumentException
1566
+	 * @throws InvalidDataTypeException
1567
+	 * @throws InvalidInterfaceException
1568
+	 * @throws ReflectionException
1569
+	 * @throws RuntimeException
1570
+	 * @throws UnexpectedEntityException
1571
+	 */
1572
+	public function set_deleted($deleted)
1573
+	{
1574
+		if ($deleted) {
1575
+			$this->delete();
1576
+		} else {
1577
+			$this->restore();
1578
+		}
1579
+	}
1580
+
1581
+
1582
+	/**
1583
+	 * Get the status object of this object
1584
+	 *
1585
+	 * @return EE_Base_Class|EE_Status
1586
+	 * @throws EE_Error
1587
+	 * @throws InvalidArgumentException
1588
+	 * @throws InvalidDataTypeException
1589
+	 * @throws InvalidInterfaceException
1590
+	 * @throws ReflectionException
1591
+	 */
1592
+	public function status_obj()
1593
+	{
1594
+		return $this->get_first_related('Status');
1595
+	}
1596
+
1597
+
1598
+	/**
1599
+	 * Returns the number of times this registration has checked into any of the datetimes it's available for
1600
+	 *
1601
+	 * @return int
1602
+	 * @throws EE_Error
1603
+	 * @throws InvalidArgumentException
1604
+	 * @throws InvalidDataTypeException
1605
+	 * @throws InvalidInterfaceException
1606
+	 * @throws ReflectionException
1607
+	 */
1608
+	public function count_checkins()
1609
+	{
1610
+		return $this->get_model()->count_related($this, 'Checkin');
1611
+	}
1612
+
1613
+
1614
+	/**
1615
+	 * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1616
+	 * registration is for.  Note, this is ONLY checked in (does not include checked out)
1617
+	 *
1618
+	 * @return int
1619
+	 * @throws EE_Error
1620
+	 * @throws InvalidArgumentException
1621
+	 * @throws InvalidDataTypeException
1622
+	 * @throws InvalidInterfaceException
1623
+	 * @throws ReflectionException
1624
+	 */
1625
+	public function count_checkins_not_checkedout()
1626
+	{
1627
+		return $this->get_model()->count_related($this, 'Checkin', [['CHK_in' => 1]]);
1628
+	}
1629
+
1630
+
1631
+	/**
1632
+	 * The purpose of this method is simply to check whether this registration can check in to the given datetime.
1633
+	 *
1634
+	 * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1635
+	 * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1636
+	 *                                          consider registration status as well as datetime access.
1637
+	 * @return bool
1638
+	 * @throws EE_Error
1639
+	 * @throws InvalidArgumentException
1640
+	 * @throws InvalidDataTypeException
1641
+	 * @throws InvalidInterfaceException
1642
+	 * @throws ReflectionException
1643
+	 */
1644
+	public function can_checkin($DTT_OR_ID, $check_approved = true)
1645
+	{
1646
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1647
+		// first check registration status
1648
+		if (! $DTT_ID || ($check_approved && ! $this->is_approved())) {
1649
+			return false;
1650
+		}
1651
+		// is there a datetime ticket that matches this dtt_ID?
1652
+		if (
1653
+			! (EEM_Datetime_Ticket::instance()->exists(
1654
+				[
1655
+					[
1656
+						'TKT_ID' => $this->get('TKT_ID'),
1657
+						'DTT_ID' => $DTT_ID,
1658
+					],
1659
+				]
1660
+			))
1661
+		) {
1662
+			return false;
1663
+		}
1664
+
1665
+		// final check is against TKT_uses
1666
+		return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1667
+	}
1668
+
1669
+
1670
+	/**
1671
+	 * This method verifies whether the user can check in for the given datetime considering the max uses value set on
1672
+	 * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1673
+	 * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1674
+	 * then return false.  Otherwise return true.
1675
+	 *
1676
+	 * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1677
+	 * @return bool true means can check in.  false means cannot check in.
1678
+	 * @throws EE_Error
1679
+	 * @throws InvalidArgumentException
1680
+	 * @throws InvalidDataTypeException
1681
+	 * @throws InvalidInterfaceException
1682
+	 * @throws ReflectionException
1683
+	 */
1684
+	public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1685
+	{
1686
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1687
+
1688
+		if (! $DTT_ID) {
1689
+			return false;
1690
+		}
1691
+
1692
+		$max_uses = $this->ticket() instanceof EE_Ticket
1693
+			? $this->ticket()->uses()
1694
+			: EE_INF;
1695
+
1696
+		// if max uses is not set or equals infinity then return true
1697
+		// because it's not a factor for whether user can check in or not.
1698
+		if (! $max_uses || $max_uses === EE_INF) {
1699
+			return true;
1700
+		}
1701
+
1702
+		// does this datetime have a check-in record?  If so, then the dtt count has already been verified so we can just
1703
+		// go ahead and toggle.
1704
+		if (EEM_Checkin::instance()->exists([['REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID]])) {
1705
+			return true;
1706
+		}
1707
+
1708
+		// made it here so the last check is whether the number of check-ins per unique datetime on this registration
1709
+		// disallows further check-ins.
1710
+		$count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1711
+			[
1712
+				[
1713
+					'REG_ID' => $this->ID(),
1714
+					'CHK_in' => true,
1715
+				],
1716
+			],
1717
+			'DTT_ID',
1718
+			true
1719
+		);
1720
+		// check-ins have already reached their max number of uses
1721
+		// so registrant can NOT check in
1722
+		if ($count_unique_dtt_checkins >= $max_uses) {
1723
+			EE_Error::add_error(
1724
+				esc_html__(
1725
+					'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1726
+					'event_espresso'
1727
+				),
1728
+				__FILE__,
1729
+				__FUNCTION__,
1730
+				__LINE__
1731
+			);
1732
+			return false;
1733
+		}
1734
+		return true;
1735
+	}
1736
+
1737
+
1738
+	/**
1739
+	 * toggle Check-in status for this registration
1740
+	 * Check-ins are toggled in the following order:
1741
+	 * never checked in -> checked in
1742
+	 * checked in -> checked out
1743
+	 * checked out -> checked in
1744
+	 *
1745
+	 * @param int  $DTT_ID  include specific datetime to toggle Check-in for.
1746
+	 *                      If not included or null, then it is assumed latest datetime is being toggled.
1747
+	 * @param bool $verify  If true then can_checkin() is used to verify whether the person
1748
+	 *                      can be checked in or not.  Otherwise this forces change in check-in status.
1749
+	 * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1750
+	 * @throws EE_Error
1751
+	 * @throws InvalidArgumentException
1752
+	 * @throws InvalidDataTypeException
1753
+	 * @throws InvalidInterfaceException
1754
+	 * @throws ReflectionException
1755
+	 */
1756
+	public function toggle_checkin_status($DTT_ID = null, $verify = false)
1757
+	{
1758
+		if (empty($DTT_ID)) {
1759
+			$datetime = $this->get_latest_related_datetime();
1760
+			$DTT_ID   = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1761
+			// verify the registration can check in for the given DTT_ID
1762
+		} elseif (! $this->can_checkin($DTT_ID, $verify)) {
1763
+			EE_Error::add_error(
1764
+				sprintf(
1765
+					esc_html__(
1766
+						'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1767
+						'event_espresso'
1768
+					),
1769
+					$this->ID(),
1770
+					$DTT_ID
1771
+				),
1772
+				__FILE__,
1773
+				__FUNCTION__,
1774
+				__LINE__
1775
+			);
1776
+			return false;
1777
+		}
1778
+		$status_paths = [
1779
+			EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1780
+			EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1781
+			EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1782
+		];
1783
+		// start by getting the current status so we know what status we'll be changing to.
1784
+		$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1785
+		$status_to  = $status_paths[ $cur_status ];
1786
+		// database only records true for checked IN or false for checked OUT
1787
+		// no record ( null ) means checked in NEVER, but we obviously don't save that
1788
+		$new_status = $status_to === EE_Checkin::status_checked_in;
1789
+		// add relation - note Check-ins are always creating new rows
1790
+		// because we are keeping track of Check-ins over time.
1791
+		// Eventually we'll probably want to show a list table
1792
+		// for the individual Check-ins so that they can be managed.
1793
+		$checkin = EE_Checkin::new_instance(
1794
+			[
1795
+				'REG_ID' => $this->ID(),
1796
+				'DTT_ID' => $DTT_ID,
1797
+				'CHK_in' => $new_status,
1798
+			]
1799
+		);
1800
+		// if the record could not be saved then return false
1801
+		if ($checkin->save() === 0) {
1802
+			if (WP_DEBUG) {
1803
+				global $wpdb;
1804
+				$error = sprintf(
1805
+					esc_html__(
1806
+						'Registration check in update failed because of the following database error: %1$s%2$s',
1807
+						'event_espresso'
1808
+					),
1809
+					'<br />',
1810
+					$wpdb->last_error
1811
+				);
1812
+			} else {
1813
+				$error = esc_html__(
1814
+					'Registration check in update failed because of an unknown database error',
1815
+					'event_espresso'
1816
+				);
1817
+			}
1818
+			EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1819
+			return false;
1820
+		}
1821
+		// Fire a checked_in and checkout_out action.
1822
+		$checked_status = $status_to === EE_Checkin::status_checked_in
1823
+			? 'checked_in'
1824
+			: 'checked_out';
1825
+		do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1826
+		return $status_to;
1827
+	}
1828
+
1829
+
1830
+	/**
1831
+	 * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1832
+	 * "Latest" is defined by the `DTT_EVT_start` column.
1833
+	 *
1834
+	 * @return EE_Datetime|null
1835
+	 * @throws EE_Error
1836
+	 * @throws InvalidArgumentException
1837
+	 * @throws InvalidDataTypeException
1838
+	 * @throws InvalidInterfaceException
1839
+	 * @throws ReflectionException
1840
+	 */
1841
+	public function get_latest_related_datetime(): ?EE_Datetime
1842
+	{
1843
+		return EEM_Datetime::instance()->get_one(
1844
+			[
1845
+				[
1846
+					'Ticket.Registration.REG_ID' => $this->ID(),
1847
+				],
1848
+				'order_by' => ['DTT_EVT_start' => 'DESC'],
1849
+			]
1850
+		);
1851
+	}
1852
+
1853
+
1854
+	/**
1855
+	 * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1856
+	 * "Earliest" is defined by the `DTT_EVT_start` column.
1857
+	 *
1858
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1859
+	 * @throws EE_Error
1860
+	 * @throws InvalidArgumentException
1861
+	 * @throws InvalidDataTypeException
1862
+	 * @throws InvalidInterfaceException
1863
+	 * @throws ReflectionException
1864
+	 */
1865
+	public function get_earliest_related_datetime()
1866
+	{
1867
+		return EEM_Datetime::instance()->get_one(
1868
+			[
1869
+				[
1870
+					'Ticket.Registration.REG_ID' => $this->ID(),
1871
+				],
1872
+				'order_by' => ['DTT_EVT_start' => 'ASC'],
1873
+			]
1874
+		);
1875
+	}
1876
+
1877
+
1878
+	/**
1879
+	 * This method simply returns the check-in status for this registration and the given datetime.
1880
+	 * If neither the datetime nor the check-in values are provided as arguments,
1881
+	 * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1882
+	 *
1883
+	 * @param int|null        $DTT_ID  The ID of the datetime we're checking against
1884
+	 *                                 (if empty we'll get the primary datetime for
1885
+	 *                                 this registration (via event) and use its ID);
1886
+	 * @param EE_Checkin|null $checkin If present, we use the given check-in object rather than the dtt_id.
1887
+	 * @return int                     Integer representing Check-in status.
1888
+	 * @throws EE_Error
1889
+	 * @throws ReflectionException
1890
+	 */
1891
+	public function check_in_status_for_datetime(?int $DTT_ID = 0, ?EE_Checkin $checkin = null): int
1892
+	{
1893
+		if ($checkin instanceof EE_Checkin) {
1894
+			return $checkin->status();
1895
+		}
1896
+
1897
+		if (! $DTT_ID) {
1898
+			return EE_Checkin::status_invalid;
1899
+		}
1900
+
1901
+		$checkin_query_params = [
1902
+			0          => ['DTT_ID' => $DTT_ID],
1903
+			'order_by' => ['CHK_timestamp' => 'DESC'],
1904
+		];
1905
+
1906
+		$checkin = $this->get_first_related(
1907
+			'Checkin',
1908
+			$checkin_query_params
1909
+		);
1910
+		return $checkin instanceof EE_Checkin ? $checkin->status() : EE_Checkin::status_checked_never;
1911
+	}
1912
+
1913
+
1914
+	/**
1915
+	 * This method returns a localized message for the toggled Check-in message.
1916
+	 *
1917
+	 * @param int|null $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1918
+	 *                         then it is assumed Check-in for primary datetime was toggled.
1919
+	 * @param bool     $error  This just flags that you want an error message returned. This is put in so that the error
1920
+	 *                         message can be customized with the attendee name.
1921
+	 * @return string internationalized message
1922
+	 * @throws EE_Error
1923
+	 * @throws ReflectionException
1924
+	 */
1925
+	public function get_checkin_msg(?int $DTT_ID, bool $error = false): string
1926
+	{
1927
+		// let's get the attendee first so we can include the name of the attendee
1928
+		$attendee = $this->attendee();
1929
+		if ($attendee instanceof EE_Attendee) {
1930
+			if ($error) {
1931
+				return sprintf(
1932
+					esc_html__("%s's check-in status was not changed.", "event_espresso"),
1933
+					$attendee->full_name()
1934
+				);
1935
+			}
1936
+			$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1937
+			// what is the status message going to be?
1938
+			switch ($cur_status) {
1939
+				case EE_Checkin::status_checked_never:
1940
+					return sprintf(
1941
+						esc_html__('%s has been removed from Check-in records', 'event_espresso'),
1942
+						$attendee->full_name()
1943
+					);
1944
+				case EE_Checkin::status_checked_in:
1945
+					return sprintf(esc_html__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1946
+				case EE_Checkin::status_checked_out:
1947
+					return sprintf(esc_html__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1948
+			}
1949
+		}
1950
+		return esc_html__('The check-in status could not be determined.', 'event_espresso');
1951
+	}
1952
+
1953
+
1954
+	/**
1955
+	 * Returns the related EE_Transaction to this registration
1956
+	 *
1957
+	 * @return EE_Transaction
1958
+	 * @throws EE_Error
1959
+	 * @throws EntityNotFoundException
1960
+	 * @throws ReflectionException
1961
+	 */
1962
+	public function transaction(): EE_Transaction
1963
+	{
1964
+		$TXN_ID = $this->transaction_ID();
1965
+		$transaction = $TXN_ID
1966
+			? EEM_Transaction::instance()->get_one_by_ID($TXN_ID)
1967
+			: $this->get_one_from_cache('Transaction');
1968
+		if (! $transaction instanceof \EE_Transaction) {
1969
+			throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1970
+		}
1971
+		return $transaction;
1972
+	}
1973
+
1974
+
1975
+	/**
1976
+	 * get Registration Code
1977
+	 *
1978
+	 * @return string
1979
+	 * @throws EE_Error
1980
+	 * @throws InvalidArgumentException
1981
+	 * @throws InvalidDataTypeException
1982
+	 * @throws InvalidInterfaceException
1983
+	 * @throws ReflectionException
1984
+	 */
1985
+	public function reg_code(): string
1986
+	{
1987
+		return $this->get('REG_code')
1988
+			?: '';
1989
+	}
1990
+
1991
+
1992
+	/**
1993
+	 * @return mixed
1994
+	 * @throws EE_Error
1995
+	 * @throws InvalidArgumentException
1996
+	 * @throws InvalidDataTypeException
1997
+	 * @throws InvalidInterfaceException
1998
+	 * @throws ReflectionException
1999
+	 */
2000
+	public function transaction_ID()
2001
+	{
2002
+		return $this->get('TXN_ID');
2003
+	}
2004
+
2005
+
2006
+	/**
2007
+	 * @return int
2008
+	 * @throws EE_Error
2009
+	 * @throws InvalidArgumentException
2010
+	 * @throws InvalidDataTypeException
2011
+	 * @throws InvalidInterfaceException
2012
+	 * @throws ReflectionException
2013
+	 */
2014
+	public function ticket_ID()
2015
+	{
2016
+		return $this->get('TKT_ID');
2017
+	}
2018
+
2019
+
2020
+	/**
2021
+	 * Set Registration Code
2022
+	 *
2023
+	 * @param RegCode|string $REG_code Registration Code
2024
+	 * @param boolean        $use_default
2025
+	 * @throws EE_Error
2026
+	 * @throws InvalidArgumentException
2027
+	 * @throws InvalidDataTypeException
2028
+	 * @throws InvalidInterfaceException
2029
+	 * @throws ReflectionException
2030
+	 */
2031
+	public function set_reg_code($REG_code, bool $use_default = false)
2032
+	{
2033
+		if (! $this->reg_code()) {
2034
+			parent::set('REG_code', $REG_code, $use_default);
2035
+		} elseif (empty($REG_code)) {
2036
+			EE_Error::add_error(
2037
+				esc_html__('REG_code can not be empty.', 'event_espresso'),
2038
+				__FILE__,
2039
+				__FUNCTION__,
2040
+				__LINE__
2041
+			);
2042
+		} else {
2043
+			EE_Error::doing_it_wrong(
2044
+				__CLASS__ . '::' . __FUNCTION__,
2045
+				esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
2046
+				'4.6.0'
2047
+			);
2048
+		}
2049
+	}
2050
+
2051
+
2052
+	/**
2053
+	 * Returns all other registrations in the same group as this registrant who have the same ticket option.
2054
+	 * Note, if you want to just get all registrations in the same transaction (group), use:
2055
+	 *    $registration->transaction()->registrations();
2056
+	 *
2057
+	 * @return EE_Registration[] or empty array if this isn't a group registration.
2058
+	 * @throws EE_Error
2059
+	 * @throws InvalidArgumentException
2060
+	 * @throws InvalidDataTypeException
2061
+	 * @throws InvalidInterfaceException
2062
+	 * @throws ReflectionException
2063
+	 * @since 4.5.0
2064
+	 */
2065
+	public function get_all_other_registrations_in_group(bool $with_same_ticket = true): array
2066
+	{
2067
+		if ($this->group_size() < 2) {
2068
+			return [];
2069
+		}
2070
+
2071
+		$query[0] = [
2072
+			'TXN_ID' => $this->transaction_ID(),
2073
+			'REG_ID' => ['!=', $this->ID()],
2074
+		];
2075
+
2076
+		if ($with_same_ticket) {
2077
+			$query[0]['TKT_ID'] = $this->ticket_ID();
2078
+		}
2079
+		/** @var EE_Registration[] $registrations */
2080
+		$registrations = $this->get_model()->get_all($query);
2081
+		return $registrations;
2082
+	}
2083
+
2084
+
2085
+	/**
2086
+	 * Return the link to the admin details for the object.
2087
+	 *
2088
+	 * @return string
2089
+	 * @throws EE_Error
2090
+	 * @throws InvalidArgumentException
2091
+	 * @throws InvalidDataTypeException
2092
+	 * @throws InvalidInterfaceException
2093
+	 * @throws ReflectionException
2094
+	 */
2095
+	public function get_admin_details_link()
2096
+	{
2097
+		EE_Registry::instance()->load_helper('URL');
2098
+		return EEH_URL::add_query_args_and_nonce(
2099
+			[
2100
+				'page'    => 'espresso_registrations',
2101
+				'action'  => 'view_registration',
2102
+				'_REG_ID' => $this->ID(),
2103
+			],
2104
+			admin_url('admin.php')
2105
+		);
2106
+	}
2107
+
2108
+
2109
+	/**
2110
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
2111
+	 *
2112
+	 * @return string
2113
+	 * @throws EE_Error
2114
+	 * @throws InvalidArgumentException
2115
+	 * @throws InvalidDataTypeException
2116
+	 * @throws InvalidInterfaceException
2117
+	 * @throws ReflectionException
2118
+	 */
2119
+	public function get_admin_edit_link()
2120
+	{
2121
+		return $this->get_admin_details_link();
2122
+	}
2123
+
2124
+
2125
+	/**
2126
+	 * Returns the link to a settings page for the object.
2127
+	 *
2128
+	 * @return string
2129
+	 * @throws EE_Error
2130
+	 * @throws InvalidArgumentException
2131
+	 * @throws InvalidDataTypeException
2132
+	 * @throws InvalidInterfaceException
2133
+	 * @throws ReflectionException
2134
+	 */
2135
+	public function get_admin_settings_link()
2136
+	{
2137
+		return $this->get_admin_details_link();
2138
+	}
2139
+
2140
+
2141
+	/**
2142
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
2143
+	 *
2144
+	 * @return string
2145
+	 * @throws EE_Error
2146
+	 * @throws InvalidArgumentException
2147
+	 * @throws InvalidDataTypeException
2148
+	 * @throws InvalidInterfaceException
2149
+	 * @throws ReflectionException
2150
+	 */
2151
+	public function get_admin_overview_link()
2152
+	{
2153
+		EE_Registry::instance()->load_helper('URL');
2154
+		return EEH_URL::add_query_args_and_nonce(
2155
+			[
2156
+				'page' => 'espresso_registrations',
2157
+			],
2158
+			admin_url('admin.php')
2159
+		);
2160
+	}
2161
+
2162
+
2163
+	/**
2164
+	 * @param array $query_params
2165
+	 * @return EE_Base_Class[]|EE_Registration[]
2166
+	 * @throws EE_Error
2167
+	 * @throws InvalidArgumentException
2168
+	 * @throws InvalidDataTypeException
2169
+	 * @throws InvalidInterfaceException
2170
+	 * @throws ReflectionException
2171
+	 */
2172
+	public function payments($query_params = [])
2173
+	{
2174
+		return $this->get_many_related('Payment', $query_params);
2175
+	}
2176
+
2177
+
2178
+	/**
2179
+	 * @param array $query_params
2180
+	 * @return EE_Base_Class[]|EE_Registration_Payment[]
2181
+	 * @throws EE_Error
2182
+	 * @throws InvalidArgumentException
2183
+	 * @throws InvalidDataTypeException
2184
+	 * @throws InvalidInterfaceException
2185
+	 * @throws ReflectionException
2186
+	 */
2187
+	public function registration_payments($query_params = [])
2188
+	{
2189
+		return $this->get_many_related('Registration_Payment', $query_params);
2190
+	}
2191
+
2192
+
2193
+	/**
2194
+	 * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
2195
+	 * Note: if there are no payments on the registration there will be no payment method returned.
2196
+	 *
2197
+	 * @return EE_Payment|EE_Payment_Method|null
2198
+	 * @throws EE_Error
2199
+	 * @throws InvalidArgumentException
2200
+	 * @throws InvalidDataTypeException
2201
+	 * @throws InvalidInterfaceException
2202
+	 */
2203
+	public function payment_method()
2204
+	{
2205
+		return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
2206
+	}
2207
+
2208
+
2209
+	/**
2210
+	 * @return \EE_Line_Item
2211
+	 * @throws EE_Error
2212
+	 * @throws EntityNotFoundException
2213
+	 * @throws InvalidArgumentException
2214
+	 * @throws InvalidDataTypeException
2215
+	 * @throws InvalidInterfaceException
2216
+	 * @throws ReflectionException
2217
+	 */
2218
+	public function ticket_line_item()
2219
+	{
2220
+		$ticket            = $this->ticket();
2221
+		$transaction       = $this->transaction();
2222
+		$line_item         = null;
2223
+		$ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
2224
+			$transaction->total_line_item(),
2225
+			'Ticket',
2226
+			[$ticket->ID()]
2227
+		);
2228
+		foreach ($ticket_line_items as $ticket_line_item) {
2229
+			if (
2230
+				$ticket_line_item instanceof \EE_Line_Item
2231
+				&& $ticket_line_item->OBJ_type() === 'Ticket'
2232
+				&& $ticket_line_item->OBJ_ID() === $ticket->ID()
2233
+			) {
2234
+				$line_item = $ticket_line_item;
2235
+				break;
2236
+			}
2237
+		}
2238
+		if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
2239
+			throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
2240
+		}
2241
+		return $line_item;
2242
+	}
2243
+
2244
+
2245
+	/**
2246
+	 * Soft Deletes this model object.
2247
+	 *
2248
+	 * @return int
2249
+	 * @throws DomainException
2250
+	 * @throws EE_Error
2251
+	 * @throws EntityNotFoundException
2252
+	 * @throws InvalidArgumentException
2253
+	 * @throws InvalidDataTypeException
2254
+	 * @throws InvalidInterfaceException
2255
+	 * @throws ReflectionException
2256
+	 * @throws RuntimeException
2257
+	 * @throws UnexpectedEntityException
2258
+	 */
2259
+	public function delete()
2260
+	{
2261
+		if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
2262
+			$this->set_status(
2263
+				RegStatus::CANCELLED,
2264
+				false,
2265
+				new Context(
2266
+					__METHOD__,
2267
+					esc_html__('Executed when a registration is trashed.', 'event_espresso')
2268
+				)
2269
+			);
2270
+		}
2271
+		return parent::delete();
2272
+	}
2273
+
2274
+
2275
+	/**
2276
+	 * Restores whatever the previous status was on a registration before it was trashed (if possible)
2277
+	 *
2278
+	 * @return int
2279
+	 * @throws DomainException
2280
+	 * @throws EE_Error
2281
+	 * @throws EntityNotFoundException
2282
+	 * @throws InvalidArgumentException
2283
+	 * @throws InvalidDataTypeException
2284
+	 * @throws InvalidInterfaceException
2285
+	 * @throws ReflectionException
2286
+	 * @throws RuntimeException
2287
+	 * @throws UnexpectedEntityException
2288
+	 */
2289
+	public function restore(): int
2290
+	{
2291
+		$previous_status = $this->get_extra_meta(
2292
+			EE_Registration::PRE_TRASH_REG_STATUS_KEY,
2293
+			true,
2294
+			RegStatus::CANCELLED
2295
+		);
2296
+		if ($previous_status) {
2297
+			$this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
2298
+			$this->set_status(
2299
+				$previous_status,
2300
+				false,
2301
+				new Context(
2302
+					__METHOD__,
2303
+					esc_html__('Executed when a trashed registration is restored.', 'event_espresso')
2304
+				)
2305
+			);
2306
+		}
2307
+		return parent::restore();
2308
+	}
2309
+
2310
+
2311
+	/**
2312
+	 * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
2313
+	 *
2314
+	 * @param boolean $trigger_set_status_logic  EE_Registration::set_status() can trigger additional logic
2315
+	 *                                           depending on whether the reg status changes to or from "Approved"
2316
+	 * @return boolean whether the Registration status was updated
2317
+	 * @throws DomainException
2318
+	 * @throws EE_Error
2319
+	 * @throws EntityNotFoundException
2320
+	 * @throws InvalidArgumentException
2321
+	 * @throws InvalidDataTypeException
2322
+	 * @throws InvalidInterfaceException
2323
+	 * @throws ReflectionException
2324
+	 * @throws RuntimeException
2325
+	 * @throws UnexpectedEntityException
2326
+	 */
2327
+	public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2328
+	{
2329
+		$paid  = $this->paid();
2330
+		$price = $this->final_price();
2331
+		switch (true) {
2332
+			// overpaid or paid
2333
+			case EEH_Money::compare_floats($paid, $price, '>'):
2334
+			case EEH_Money::compare_floats($paid, $price):
2335
+				$new_status = RegStatus::APPROVED;
2336
+				break;
2337
+			//  underpaid
2338
+			case EEH_Money::compare_floats($paid, $price, '<'):
2339
+				$new_status = RegStatus::PENDING_PAYMENT;
2340
+				break;
2341
+			// uhhh Houston...
2342
+			default:
2343
+				throw new RuntimeException(
2344
+					esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2345
+				);
2346
+		}
2347
+		if ($new_status !== $this->status_ID()) {
2348
+			if ($trigger_set_status_logic) {
2349
+				return $this->set_status(
2350
+					$new_status,
2351
+					false,
2352
+					new Context(
2353
+						__METHOD__,
2354
+						esc_html__(
2355
+							'Executed when the registration status is updated based on total paid.',
2356
+							'event_espresso'
2357
+						)
2358
+					)
2359
+				);
2360
+			}
2361
+			parent::set('STS_ID', $new_status);
2362
+			return true;
2363
+		}
2364
+		return false;
2365
+	}
2366
+
2367
+
2368
+	/*************************** DEPRECATED ***************************/
2369
+
2370
+
2371
+	/**
2372
+	 * @deprecated
2373
+	 * @since     4.7.0
2374
+	 */
2375
+	public function price_paid()
2376
+	{
2377
+		EE_Error::doing_it_wrong(
2378
+			'EE_Registration::price_paid()',
2379
+			esc_html__(
2380
+				'This method is deprecated, please use EE_Registration::final_price() instead.',
2381
+				'event_espresso'
2382
+			),
2383
+			'4.7.0'
2384
+		);
2385
+		return $this->final_price();
2386
+	}
2387
+
2388
+
2389
+	/**
2390
+	 * @param float $REG_final_price
2391
+	 * @throws EE_Error
2392
+	 * @throws EntityNotFoundException
2393
+	 * @throws InvalidArgumentException
2394
+	 * @throws InvalidDataTypeException
2395
+	 * @throws InvalidInterfaceException
2396
+	 * @throws ReflectionException
2397
+	 * @throws RuntimeException
2398
+	 * @throws DomainException
2399
+	 * @deprecated
2400
+	 * @since     4.7.0
2401
+	 */
2402
+	public function set_price_paid($REG_final_price = 0.00)
2403
+	{
2404
+		EE_Error::doing_it_wrong(
2405
+			'EE_Registration::set_price_paid()',
2406
+			esc_html__(
2407
+				'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2408
+				'event_espresso'
2409
+			),
2410
+			'4.7.0'
2411
+		);
2412
+		$this->set_final_price($REG_final_price);
2413
+	}
2414
+
2415
+
2416
+	/**
2417
+	 * @return string
2418
+	 * @throws EE_Error
2419
+	 * @throws InvalidArgumentException
2420
+	 * @throws InvalidDataTypeException
2421
+	 * @throws InvalidInterfaceException
2422
+	 * @throws ReflectionException
2423
+	 * @deprecated
2424
+	 * @since 4.7.0
2425
+	 */
2426
+	public function pretty_price_paid()
2427
+	{
2428
+		EE_Error::doing_it_wrong(
2429
+			'EE_Registration::pretty_price_paid()',
2430
+			esc_html__(
2431
+				'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2432
+				'event_espresso'
2433
+			),
2434
+			'4.7.0'
2435
+		);
2436
+		return $this->pretty_final_price();
2437
+	}
2438
+
2439
+
2440
+	/**
2441
+	 * Gets the primary datetime related to this registration via the related Event to this registration
2442
+	 *
2443
+	 * @return EE_Datetime
2444
+	 * @throws EE_Error
2445
+	 * @throws EntityNotFoundException
2446
+	 * @throws InvalidArgumentException
2447
+	 * @throws InvalidDataTypeException
2448
+	 * @throws InvalidInterfaceException
2449
+	 * @throws ReflectionException
2450
+	 * @deprecated 4.9.17
2451
+	 */
2452
+	public function get_related_primary_datetime()
2453
+	{
2454
+		EE_Error::doing_it_wrong(
2455
+			__METHOD__,
2456
+			esc_html__(
2457
+				'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2458
+				'event_espresso'
2459
+			),
2460
+			'4.9.17',
2461
+			'5.0.0'
2462
+		);
2463
+		return $this->event()->primary_datetime();
2464
+	}
2465
+
2466
+
2467
+	/**
2468
+	 * Returns the contact's name (or "Unknown" if there is no contact.)
2469
+	 *
2470
+	 * @return string
2471
+	 * @throws EE_Error
2472
+	 * @throws InvalidArgumentException
2473
+	 * @throws InvalidDataTypeException
2474
+	 * @throws InvalidInterfaceException
2475
+	 * @throws ReflectionException
2476
+	 * @since 4.10.12.p
2477
+	 */
2478
+	public function name()
2479
+	{
2480
+		return $this->attendeeName();
2481
+	}
2482
+
2483
+
2484
+	/**
2485
+	 * @return bool
2486
+	 * @throws EE_Error
2487
+	 * @throws ReflectionException
2488
+	 */
2489
+	public function wasMoved(): bool
2490
+	{
2491
+		// only need to check 'registration-moved-to' because
2492
+		// the existence of a new REG ID means the registration was moved
2493
+		$reg_moved = $this->get_extra_meta('registration-moved-to', true, []);
2494
+		return isset($reg_moved['NEW_REG_ID']) && $reg_moved['NEW_REG_ID'];
2495
+	}
2496
+
2497
+
2498
+	/**
2499
+	 * @param EE_Payment $payment
2500
+	 * @param float|null $amount
2501
+	 * @return float
2502
+	 * @throws EE_Error
2503
+	 * @throws ReflectionException
2504
+	 * @since 5.0.8.p
2505
+	 */
2506
+	public function applyPayment(EE_Payment $payment, ?float $amount = null): float
2507
+	{
2508
+		$payment_amount = $amount ?? $payment->amount();
2509
+		// ensure $payment_amount is NOT negative
2510
+		$payment_amount = (float) abs($payment_amount);
2511
+		$payment_amount = $payment->is_a_refund()
2512
+			? $this->processRefund($payment_amount)
2513
+			: $this->processPayment($payment_amount);
2514
+		if ($payment_amount) {
2515
+			$reg_payment = EEM_Registration_Payment::instance()->get_one(
2516
+				[['REG_ID' => $this->ID(), 'PAY_ID' => $payment->ID()]]
2517
+			);
2518
+			// if existing registration payment exists
2519
+			if ($reg_payment instanceof EE_Registration_Payment) {
2520
+				// then update that record
2521
+				$reg_payment->set_amount($payment_amount);
2522
+			} else {
2523
+				// or add new relation between registration and payment and set amount
2524
+				$reg_payment = EE_Registration_Payment::new_instance(
2525
+					[
2526
+						'REG_ID'     => $this->ID(),
2527
+						'PAY_ID'     => $payment->ID(),
2528
+						'RPY_amount' => $payment_amount,
2529
+					]
2530
+				);
2531
+			}
2532
+			$reg_payment->save();
2533
+		}
2534
+		return $payment_amount;
2535
+	}
2536
+
2537
+
2538
+	/**
2539
+	 * @throws EE_Error
2540
+	 * @throws ReflectionException
2541
+	 */
2542
+	private function processPayment(float $payment_amount): float
2543
+	{
2544
+		$paid  = $this->paid();
2545
+		$owing = $this->final_price() - $paid;
2546
+		if ($owing <= 0) {
2547
+			return 0.0;
2548
+		}
2549
+		// don't allow payment amount to exceed the incoming amount, OR the amount owing
2550
+		$payment_amount = min($payment_amount, $owing);
2551
+		$paid           = $paid + $payment_amount;
2552
+		// calculate and set new REG_paid
2553
+		$this->set_paid($paid);
2554
+		// make it stick
2555
+		$this->save();
2556
+		return (float) $payment_amount;
2557
+	}
2558
+
2559
+
2560
+	/**
2561
+	 * @throws ReflectionException
2562
+	 * @throws EE_Error
2563
+	 */
2564
+	private function processRefund(float $payment_amount): float
2565
+	{
2566
+		$paid = $this->paid();
2567
+		if ($paid <= 0) {
2568
+			return 0.0;
2569
+		}
2570
+		// don't allow refund amount to exceed the incoming amount, OR the amount paid
2571
+		$payment_amount = min($payment_amount, $paid);
2572
+		// calculate and set new REG_paid
2573
+		$paid = $paid - $payment_amount;
2574
+		$this->set_paid($paid);
2575
+		// make it stick
2576
+		$this->save();
2577
+		// convert payment amount back to a negative value for storage in the db
2578
+		return (float) $payment_amount * -1;
2579
+	}
2580
+
2581
+
2582
+	/**
2583
+	 * @return string
2584
+	 * @throws EE_Error
2585
+	 * @throws ReflectionException
2586
+	 * @since 5.0.20.p
2587
+	 */
2588
+	public function defaultRegistrationStatus(): string
2589
+	{
2590
+		$default_event_reg_status = $this->event()->default_registration_status();
2591
+		$default_reg_status = (string) apply_filters(
2592
+			'AFEE__EE_Registration__defaultRegistrationStatus__default_reg_status',
2593
+			$default_event_reg_status,
2594
+			$this
2595
+		);
2596
+		return RegStatus::isValidStatus($default_reg_status, false)
2597
+			? $default_reg_status
2598
+			: $default_event_reg_status;
2599
+	}
2600
+
2601
+
2602
+	/**
2603
+	 * @return string
2604
+	 * @throws EE_Error
2605
+	 * @throws ReflectionException
2606
+	 * @since 5.0.30.p
2607
+	 */
2608
+	public function cancelRegistrationConfirmationCode(): string
2609
+	{
2610
+		// concatenate all the fields that make up the source string
2611
+		// ex: 944-1084-720-379-7-2024-10-11 18:21:00-626-379-7-2ff6
2612
+		$source_string = $this->ID() . $this->event_ID() . $this->attendee_ID() . $this->ticket_ID();
2613
+		$source_string .= $this->count() . $this->reg_date() . $this->reg_code();
2614
+		// create a hash of the source string, ex: a9c0d28f79b5602a428e386821015420
2615
+		$source_string = md5($source_string);
2616
+		// return the first 4 characters of the hash in uppercase, ex: A9C0
2617
+		return strtoupper(substr($source_string, 0, 4));
2618
+	}
2619 2619
 }
Please login to merge, or discard this patch.
core/services/notifications/PersistentAdminNoticeManager.php 1 patch
Indentation   +411 added lines, -411 removed lines patch added patch discarded remove patch
@@ -29,415 +29,415 @@
 block discarded – undo
29 29
  */
30 30
 class PersistentAdminNoticeManager
31 31
 {
32
-    const WP_OPTION_KEY = 'ee_pers_admin_notices';
33
-
34
-    private CapabilitiesChecker $capabilities_checker;
35
-
36
-    private RequestInterface $request;
37
-
38
-    /**
39
-     * @var Collection|PersistentAdminNotice[]|null $notice_collection
40
-     */
41
-    private ?Collection $notice_collection = null;
42
-
43
-    /**
44
-     * if AJAX is not enabled, then the return URL will be used for redirecting back to the admin page where the
45
-     * persistent admin notice was displayed, and ultimately dismissed from.
46
-     *
47
-     * @var string $return_url
48
-     */
49
-    private string $return_url;
50
-
51
-
52
-    /**
53
-     * PersistentAdminNoticeManager constructor
54
-     *
55
-     * @param CapabilitiesChecker $capabilities_checker
56
-     * @param RequestInterface    $request
57
-     * @param string              $return_url where to  redirect to after dismissing notices
58
-     */
59
-    public function __construct(
60
-        CapabilitiesChecker $capabilities_checker,
61
-        RequestInterface $request,
62
-        string $return_url = ''
63
-    ) {
64
-        $this->capabilities_checker = $capabilities_checker;
65
-        $this->request              = $request;
66
-        $this->setReturnUrl($return_url);
67
-        add_action('wp_ajax_dismiss_ee_nag_notice', [$this, 'dismissNotice']);
68
-        add_action('shutdown', [$this, 'registerAndSaveNotices'], 998);
69
-    }
70
-
71
-
72
-    public function loadAdminNotices()
73
-    {
74
-        // setup up notices at priority 9 because `EE_Admin::display_admin_notices()` runs at priority 10,
75
-        // and we want to retrieve and generate any nag notices at the last possible moment
76
-        add_action('admin_notices', [$this, 'displayNotices'], 9);
77
-        add_action('network_admin_notices', [$this, 'displayNotices'], 9);
78
-    }
79
-
80
-
81
-    /**
82
-     * @param string $return_url
83
-     */
84
-    public function setReturnUrl(string $return_url)
85
-    {
86
-        $this->return_url = $return_url;
87
-    }
88
-
89
-
90
-    /**
91
-     * @return Collection
92
-     * @throws InvalidEntityException
93
-     * @throws InvalidInterfaceException
94
-     * @throws DomainException
95
-     * @throws DuplicateCollectionIdentifierException
96
-     */
97
-    protected function getPersistentAdminNoticeCollection(): Collection
98
-    {
99
-        if (! $this->notice_collection instanceof Collection) {
100
-            $this->notice_collection = new Collection(
101
-                'EventEspresso\core\domain\entities\notifications\PersistentAdminNotice'
102
-            );
103
-            $this->retrieveStoredNotices();
104
-            $this->registerNotices();
105
-        }
106
-        return $this->notice_collection;
107
-    }
108
-
109
-
110
-    /**
111
-     * generates PersistentAdminNotice objects for all non-dismissed notices saved to the db
112
-     *
113
-     * @return void
114
-     * @throws InvalidEntityException
115
-     * @throws DomainException
116
-     * @throws DuplicateCollectionIdentifierException
117
-     */
118
-    protected function retrieveStoredNotices()
119
-    {
120
-        $persistent_admin_notices = get_option(PersistentAdminNoticeManager::WP_OPTION_KEY, []);
121
-        if (! empty($persistent_admin_notices)) {
122
-            foreach ($persistent_admin_notices as $name => $details) {
123
-                if (is_array($details)) {
124
-                    if (
125
-                        ! isset(
126
-                            $details['message'],
127
-                            $details['capability'],
128
-                            $details['cap_context'],
129
-                            $details['dismissed']
130
-                        )
131
-                    ) {
132
-                        throw new DomainException(
133
-                            sprintf(
134
-                                esc_html__(
135
-                                    'The "%1$s" PersistentAdminNotice could not be retrieved from the database.',
136
-                                    'event_espresso'
137
-                                ),
138
-                                $name
139
-                            )
140
-                        );
141
-                    }
142
-                    $notice = new PersistentAdminNotice(
143
-                        (string) $name,
144
-                        (string) $details['message'],
145
-                        (bool) ($details['force_update'] ?? false),
146
-                        (string) $details['capability'],
147
-                        (string) $details['cap_context'],
148
-                        (bool) $details['dismissed'],
149
-                        (string) ($details['type'] ?? 'info'),
150
-                        (string) ($details['extra_css'] ?? '')
151
-                    );
152
-                    // new format for nag notices
153
-                    $this->notice_collection->add($notice, sanitize_key($name));
154
-                } else {
155
-                    try {
156
-                        // old nag notices, that we want to convert to the new format
157
-                        $this->notice_collection->add(
158
-                            new PersistentAdminNotice(
159
-                                (string) $name,
160
-                                (string) $details,
161
-                                false,
162
-                                '',
163
-                                '',
164
-                                empty($details)
165
-                            ),
166
-                            sanitize_key($name)
167
-                        );
168
-                    } catch (Exception $e) {
169
-                        EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
170
-                    }
171
-                }
172
-                // each notice will self register when the action hook in registerNotices is triggered
173
-            }
174
-        }
175
-    }
176
-
177
-
178
-    /**
179
-     * exposes the Persistent Admin Notice Collection via an action
180
-     * so that PersistentAdminNotice objects can be added and/or removed
181
-     * without compromising the actual collection like a filter would
182
-     */
183
-    protected function registerNotices()
184
-    {
185
-        do_action(
186
-            'AHEE__EventEspresso_core_services_notifications_PersistentAdminNoticeManager__registerNotices',
187
-            $this->notice_collection
188
-        );
189
-    }
190
-
191
-
192
-    /**
193
-     * @throws DomainException
194
-     * @throws InvalidClassException
195
-     * @throws InvalidInterfaceException
196
-     * @throws InvalidEntityException
197
-     * @throws DuplicateCollectionIdentifierException
198
-     */
199
-    public function displayNotices()
200
-    {
201
-        $this->notice_collection = $this->getPersistentAdminNoticeCollection();
202
-        if ($this->notice_collection->hasObjects()) {
203
-            $enqueue_assets = false;
204
-            // and display notices
205
-            foreach ($this->notice_collection as $persistent_admin_notice) {
206
-                /** @var PersistentAdminNotice $persistent_admin_notice */
207
-                // don't display notices that have already been dismissed
208
-                if ($persistent_admin_notice->getDismissed()) {
209
-                    continue;
210
-                }
211
-                try {
212
-                    $this->capabilities_checker->processCapCheck(
213
-                        $persistent_admin_notice->getCapCheck()
214
-                    );
215
-                } catch (InsufficientPermissionsException $e) {
216
-                    // user does not have required cap, so skip to next notice
217
-                    // and just eat the exception - nom nom nom nom
218
-                    continue;
219
-                }
220
-                if ($persistent_admin_notice->getMessage() === '') {
221
-                    continue;
222
-                }
223
-                $this->displayPersistentAdminNotice($persistent_admin_notice);
224
-                $enqueue_assets = true;
225
-            }
226
-            if ($enqueue_assets) {
227
-                $this->enqueueAssets();
228
-            }
229
-        }
230
-    }
231
-
232
-
233
-    /**
234
-     * does what it's named
235
-     *
236
-     * @return void
237
-     */
238
-    public function enqueueAssets()
239
-    {
240
-        wp_register_script(
241
-            'espresso_core',
242
-            EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
243
-            ['jquery'],
244
-            EVENT_ESPRESSO_VERSION,
245
-            true
246
-        );
247
-        wp_register_script(
248
-            'ee_error_js',
249
-            EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js',
250
-            ['espresso_core'],
251
-            EVENT_ESPRESSO_VERSION,
252
-            true
253
-        );
254
-        wp_localize_script(
255
-            'ee_error_js',
256
-            'ee_dismiss',
257
-            [
258
-                'return_url'    => urlencode($this->return_url),
259
-                'ajax_url'      => WP_AJAX_URL,
260
-                'unknown_error' => wp_strip_all_tags(
261
-                    __(
262
-                        'An unknown error has occurred on the server while attempting to dismiss this notice.',
263
-                        'event_espresso'
264
-                    )
265
-                ),
266
-            ]
267
-        );
268
-        wp_enqueue_script('ee_error_js');
269
-    }
270
-
271
-
272
-    /**
273
-     * displayPersistentAdminNoticeHtml
274
-     *
275
-     * @param PersistentAdminNotice $persistent_admin_notice
276
-     */
277
-    protected function displayPersistentAdminNotice(PersistentAdminNotice $persistent_admin_notice)
278
-    {
279
-        // used in template
280
-        $persistent_admin_notice_name    = $persistent_admin_notice->getName();
281
-        $persistent_admin_notice_message = $persistent_admin_notice->getMessage();
282
-        $persistent_admin_notice_type    = $persistent_admin_notice->getType();
283
-        $persistent_admin_notice_css     = $persistent_admin_notice->extraCss();
284
-        $is_dismissible                  = $persistent_admin_notice->getForceUpdate() !== true;
285
-        require EE_TEMPLATES . '/notifications/persistent_admin_notice.template.php';
286
-    }
287
-
288
-
289
-    /**
290
-     * dismissNotice
291
-     *
292
-     * @param string $pan_name the name, or key of the Persistent Admin Notice to be dismissed
293
-     * @param bool   $purge    if true, then delete it from the db
294
-     * @param bool   $return   forget all of this AJAX or redirect nonsense, and just return
295
-     * @return void
296
-     * @throws InvalidEntityException
297
-     * @throws InvalidInterfaceException
298
-     * @throws DomainException
299
-     * @throws InvalidArgumentException
300
-     * @throws InvalidArgumentException
301
-     * @throws InvalidArgumentException
302
-     * @throws InvalidArgumentException
303
-     * @throws DuplicateCollectionIdentifierException
304
-     */
305
-    public function dismissNotice(string $pan_name = '', bool $purge = false, bool $return = false)
306
-    {
307
-        $pan_name                = $this->request->getRequestParam('ee_nag_notice', $pan_name);
308
-        $this->notice_collection = $this->getPersistentAdminNoticeCollection();
309
-        if (! empty($pan_name) && $this->notice_collection->has($pan_name)) {
310
-            /** @var PersistentAdminNotice $persistent_admin_notice */
311
-            $persistent_admin_notice = $this->notice_collection->get($pan_name);
312
-            try {
313
-                $this->capabilities_checker->processCapCheck(
314
-                    $persistent_admin_notice->getCapCheck()
315
-                );
316
-            } catch (InsufficientPermissionsException $e) {
317
-                // user does not have required cap, so just eat the exception - nom nom nom nom
318
-                return;
319
-            }
320
-            $persistent_admin_notice->setDismissed(true);
321
-            $persistent_admin_notice->setPurge($purge);
322
-            $this->saveNotices();
323
-        }
324
-        if ($return) {
325
-            return;
326
-        }
327
-        if ($this->request->isAjax()) {
328
-            // grab any notices and concatenate into string
329
-            echo wp_json_encode(
330
-                [
331
-                    'errors' => implode('<br />', EE_Error::get_notices(false)),
332
-                ]
333
-            );
334
-            exit();
335
-        }
336
-        // save errors to a transient to be displayed on next request (after redirect)
337
-        EE_Error::get_notices(false, true);
338
-        wp_safe_redirect(
339
-            urldecode(
340
-                $this->request->getRequestParam('return_url', '')
341
-            )
342
-        );
343
-    }
344
-
345
-
346
-    /**
347
-     * saveNotices
348
-     *
349
-     * @throws DomainException
350
-     * @throws InvalidInterfaceException
351
-     * @throws InvalidEntityException
352
-     * @throws DuplicateCollectionIdentifierException
353
-     */
354
-    public function saveNotices()
355
-    {
356
-        $this->notice_collection = $this->getPersistentAdminNoticeCollection();
357
-        $new_notices_array = [];
358
-        if ($this->notice_collection->hasObjects()) {
359
-            $persistent_admin_notices = get_option(PersistentAdminNoticeManager::WP_OPTION_KEY, []);
360
-            // maybe initialize persistent_admin_notices
361
-            if (empty($persistent_admin_notices)) {
362
-                add_option(PersistentAdminNoticeManager::WP_OPTION_KEY, [], '', 'no');
363
-            }
364
-            foreach ($this->notice_collection as $persistent_admin_notice) {
365
-                // remove this notice ?
366
-                if ($persistent_admin_notice->getPurge()) {
367
-                    continue;
368
-                }
369
-                /** @var PersistentAdminNotice $persistent_admin_notice */
370
-                $new_notices_array[ $persistent_admin_notice->getName() ] = [
371
-                    'message'      => $persistent_admin_notice->getMessage(),
372
-                    'capability'   => $persistent_admin_notice->getCapability(),
373
-                    'cap_context'  => $persistent_admin_notice->getCapContext(),
374
-                    'dismissed'    => $persistent_admin_notice->getDismissed(),
375
-                    'force_update' => $persistent_admin_notice->getForceUpdate(),
376
-                    'type'         => $persistent_admin_notice->getType(),
377
-                    'extra_css'    => $persistent_admin_notice->extraCss(),
378
-                ];
379
-            }
380
-        }
381
-        update_option(PersistentAdminNoticeManager::WP_OPTION_KEY, $new_notices_array);
382
-    }
383
-
384
-
385
-    /**
386
-     * @throws DomainException
387
-     * @throws InvalidEntityException
388
-     * @throws InvalidInterfaceException
389
-     * @throws DuplicateCollectionIdentifierException
390
-     */
391
-    public function registerAndSaveNotices()
392
-    {
393
-        $this->getPersistentAdminNoticeCollection();
394
-        $this->registerNotices();
395
-        $this->saveNotices();
396
-        add_filter(
397
-            'PersistentAdminNoticeManager__registerAndSaveNotices__complete',
398
-            '__return_true'
399
-        );
400
-    }
401
-
402
-
403
-    /**
404
-     * @throws DomainException
405
-     * @throws InvalidEntityException
406
-     * @throws InvalidInterfaceException
407
-     * @throws InvalidArgumentException
408
-     * @throws DuplicateCollectionIdentifierException
409
-     */
410
-    public static function loadRegisterAndSaveNotices()
411
-    {
412
-        /** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
413
-        $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
414
-            'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
415
-        );
416
-        // if shutdown has already run, then call registerAndSaveNotices() manually
417
-        if (did_action('shutdown')) {
418
-            $persistent_admin_notice_manager->registerAndSaveNotices();
419
-        }
420
-    }
421
-
422
-
423
-    public static function dismissPersistentAdminNotice(string $notice_name)
424
-    {
425
-        /** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
426
-        $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(PersistentAdminNoticeManager::class);
427
-        $persistent_admin_notice_manager->dismissNotice(sanitize_key($notice_name), true, true);
428
-    }
429
-
430
-
431
-    public static function deletePersistentAdminNotice(string $notice_name)
432
-    {
433
-        /** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
434
-        $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(PersistentAdminNoticeManager::class);
435
-        $persistent_admin_notice_manager->getPersistentAdminNoticeCollection();
436
-        $notice = $persistent_admin_notice_manager->notice_collection->get(sanitize_key($notice_name));
437
-        if ($notice) {
438
-            $notice->setPurge(true);
439
-            $persistent_admin_notice_manager->notice_collection->remove($notice);
440
-            $persistent_admin_notice_manager->saveNotices();
441
-        }
442
-    }
32
+	const WP_OPTION_KEY = 'ee_pers_admin_notices';
33
+
34
+	private CapabilitiesChecker $capabilities_checker;
35
+
36
+	private RequestInterface $request;
37
+
38
+	/**
39
+	 * @var Collection|PersistentAdminNotice[]|null $notice_collection
40
+	 */
41
+	private ?Collection $notice_collection = null;
42
+
43
+	/**
44
+	 * if AJAX is not enabled, then the return URL will be used for redirecting back to the admin page where the
45
+	 * persistent admin notice was displayed, and ultimately dismissed from.
46
+	 *
47
+	 * @var string $return_url
48
+	 */
49
+	private string $return_url;
50
+
51
+
52
+	/**
53
+	 * PersistentAdminNoticeManager constructor
54
+	 *
55
+	 * @param CapabilitiesChecker $capabilities_checker
56
+	 * @param RequestInterface    $request
57
+	 * @param string              $return_url where to  redirect to after dismissing notices
58
+	 */
59
+	public function __construct(
60
+		CapabilitiesChecker $capabilities_checker,
61
+		RequestInterface $request,
62
+		string $return_url = ''
63
+	) {
64
+		$this->capabilities_checker = $capabilities_checker;
65
+		$this->request              = $request;
66
+		$this->setReturnUrl($return_url);
67
+		add_action('wp_ajax_dismiss_ee_nag_notice', [$this, 'dismissNotice']);
68
+		add_action('shutdown', [$this, 'registerAndSaveNotices'], 998);
69
+	}
70
+
71
+
72
+	public function loadAdminNotices()
73
+	{
74
+		// setup up notices at priority 9 because `EE_Admin::display_admin_notices()` runs at priority 10,
75
+		// and we want to retrieve and generate any nag notices at the last possible moment
76
+		add_action('admin_notices', [$this, 'displayNotices'], 9);
77
+		add_action('network_admin_notices', [$this, 'displayNotices'], 9);
78
+	}
79
+
80
+
81
+	/**
82
+	 * @param string $return_url
83
+	 */
84
+	public function setReturnUrl(string $return_url)
85
+	{
86
+		$this->return_url = $return_url;
87
+	}
88
+
89
+
90
+	/**
91
+	 * @return Collection
92
+	 * @throws InvalidEntityException
93
+	 * @throws InvalidInterfaceException
94
+	 * @throws DomainException
95
+	 * @throws DuplicateCollectionIdentifierException
96
+	 */
97
+	protected function getPersistentAdminNoticeCollection(): Collection
98
+	{
99
+		if (! $this->notice_collection instanceof Collection) {
100
+			$this->notice_collection = new Collection(
101
+				'EventEspresso\core\domain\entities\notifications\PersistentAdminNotice'
102
+			);
103
+			$this->retrieveStoredNotices();
104
+			$this->registerNotices();
105
+		}
106
+		return $this->notice_collection;
107
+	}
108
+
109
+
110
+	/**
111
+	 * generates PersistentAdminNotice objects for all non-dismissed notices saved to the db
112
+	 *
113
+	 * @return void
114
+	 * @throws InvalidEntityException
115
+	 * @throws DomainException
116
+	 * @throws DuplicateCollectionIdentifierException
117
+	 */
118
+	protected function retrieveStoredNotices()
119
+	{
120
+		$persistent_admin_notices = get_option(PersistentAdminNoticeManager::WP_OPTION_KEY, []);
121
+		if (! empty($persistent_admin_notices)) {
122
+			foreach ($persistent_admin_notices as $name => $details) {
123
+				if (is_array($details)) {
124
+					if (
125
+						! isset(
126
+							$details['message'],
127
+							$details['capability'],
128
+							$details['cap_context'],
129
+							$details['dismissed']
130
+						)
131
+					) {
132
+						throw new DomainException(
133
+							sprintf(
134
+								esc_html__(
135
+									'The "%1$s" PersistentAdminNotice could not be retrieved from the database.',
136
+									'event_espresso'
137
+								),
138
+								$name
139
+							)
140
+						);
141
+					}
142
+					$notice = new PersistentAdminNotice(
143
+						(string) $name,
144
+						(string) $details['message'],
145
+						(bool) ($details['force_update'] ?? false),
146
+						(string) $details['capability'],
147
+						(string) $details['cap_context'],
148
+						(bool) $details['dismissed'],
149
+						(string) ($details['type'] ?? 'info'),
150
+						(string) ($details['extra_css'] ?? '')
151
+					);
152
+					// new format for nag notices
153
+					$this->notice_collection->add($notice, sanitize_key($name));
154
+				} else {
155
+					try {
156
+						// old nag notices, that we want to convert to the new format
157
+						$this->notice_collection->add(
158
+							new PersistentAdminNotice(
159
+								(string) $name,
160
+								(string) $details,
161
+								false,
162
+								'',
163
+								'',
164
+								empty($details)
165
+							),
166
+							sanitize_key($name)
167
+						);
168
+					} catch (Exception $e) {
169
+						EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
170
+					}
171
+				}
172
+				// each notice will self register when the action hook in registerNotices is triggered
173
+			}
174
+		}
175
+	}
176
+
177
+
178
+	/**
179
+	 * exposes the Persistent Admin Notice Collection via an action
180
+	 * so that PersistentAdminNotice objects can be added and/or removed
181
+	 * without compromising the actual collection like a filter would
182
+	 */
183
+	protected function registerNotices()
184
+	{
185
+		do_action(
186
+			'AHEE__EventEspresso_core_services_notifications_PersistentAdminNoticeManager__registerNotices',
187
+			$this->notice_collection
188
+		);
189
+	}
190
+
191
+
192
+	/**
193
+	 * @throws DomainException
194
+	 * @throws InvalidClassException
195
+	 * @throws InvalidInterfaceException
196
+	 * @throws InvalidEntityException
197
+	 * @throws DuplicateCollectionIdentifierException
198
+	 */
199
+	public function displayNotices()
200
+	{
201
+		$this->notice_collection = $this->getPersistentAdminNoticeCollection();
202
+		if ($this->notice_collection->hasObjects()) {
203
+			$enqueue_assets = false;
204
+			// and display notices
205
+			foreach ($this->notice_collection as $persistent_admin_notice) {
206
+				/** @var PersistentAdminNotice $persistent_admin_notice */
207
+				// don't display notices that have already been dismissed
208
+				if ($persistent_admin_notice->getDismissed()) {
209
+					continue;
210
+				}
211
+				try {
212
+					$this->capabilities_checker->processCapCheck(
213
+						$persistent_admin_notice->getCapCheck()
214
+					);
215
+				} catch (InsufficientPermissionsException $e) {
216
+					// user does not have required cap, so skip to next notice
217
+					// and just eat the exception - nom nom nom nom
218
+					continue;
219
+				}
220
+				if ($persistent_admin_notice->getMessage() === '') {
221
+					continue;
222
+				}
223
+				$this->displayPersistentAdminNotice($persistent_admin_notice);
224
+				$enqueue_assets = true;
225
+			}
226
+			if ($enqueue_assets) {
227
+				$this->enqueueAssets();
228
+			}
229
+		}
230
+	}
231
+
232
+
233
+	/**
234
+	 * does what it's named
235
+	 *
236
+	 * @return void
237
+	 */
238
+	public function enqueueAssets()
239
+	{
240
+		wp_register_script(
241
+			'espresso_core',
242
+			EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
243
+			['jquery'],
244
+			EVENT_ESPRESSO_VERSION,
245
+			true
246
+		);
247
+		wp_register_script(
248
+			'ee_error_js',
249
+			EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js',
250
+			['espresso_core'],
251
+			EVENT_ESPRESSO_VERSION,
252
+			true
253
+		);
254
+		wp_localize_script(
255
+			'ee_error_js',
256
+			'ee_dismiss',
257
+			[
258
+				'return_url'    => urlencode($this->return_url),
259
+				'ajax_url'      => WP_AJAX_URL,
260
+				'unknown_error' => wp_strip_all_tags(
261
+					__(
262
+						'An unknown error has occurred on the server while attempting to dismiss this notice.',
263
+						'event_espresso'
264
+					)
265
+				),
266
+			]
267
+		);
268
+		wp_enqueue_script('ee_error_js');
269
+	}
270
+
271
+
272
+	/**
273
+	 * displayPersistentAdminNoticeHtml
274
+	 *
275
+	 * @param PersistentAdminNotice $persistent_admin_notice
276
+	 */
277
+	protected function displayPersistentAdminNotice(PersistentAdminNotice $persistent_admin_notice)
278
+	{
279
+		// used in template
280
+		$persistent_admin_notice_name    = $persistent_admin_notice->getName();
281
+		$persistent_admin_notice_message = $persistent_admin_notice->getMessage();
282
+		$persistent_admin_notice_type    = $persistent_admin_notice->getType();
283
+		$persistent_admin_notice_css     = $persistent_admin_notice->extraCss();
284
+		$is_dismissible                  = $persistent_admin_notice->getForceUpdate() !== true;
285
+		require EE_TEMPLATES . '/notifications/persistent_admin_notice.template.php';
286
+	}
287
+
288
+
289
+	/**
290
+	 * dismissNotice
291
+	 *
292
+	 * @param string $pan_name the name, or key of the Persistent Admin Notice to be dismissed
293
+	 * @param bool   $purge    if true, then delete it from the db
294
+	 * @param bool   $return   forget all of this AJAX or redirect nonsense, and just return
295
+	 * @return void
296
+	 * @throws InvalidEntityException
297
+	 * @throws InvalidInterfaceException
298
+	 * @throws DomainException
299
+	 * @throws InvalidArgumentException
300
+	 * @throws InvalidArgumentException
301
+	 * @throws InvalidArgumentException
302
+	 * @throws InvalidArgumentException
303
+	 * @throws DuplicateCollectionIdentifierException
304
+	 */
305
+	public function dismissNotice(string $pan_name = '', bool $purge = false, bool $return = false)
306
+	{
307
+		$pan_name                = $this->request->getRequestParam('ee_nag_notice', $pan_name);
308
+		$this->notice_collection = $this->getPersistentAdminNoticeCollection();
309
+		if (! empty($pan_name) && $this->notice_collection->has($pan_name)) {
310
+			/** @var PersistentAdminNotice $persistent_admin_notice */
311
+			$persistent_admin_notice = $this->notice_collection->get($pan_name);
312
+			try {
313
+				$this->capabilities_checker->processCapCheck(
314
+					$persistent_admin_notice->getCapCheck()
315
+				);
316
+			} catch (InsufficientPermissionsException $e) {
317
+				// user does not have required cap, so just eat the exception - nom nom nom nom
318
+				return;
319
+			}
320
+			$persistent_admin_notice->setDismissed(true);
321
+			$persistent_admin_notice->setPurge($purge);
322
+			$this->saveNotices();
323
+		}
324
+		if ($return) {
325
+			return;
326
+		}
327
+		if ($this->request->isAjax()) {
328
+			// grab any notices and concatenate into string
329
+			echo wp_json_encode(
330
+				[
331
+					'errors' => implode('<br />', EE_Error::get_notices(false)),
332
+				]
333
+			);
334
+			exit();
335
+		}
336
+		// save errors to a transient to be displayed on next request (after redirect)
337
+		EE_Error::get_notices(false, true);
338
+		wp_safe_redirect(
339
+			urldecode(
340
+				$this->request->getRequestParam('return_url', '')
341
+			)
342
+		);
343
+	}
344
+
345
+
346
+	/**
347
+	 * saveNotices
348
+	 *
349
+	 * @throws DomainException
350
+	 * @throws InvalidInterfaceException
351
+	 * @throws InvalidEntityException
352
+	 * @throws DuplicateCollectionIdentifierException
353
+	 */
354
+	public function saveNotices()
355
+	{
356
+		$this->notice_collection = $this->getPersistentAdminNoticeCollection();
357
+		$new_notices_array = [];
358
+		if ($this->notice_collection->hasObjects()) {
359
+			$persistent_admin_notices = get_option(PersistentAdminNoticeManager::WP_OPTION_KEY, []);
360
+			// maybe initialize persistent_admin_notices
361
+			if (empty($persistent_admin_notices)) {
362
+				add_option(PersistentAdminNoticeManager::WP_OPTION_KEY, [], '', 'no');
363
+			}
364
+			foreach ($this->notice_collection as $persistent_admin_notice) {
365
+				// remove this notice ?
366
+				if ($persistent_admin_notice->getPurge()) {
367
+					continue;
368
+				}
369
+				/** @var PersistentAdminNotice $persistent_admin_notice */
370
+				$new_notices_array[ $persistent_admin_notice->getName() ] = [
371
+					'message'      => $persistent_admin_notice->getMessage(),
372
+					'capability'   => $persistent_admin_notice->getCapability(),
373
+					'cap_context'  => $persistent_admin_notice->getCapContext(),
374
+					'dismissed'    => $persistent_admin_notice->getDismissed(),
375
+					'force_update' => $persistent_admin_notice->getForceUpdate(),
376
+					'type'         => $persistent_admin_notice->getType(),
377
+					'extra_css'    => $persistent_admin_notice->extraCss(),
378
+				];
379
+			}
380
+		}
381
+		update_option(PersistentAdminNoticeManager::WP_OPTION_KEY, $new_notices_array);
382
+	}
383
+
384
+
385
+	/**
386
+	 * @throws DomainException
387
+	 * @throws InvalidEntityException
388
+	 * @throws InvalidInterfaceException
389
+	 * @throws DuplicateCollectionIdentifierException
390
+	 */
391
+	public function registerAndSaveNotices()
392
+	{
393
+		$this->getPersistentAdminNoticeCollection();
394
+		$this->registerNotices();
395
+		$this->saveNotices();
396
+		add_filter(
397
+			'PersistentAdminNoticeManager__registerAndSaveNotices__complete',
398
+			'__return_true'
399
+		);
400
+	}
401
+
402
+
403
+	/**
404
+	 * @throws DomainException
405
+	 * @throws InvalidEntityException
406
+	 * @throws InvalidInterfaceException
407
+	 * @throws InvalidArgumentException
408
+	 * @throws DuplicateCollectionIdentifierException
409
+	 */
410
+	public static function loadRegisterAndSaveNotices()
411
+	{
412
+		/** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
413
+		$persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
414
+			'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
415
+		);
416
+		// if shutdown has already run, then call registerAndSaveNotices() manually
417
+		if (did_action('shutdown')) {
418
+			$persistent_admin_notice_manager->registerAndSaveNotices();
419
+		}
420
+	}
421
+
422
+
423
+	public static function dismissPersistentAdminNotice(string $notice_name)
424
+	{
425
+		/** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
426
+		$persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(PersistentAdminNoticeManager::class);
427
+		$persistent_admin_notice_manager->dismissNotice(sanitize_key($notice_name), true, true);
428
+	}
429
+
430
+
431
+	public static function deletePersistentAdminNotice(string $notice_name)
432
+	{
433
+		/** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
434
+		$persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(PersistentAdminNoticeManager::class);
435
+		$persistent_admin_notice_manager->getPersistentAdminNoticeCollection();
436
+		$notice = $persistent_admin_notice_manager->notice_collection->get(sanitize_key($notice_name));
437
+		if ($notice) {
438
+			$notice->setPurge(true);
439
+			$persistent_admin_notice_manager->notice_collection->remove($notice);
440
+			$persistent_admin_notice_manager->saveNotices();
441
+		}
442
+	}
443 443
 }
Please login to merge, or discard this patch.
reg_steps/payment_options/EE_SPCO_Reg_Step_Payment_Options.class.php 1 patch
Indentation   +2930 added lines, -2930 removed lines patch added patch discarded remove patch
@@ -23,2934 +23,2934 @@
 block discarded – undo
23 23
  */
24 24
 class EE_SPCO_Reg_Step_Payment_Options extends EE_SPCO_Reg_Step
25 25
 {
26
-    /**
27
-     * @var EE_Line_Item_Display $Line_Item_Display
28
-     */
29
-    protected $line_item_display;
30
-
31
-    /**
32
-     * @var boolean $handle_IPN_in_this_request
33
-     */
34
-    protected $handle_IPN_in_this_request = false;
35
-
36
-
37
-    /**
38
-     *    set_hooks - for hooking into EE Core, other modules, etc
39
-     *
40
-     * @access    public
41
-     * @return    void
42
-     */
43
-    public static function set_hooks()
44
-    {
45
-        add_filter(
46
-            'FHEE__SPCO__EE_Line_Item_Filter_Collection',
47
-            ['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
48
-        );
49
-        add_action(
50
-            'wp_ajax_switch_spco_billing_form',
51
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
52
-        );
53
-        add_action(
54
-            'wp_ajax_nopriv_switch_spco_billing_form',
55
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
56
-        );
57
-        add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
58
-        add_action(
59
-            'wp_ajax_nopriv_save_payer_details',
60
-            ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
61
-        );
62
-        add_action(
63
-            'wp_ajax_get_transaction_details_for_gateways',
64
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
65
-        );
66
-        add_action(
67
-            'wp_ajax_nopriv_get_transaction_details_for_gateways',
68
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
69
-        );
70
-        add_filter(
71
-            'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
72
-            ['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
73
-        );
74
-    }
75
-
76
-
77
-    /**
78
-     *    ajax switch_spco_billing_form
79
-     *
80
-     */
81
-    public static function switch_spco_billing_form()
82
-    {
83
-        EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
84
-    }
85
-
86
-
87
-    /**
88
-     *    ajax save_payer_details
89
-     *
90
-     */
91
-    public static function save_payer_details()
92
-    {
93
-        EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
94
-    }
95
-
96
-
97
-    /**
98
-     *    ajax get_transaction_details
99
-     *
100
-     */
101
-    public static function get_transaction_details()
102
-    {
103
-        EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
104
-    }
105
-
106
-
107
-    /**
108
-     * bypass_recaptcha_for_load_payment_method
109
-     *
110
-     * @access public
111
-     * @return array
112
-     * @throws InvalidArgumentException
113
-     * @throws InvalidDataTypeException
114
-     * @throws InvalidInterfaceException
115
-     */
116
-    public static function bypass_recaptcha_for_load_payment_method()
117
-    {
118
-        return [
119
-            'EESID'  => EE_Registry::instance()->SSN->id(),
120
-            'step'   => 'payment_options',
121
-            'action' => 'spco_billing_form',
122
-        ];
123
-    }
124
-
125
-
126
-    /**
127
-     *    class constructor
128
-     *
129
-     * @access    public
130
-     * @param EE_Checkout $checkout
131
-     */
132
-    public function __construct(EE_Checkout $checkout)
133
-    {
134
-        $this->request   = EED_Single_Page_Checkout::getRequest();
135
-        $this->_slug     = 'payment_options';
136
-        $this->_name     = esc_html__('Payment Options', 'event_espresso');
137
-        $this->_template = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
138
-        $this->checkout  = $checkout;
139
-        $this->_reset_success_message();
140
-        $this->set_instructions(
141
-            esc_html__(
142
-                'Please select a method of payment and provide any necessary billing information before proceeding.',
143
-                'event_espresso'
144
-            )
145
-        );
146
-    }
147
-
148
-
149
-    /**
150
-     * @return null
151
-     */
152
-    public function line_item_display()
153
-    {
154
-        return $this->line_item_display;
155
-    }
156
-
157
-
158
-    /**
159
-     * @param null $line_item_display
160
-     */
161
-    public function set_line_item_display($line_item_display)
162
-    {
163
-        $this->line_item_display = $line_item_display;
164
-    }
165
-
166
-
167
-    /**
168
-     * @return boolean
169
-     */
170
-    public function handle_IPN_in_this_request()
171
-    {
172
-        return $this->handle_IPN_in_this_request;
173
-    }
174
-
175
-
176
-    /**
177
-     * @param boolean $handle_IPN_in_this_request
178
-     */
179
-    public function set_handle_IPN_in_this_request($handle_IPN_in_this_request)
180
-    {
181
-        $this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
182
-    }
183
-
184
-
185
-    /**
186
-     * translate_js_strings
187
-     *
188
-     * @return void
189
-     */
190
-    public function translate_js_strings()
191
-    {
192
-        EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
193
-            'Please select a method of payment in order to continue.',
194
-            'event_espresso'
195
-        );
196
-        EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
197
-            'A valid method of payment could not be determined. Please refresh the page and try again.',
198
-            'event_espresso'
199
-        );
200
-        EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
201
-            'Forwarding to Secure Payment Provider.',
202
-            'event_espresso'
203
-        );
204
-    }
205
-
206
-
207
-    /**
208
-     * enqueue_styles_and_scripts
209
-     *
210
-     * @return void
211
-     * @throws EE_Error
212
-     * @throws InvalidArgumentException
213
-     * @throws InvalidDataTypeException
214
-     * @throws InvalidInterfaceException
215
-     * @throws ReflectionException
216
-     */
217
-    public function enqueue_styles_and_scripts()
218
-    {
219
-        $transaction = $this->checkout->transaction;
220
-        // if the transaction isn't set or nothing is owed on it, don't enqueue any JS
221
-        if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
222
-            return;
223
-        }
224
-        foreach (
225
-            EEM_Payment_Method::instance()->get_all_for_transaction(
226
-                $transaction,
227
-                EEM_Payment_Method::scope_cart
228
-            ) as $payment_method
229
-        ) {
230
-            $type_obj = $payment_method->type_obj();
231
-            if ($type_obj instanceof EE_PMT_Base) {
232
-                $billing_form = $type_obj->generate_new_billing_form($transaction);
233
-                if ($billing_form instanceof EE_Form_Section_Proper) {
234
-                    $billing_form->enqueue_js();
235
-                }
236
-            }
237
-        }
238
-    }
239
-
240
-
241
-    /**
242
-     * initialize_reg_step
243
-     *
244
-     * @return bool
245
-     * @throws EE_Error
246
-     * @throws InvalidArgumentException
247
-     * @throws ReflectionException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     */
251
-    public function initialize_reg_step()
252
-    {
253
-        // TODO: if /when we implement donations, then this will need overriding
254
-        if (
255
-            // don't need payment options for:
256
-            // registrations made via the admin
257
-            // completed transactions
258
-            // overpaid transactions
259
-            // $ 0.00 transactions(no payment required)
260
-            ! $this->checkout->payment_required()
261
-            // but do NOT remove if current action being called belongs to this reg step
262
-            && ! is_callable([$this, $this->checkout->action])
263
-            && ! $this->completed()
264
-        ) {
265
-            // and if so, then we no longer need the Payment Options step
266
-            if ($this->is_current_step()) {
267
-                $this->checkout->generate_reg_form = false;
268
-            }
269
-            $this->checkout->remove_reg_step($this->_slug);
270
-            // DEBUG LOG
271
-            // $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
272
-            return false;
273
-        }
274
-        // load EEM_Payment_Method
275
-        EE_Registry::instance()->load_model('Payment_Method');
276
-        // get all active payment methods
277
-        $this->checkout->available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
278
-            $this->checkout->transaction,
279
-            EEM_Payment_Method::scope_cart
280
-        );
281
-        return true;
282
-    }
283
-
284
-
285
-    /**
286
-     * @return EE_Form_Section_Proper
287
-     * @throws EE_Error
288
-     * @throws InvalidArgumentException
289
-     * @throws ReflectionException
290
-     * @throws EntityNotFoundException
291
-     * @throws InvalidDataTypeException
292
-     * @throws InvalidInterfaceException
293
-     * @throws InvalidStatusException
294
-     */
295
-    public function generate_reg_form()
296
-    {
297
-        // reset in case someone changes their mind
298
-        $this->_reset_selected_method_of_payment();
299
-        // set some defaults
300
-        $this->checkout->selected_method_of_payment = 'payments_closed';
301
-        $registrations_requiring_payment            = [];
302
-        $registrations_for_free_events              = [];
303
-        $registrations_requiring_pre_approval       = [];
304
-        $sold_out_events                            = [];
305
-        $insufficient_spaces_available              = [];
306
-        $no_payment_required                        = true;
307
-        // loop thru registrations to gather info
308
-        $registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
309
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
310
-            $registrations,
311
-            $this->checkout->revisit
312
-        );
313
-        foreach ($registrations as $REG_ID => $registration) {
314
-            /** @var $registration EE_Registration */
315
-            // Skip if the registration has been moved
316
-            if ($registration->wasMoved()) {
317
-                continue;
318
-            }
319
-            // has this registration lost it's space ?
320
-            if (isset($ejected_registrations[ $REG_ID ])) {
321
-                if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
322
-                    $sold_out_events[ $registration->event()->ID() ] = $registration->event();
323
-                } else {
324
-                    $insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
325
-                }
326
-                continue;
327
-            }
328
-            // event requires admin approval
329
-            if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
330
-                // add event to list of events with pre-approval reg status
331
-                $registrations_requiring_pre_approval[ $REG_ID ] = $registration;
332
-                do_action(
333
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
334
-                    $registration->event(),
335
-                    $this
336
-                );
337
-                continue;
338
-            }
339
-            if (
340
-                $this->checkout->revisit
341
-                && $registration->status_ID() !== RegStatus::APPROVED
342
-                && (
343
-                    $registration->event()->is_sold_out()
344
-                    || $registration->event()->is_sold_out(true)
345
-                )
346
-            ) {
347
-                // add event to list of events that are sold out
348
-                $sold_out_events[ $registration->event()->ID() ] = $registration->event();
349
-                do_action(
350
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
351
-                    $registration->event(),
352
-                    $this
353
-                );
354
-                continue;
355
-            }
356
-            // are they allowed to pay now and is there monies owing?
357
-            if ($registration->owes_monies_and_can_pay()) {
358
-                $registrations_requiring_payment[ $REG_ID ] = $registration;
359
-                do_action(
360
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
361
-                    $registration->event(),
362
-                    $this
363
-                );
364
-            } elseif (
365
-                ! $this->checkout->revisit
366
-                      && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
367
-                      && $registration->ticket()->is_free()
368
-            ) {
369
-                $registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
370
-            }
371
-        }
372
-        $subsections = [];
373
-        // now decide which template to load
374
-        if (! empty($sold_out_events)) {
375
-            $subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
376
-        }
377
-        if (! empty($insufficient_spaces_available)) {
378
-            $subsections['insufficient_space'] = $this->_insufficient_spaces_available(
379
-                $insufficient_spaces_available
380
-            );
381
-        }
382
-        if (! empty($registrations_requiring_pre_approval)) {
383
-            $subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
384
-                $registrations_requiring_pre_approval
385
-            );
386
-        }
387
-        if (! empty($registrations_for_free_events)) {
388
-            $subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
389
-        }
390
-        if (! empty($registrations_requiring_payment)) {
391
-            if ($this->checkout->amount_owing > 0) {
392
-                // autoload Line_Item_Display classes
393
-                EEH_Autoloader::register_line_item_filter_autoloaders();
394
-                $line_item_filter_processor = new EE_Line_Item_Filter_Processor(
395
-                    apply_filters(
396
-                        'FHEE__SPCO__EE_Line_Item_Filter_Collection',
397
-                        new EE_Line_Item_Filter_Collection()
398
-                    ),
399
-                    $this->checkout->cart->get_grand_total()
400
-                );
401
-                /** @var EE_Line_Item $filtered_line_item_tree */
402
-                $filtered_line_item_tree = $line_item_filter_processor->process();
403
-                EEH_Autoloader::register_line_item_display_autoloaders();
404
-                $this->set_line_item_display(new EE_Line_Item_Display('spco'));
405
-                $subsections['payment_options'] = $this->_display_payment_options(
406
-                    $this->line_item_display->display_line_item(
407
-                        $filtered_line_item_tree,
408
-                        ['registrations' => $registrations]
409
-                    )
410
-                );
411
-                $this->checkout->amount_owing   = $filtered_line_item_tree->total();
412
-                $this->_apply_registration_payments_to_amount_owing($registrations);
413
-            }
414
-            $no_payment_required = false;
415
-        } else {
416
-            $this->_hide_reg_step_submit_button_if_revisit();
417
-        }
418
-        $this->_save_selected_method_of_payment();
419
-
420
-        $subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
421
-        $subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
422
-
423
-        return new EE_Form_Section_Proper(
424
-            [
425
-                'name'            => $this->reg_form_name(),
426
-                'html_id'         => $this->reg_form_name(),
427
-                'subsections'     => $subsections,
428
-                'layout_strategy' => new EE_No_Layout(),
429
-            ]
430
-        );
431
-    }
432
-
433
-
434
-    /**
435
-     * add line item filters required for this reg step
436
-     * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
437
-     *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
438
-     *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
439
-     *        payment options reg step, can apply these filters via the following: apply_filters(
440
-     *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
441
-     *        filter collection by passing that instead of instantiating a new collection
442
-     *
443
-     * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
444
-     * @return EE_Line_Item_Filter_Collection
445
-     * @throws EE_Error
446
-     * @throws InvalidArgumentException
447
-     * @throws ReflectionException
448
-     * @throws EntityNotFoundException
449
-     * @throws InvalidDataTypeException
450
-     * @throws InvalidInterfaceException
451
-     * @throws InvalidStatusException
452
-     */
453
-    public static function add_spco_line_item_filters(EE_Line_Item_Filter_Collection $line_item_filter_collection)
454
-    {
455
-        if (! EE_Registry::instance()->SSN instanceof EE_Session) {
456
-            return $line_item_filter_collection;
457
-        }
458
-        if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
459
-            return $line_item_filter_collection;
460
-        }
461
-        if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
462
-            return $line_item_filter_collection;
463
-        }
464
-        $line_item_filter_collection->add(
465
-            new EE_Billable_Line_Item_Filter(
466
-                EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
467
-                    EE_Registry::instance()->SSN->checkout()->transaction->registrations(
468
-                        EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
469
-                    )
470
-                )
471
-            )
472
-        );
473
-        $line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
474
-        return $line_item_filter_collection;
475
-    }
476
-
477
-
478
-    /**
479
-     * remove_ejected_registrations
480
-     * if a registrant has lost their potential space at an event due to lack of payment,
481
-     * then this method removes them from the list of registrations being paid for during this request
482
-     *
483
-     * @param EE_Registration[] $registrations
484
-     * @return EE_Registration[]
485
-     * @throws EE_Error
486
-     * @throws InvalidArgumentException
487
-     * @throws ReflectionException
488
-     * @throws EntityNotFoundException
489
-     * @throws InvalidDataTypeException
490
-     * @throws InvalidInterfaceException
491
-     * @throws InvalidStatusException
492
-     */
493
-    public static function remove_ejected_registrations(array $registrations)
494
-    {
495
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
496
-            $registrations,
497
-            EE_Registry::instance()->SSN->checkout()->revisit
498
-        );
499
-        foreach ($registrations as $REG_ID => $registration) {
500
-            // has this registration lost it's space ?
501
-            if (isset($ejected_registrations[ $REG_ID ])) {
502
-                unset($registrations[ $REG_ID ]);
503
-            }
504
-        }
505
-        return $registrations;
506
-    }
507
-
508
-
509
-    /**
510
-     * find_registrations_that_lost_their_space
511
-     * If a registrant chooses an offline payment method like Invoice,
512
-     * then no space is reserved for them at the event until they fully pay fo that site
513
-     * (unless the event's default reg status is set to APPROVED)
514
-     * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
515
-     * then this method will determine which registrations have lost the ability to complete the reg process.
516
-     *
517
-     * @param EE_Registration[] $registrations
518
-     * @param bool              $revisit
519
-     * @return array
520
-     * @throws EE_Error
521
-     * @throws InvalidArgumentException
522
-     * @throws ReflectionException
523
-     * @throws EntityNotFoundException
524
-     * @throws InvalidDataTypeException
525
-     * @throws InvalidInterfaceException
526
-     * @throws InvalidStatusException
527
-     */
528
-    public static function find_registrations_that_lost_their_space(array $registrations, $revisit = false)
529
-    {
530
-        // registrations per event
531
-        $event_reg_count = [];
532
-        // spaces left per event
533
-        $event_spaces_remaining = [];
534
-        // tickets left sorted by ID
535
-        $tickets_remaining = [];
536
-        // registrations that have lost their space
537
-        $ejected_registrations = [];
538
-        foreach ($registrations as $REG_ID => $registration) {
539
-            if (
540
-                $registration->status_ID() === RegStatus::APPROVED
541
-                || apply_filters(
542
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
543
-                    false,
544
-                    $registration,
545
-                    $revisit
546
-                )
547
-            ) {
548
-                continue;
549
-            }
550
-            $EVT_ID = $registration->event_ID();
551
-            $ticket = $registration->ticket();
552
-            if (! isset($tickets_remaining[ $ticket->ID() ])) {
553
-                $tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
554
-            }
555
-            if ($tickets_remaining[ $ticket->ID() ] > 0) {
556
-                if (! isset($event_reg_count[ $EVT_ID ])) {
557
-                    $event_reg_count[ $EVT_ID ] = 0;
558
-                }
559
-                $event_reg_count[ $EVT_ID ]++;
560
-                if (! isset($event_spaces_remaining[ $EVT_ID ])) {
561
-                    $event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
562
-                }
563
-            }
564
-            if (
565
-                $revisit
566
-                && ($tickets_remaining[ $ticket->ID() ] === 0
567
-                    || $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
568
-                )
569
-            ) {
570
-                $ejected_registrations[ $REG_ID ] = $registration->event();
571
-                if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
572
-                    /** @type EE_Registration_Processor $registration_processor */
573
-                    $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
574
-                    // at this point, we should have enough details about the registrant to consider the registration
575
-                    // NOT incomplete
576
-                    $registration_processor->manually_update_registration_status(
577
-                        $registration,
578
-                        RegStatus::WAIT_LIST
579
-                    );
580
-                }
581
-            }
582
-        }
583
-        return $ejected_registrations;
584
-    }
585
-
586
-
587
-    /**
588
-     * _hide_reg_step_submit_button
589
-     * removes the html for the reg step submit button
590
-     * by replacing it with an empty string via filter callback
591
-     *
592
-     * @return void
593
-     */
594
-    protected function _adjust_registration_status_if_event_old_sold()
595
-    {
596
-    }
597
-
598
-
599
-    /**
600
-     * _hide_reg_step_submit_button
601
-     * removes the html for the reg step submit button
602
-     * by replacing it with an empty string via filter callback
603
-     *
604
-     * @return void
605
-     */
606
-    protected function _hide_reg_step_submit_button_if_revisit()
607
-    {
608
-        if ($this->checkout->revisit) {
609
-            add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
610
-        }
611
-    }
612
-
613
-
614
-    /**
615
-     * sold_out_events
616
-     * displays notices regarding events that have sold out since hte registrant first signed up
617
-     *
618
-     * @param EE_Event[] $sold_out_events_array
619
-     * @return EE_Form_Section_Proper
620
-     * @throws EE_Error
621
-     */
622
-    private function _sold_out_events($sold_out_events_array = [])
623
-    {
624
-        // set some defaults
625
-        $this->checkout->selected_method_of_payment = 'events_sold_out';
626
-        $sold_out_events                            = '';
627
-        foreach ($sold_out_events_array as $sold_out_event) {
628
-            $sold_out_events .= EEH_HTML::li(
629
-                EEH_HTML::span(
630
-                    '  ' . $sold_out_event->name(),
631
-                    '',
632
-                    'dashicons dashicons-marker ee-icon-size-16 pink-text'
633
-                )
634
-            );
635
-        }
636
-        return new EE_Form_Section_Proper(
637
-            [
638
-                'layout_strategy' => new EE_Template_Layout(
639
-                    [
640
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
641
-                                                  . $this->_slug
642
-                                                  . '/sold_out_events.template.php',
643
-                        'template_args'        => apply_filters(
644
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
645
-                            [
646
-                                'sold_out_events'     => $sold_out_events,
647
-                                'sold_out_events_msg' => apply_filters(
648
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
649
-                                    sprintf(
650
-                                        esc_html__(
651
-                                            'It appears that the event you were about to make a payment for has sold out since you first registered. If you have already made a partial payment towards this event, please contact the event administrator for a refund.%3$s%3$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%2$s',
652
-                                            'event_espresso'
653
-                                        ),
654
-                                        '<strong>',
655
-                                        '</strong>',
656
-                                        '<br />'
657
-                                    )
658
-                                ),
659
-                            ]
660
-                        ),
661
-                    ]
662
-                ),
663
-            ]
664
-        );
665
-    }
666
-
667
-
668
-    /**
669
-     * _insufficient_spaces_available
670
-     * displays notices regarding events that do not have enough remaining spaces
671
-     * to satisfy the current number of registrations looking to pay
672
-     *
673
-     * @param EE_Event[] $insufficient_spaces_events_array
674
-     * @return EE_Form_Section_Proper
675
-     * @throws EE_Error
676
-     * @throws ReflectionException
677
-     */
678
-    private function _insufficient_spaces_available($insufficient_spaces_events_array = [])
679
-    {
680
-        // set some defaults
681
-        $this->checkout->selected_method_of_payment = 'invoice';
682
-        $insufficient_space_events                  = '';
683
-        foreach ($insufficient_spaces_events_array as $event) {
684
-            if ($event instanceof EE_Event) {
685
-                $insufficient_space_events .= EEH_HTML::li(
686
-                    EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
687
-                );
688
-            }
689
-        }
690
-        return new EE_Form_Section_Proper(
691
-            [
692
-                'subsections'     => [
693
-                    'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
694
-                    'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
695
-                ],
696
-                'layout_strategy' => new EE_Template_Layout(
697
-                    [
698
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
699
-                                                  . $this->_slug
700
-                                                  . '/sold_out_events.template.php',
701
-                        'template_args'        => apply_filters(
702
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
703
-                            [
704
-                                'sold_out_events'     => $insufficient_space_events,
705
-                                'sold_out_events_msg' => apply_filters(
706
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
707
-                                    esc_html__(
708
-                                        'It appears that the event you were about to make a payment for has sold additional tickets since you first registered, and there are no longer enough spaces left to accommodate your selections. You may continue to pay and secure the available space(s) remaining, or simply cancel if you no longer wish to purchase. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
709
-                                        'event_espresso'
710
-                                    )
711
-                                ),
712
-                            ]
713
-                        ),
714
-                    ]
715
-                ),
716
-            ]
717
-        );
718
-    }
719
-
720
-
721
-    /**
722
-     * registrations_requiring_pre_approval
723
-     *
724
-     * @param array $registrations_requiring_pre_approval
725
-     * @return EE_Form_Section_Proper
726
-     * @throws EE_Error
727
-     * @throws EntityNotFoundException
728
-     * @throws ReflectionException
729
-     */
730
-    private function _registrations_requiring_pre_approval($registrations_requiring_pre_approval = [])
731
-    {
732
-        $events_requiring_pre_approval = [];
733
-        foreach ($registrations_requiring_pre_approval as $registration) {
734
-            if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
735
-                $events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
736
-                    EEH_HTML::span(
737
-                        '',
738
-                        '',
739
-                        'dashicons dashicons-marker ee-icon-size-16 orange-text'
740
-                    )
741
-                    . EEH_HTML::span($registration->event()->name(), '', 'orange-text')
742
-                );
743
-            }
744
-        }
745
-        return new EE_Form_Section_Proper(
746
-            [
747
-                'layout_strategy' => new EE_Template_Layout(
748
-                    [
749
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
750
-                                                  . $this->_slug
751
-                                                  . '/events_requiring_pre_approval.template.php', // layout_template
752
-                        'template_args'        => apply_filters(
753
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
754
-                            [
755
-                                'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
756
-                                'events_requiring_pre_approval_msg' => apply_filters(
757
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
758
-                                    esc_html__(
759
-                                        'The following events do not require payment at this time and will not be billed during this transaction. Billing will only occur after the attendee has been approved by the event organizer. You will be notified when your registration has been processed. If this is a free event, then no billing will occur.',
760
-                                        'event_espresso'
761
-                                    )
762
-                                ),
763
-                            ]
764
-                        ),
765
-                    ]
766
-                ),
767
-            ]
768
-        );
769
-    }
770
-
771
-
772
-    /**
773
-     * _no_payment_required
774
-     *
775
-     * @param EE_Event[] $registrations_for_free_events
776
-     * @return EE_Form_Section_Proper
777
-     * @throws EE_Error
778
-     */
779
-    private function _no_payment_required($registrations_for_free_events = [])
780
-    {
781
-        // set some defaults
782
-        $this->checkout->selected_method_of_payment = 'no_payment_required';
783
-        // generate no_payment_required form
784
-        return new EE_Form_Section_Proper(
785
-            [
786
-                'layout_strategy' => new EE_Template_Layout(
787
-                    [
788
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
789
-                                                  . $this->_slug
790
-                                                  . '/no_payment_required.template.php', // layout_template
791
-                        'template_args'        => apply_filters(
792
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
793
-                            [
794
-                                'revisit'                       => $this->checkout->revisit,
795
-                                'registrations'                 => [],
796
-                                'ticket_count'                  => [],
797
-                                'registrations_for_free_events' => $registrations_for_free_events,
798
-                                'no_payment_required_msg'       => EEH_HTML::p(
799
-                                    esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
800
-                                ),
801
-                            ]
802
-                        ),
803
-                    ]
804
-                ),
805
-            ]
806
-        );
807
-    }
808
-
809
-
810
-    /**
811
-     * _display_payment_options
812
-     *
813
-     * @param string $transaction_details
814
-     * @return EE_Form_Section_Proper
815
-     * @throws EE_Error
816
-     * @throws InvalidArgumentException
817
-     * @throws InvalidDataTypeException
818
-     * @throws InvalidInterfaceException
819
-     */
820
-    private function _display_payment_options($transaction_details = '')
821
-    {
822
-        // has method_of_payment been set by no-js user?
823
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
824
-        // build payment options form
825
-        return apply_filters(
826
-            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
827
-            new EE_Form_Section_Proper(
828
-                [
829
-                    'subsections'     => [
830
-                        'before_payment_options' => apply_filters(
831
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
832
-                            new EE_Form_Section_Proper(
833
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
834
-                            )
835
-                        ),
836
-                        'payment_options'        => $this->_setup_payment_options(),
837
-                        'after_payment_options'  => apply_filters(
838
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
839
-                            new EE_Form_Section_Proper(
840
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
841
-                            )
842
-                        ),
843
-                    ],
844
-                    'layout_strategy' => new EE_Template_Layout(
845
-                        [
846
-                            'layout_template_file' => $this->_template,
847
-                            'template_args'        => apply_filters(
848
-                                'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
849
-                                [
850
-                                    'reg_count'                 => $this->line_item_display->total_items(),
851
-                                    'transaction_details'       => $transaction_details,
852
-                                    'available_payment_methods' => [],
853
-                                ]
854
-                            ),
855
-                        ]
856
-                    ),
857
-                ]
858
-            )
859
-        );
860
-    }
861
-
862
-
863
-    /**
864
-     * _extra_hidden_inputs
865
-     *
866
-     * @param bool $no_payment_required
867
-     * @return EE_Form_Section_Proper
868
-     * @throws EE_Error
869
-     * @throws ReflectionException
870
-     */
871
-    private function _extra_hidden_inputs($no_payment_required = true)
872
-    {
873
-        return new EE_Form_Section_Proper(
874
-            [
875
-                'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
876
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
877
-                'subsections'     => [
878
-                    'spco_no_payment_required' => new EE_Hidden_Input(
879
-                        [
880
-                            'normalization_strategy' => new EE_Boolean_Normalization(),
881
-                            'html_name'              => 'spco_no_payment_required',
882
-                            'html_id'                => 'spco-no-payment-required-payment_options',
883
-                            'default'                => $no_payment_required,
884
-                        ]
885
-                    ),
886
-                    'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
887
-                        [
888
-                            'normalization_strategy' => new EE_Int_Normalization(),
889
-                            'html_name'              => 'spco_transaction_id',
890
-                            'html_id'                => 'spco-transaction-id',
891
-                            'default'                => $this->checkout->transaction->ID(),
892
-                        ]
893
-                    ),
894
-                ],
895
-            ]
896
-        );
897
-    }
898
-
899
-
900
-    /**
901
-     * @param array $registrations
902
-     * @throws EE_Error
903
-     * @throws ReflectionException
904
-     */
905
-    protected function _apply_registration_payments_to_amount_owing(array $registrations)
906
-    {
907
-        $payments = [];
908
-        foreach ($registrations as $registration) {
909
-            if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
910
-                $payments += $registration->registration_payments();
911
-            }
912
-        }
913
-        if (! empty($payments)) {
914
-            foreach ($payments as $payment) {
915
-                if ($payment instanceof EE_Registration_Payment) {
916
-                    $this->checkout->amount_owing -= $payment->amount();
917
-                }
918
-            }
919
-        }
920
-    }
921
-
922
-
923
-    /**
924
-     *    _reset_selected_method_of_payment
925
-     *
926
-     * @access    private
927
-     * @param bool $force_reset
928
-     * @return void
929
-     * @throws InvalidArgumentException
930
-     * @throws InvalidDataTypeException
931
-     * @throws InvalidInterfaceException
932
-     */
933
-    private function _reset_selected_method_of_payment($force_reset = false)
934
-    {
935
-        /** @var RequestInterface $request */
936
-        $request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
937
-        $reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
938
-        if ($reset_payment_method) {
939
-            $this->checkout->selected_method_of_payment = null;
940
-            $this->checkout->payment_method             = null;
941
-            $this->checkout->billing_form               = null;
942
-            $this->_save_selected_method_of_payment();
943
-        }
944
-    }
945
-
946
-
947
-    /**
948
-     * _save_selected_method_of_payment
949
-     * stores the selected_method_of_payment in the session
950
-     * so that it's available for all subsequent requests including AJAX
951
-     *
952
-     * @access        private
953
-     * @param string $selected_method_of_payment
954
-     * @return void
955
-     * @throws InvalidArgumentException
956
-     * @throws InvalidDataTypeException
957
-     * @throws InvalidInterfaceException
958
-     */
959
-    private function _save_selected_method_of_payment($selected_method_of_payment = '')
960
-    {
961
-        $selected_method_of_payment = ! empty($selected_method_of_payment)
962
-            ? $selected_method_of_payment
963
-            : $this->checkout->selected_method_of_payment;
964
-        EE_Registry::instance()->SSN->set_session_data(
965
-            ['selected_method_of_payment' => $selected_method_of_payment]
966
-        );
967
-    }
968
-
969
-
970
-    /**
971
-     * _setup_payment_options
972
-     *
973
-     * @return EE_Form_Section_Proper
974
-     * @throws EE_Error
975
-     * @throws InvalidArgumentException
976
-     * @throws InvalidDataTypeException
977
-     * @throws InvalidInterfaceException
978
-     */
979
-    public function _setup_payment_options()
980
-    {
981
-        // load payment method classes
982
-        $this->checkout->available_payment_methods = $this->_get_available_payment_methods();
983
-        if (empty($this->checkout->available_payment_methods)) {
984
-            EE_Error::add_error(
985
-                apply_filters(
986
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
987
-                    sprintf(
988
-                        esc_html__(
989
-                            'Sorry, you cannot complete your purchase because a payment method is not active.%1$s Please contact %2$s for assistance and provide a description of the problem.',
990
-                            'event_espresso'
991
-                        ),
992
-                        '<br>',
993
-                        EE_Registry::instance()->CFG->organization->get_pretty('email')
994
-                    )
995
-                ),
996
-                __FILE__,
997
-                __FUNCTION__,
998
-                __LINE__
999
-            );
1000
-        }
1001
-        // switch up header depending on number of available payment methods
1002
-        $payment_method_header     = count($this->checkout->available_payment_methods) > 1
1003
-            ? apply_filters(
1004
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
1005
-                esc_html__('Please Select Your Method of Payment', 'event_espresso')
1006
-            )
1007
-            : apply_filters(
1008
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
1009
-                esc_html__('Method of Payment', 'event_espresso')
1010
-            );
1011
-        $available_payment_methods = [
1012
-            // display the "Payment Method" header
1013
-            'payment_method_header' => new EE_Form_Section_HTML(
1014
-                apply_filters(
1015
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
1016
-                    EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
1017
-                    $payment_method_header
1018
-                )
1019
-            ),
1020
-        ];
1021
-        // the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
1022
-        $available_payment_method_options = [];
1023
-        $default_payment_method_option    = [];
1024
-        // additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
1025
-        $payment_methods_billing_info = [
1026
-            new EE_Form_Section_HTML(
1027
-                EEH_HTML::div('<br />', '', '', 'clear:both;')
1028
-            ),
1029
-        ];
1030
-        // loop through payment methods
1031
-        foreach ($this->checkout->available_payment_methods as $payment_method) {
1032
-            if ($payment_method instanceof EE_Payment_Method) {
1033
-                $payment_method_button = EEH_HTML::img(
1034
-                    $payment_method->button_url(),
1035
-                    $payment_method->name(),
1036
-                    'spco-payment-method-' . $payment_method->slug() . '-btn-img',
1037
-                    'spco-payment-method-btn-img'
1038
-                );
1039
-                // check if any payment methods are set as default
1040
-                // if payment method is already selected OR nothing is selected and this payment method should be
1041
-                // open_by_default
1042
-                if (
1043
-                    ($this->checkout->selected_method_of_payment === $payment_method->slug())
1044
-                    || (! $this->checkout->selected_method_of_payment && $payment_method->open_by_default())
1045
-                ) {
1046
-                    $this->checkout->selected_method_of_payment = $payment_method->slug();
1047
-                    $this->_save_selected_method_of_payment();
1048
-                    $default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1049
-                } else {
1050
-                    $available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1051
-                }
1052
-                $payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1053
-                    $this->_payment_method_billing_info(
1054
-                        $payment_method
1055
-                    );
1056
-            }
1057
-        }
1058
-        // prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1059
-        // of PMs
1060
-        $available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1061
-        // now generate the actual form  inputs
1062
-        $available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1063
-            $available_payment_method_options
1064
-        );
1065
-        $available_payment_methods                              += $payment_methods_billing_info;
1066
-        // build the available payment methods form
1067
-        return new EE_Form_Section_Proper(
1068
-            [
1069
-                'html_id'         => 'spco-available-methods-of-payment-dv',
1070
-                'subsections'     => $available_payment_methods,
1071
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1072
-            ]
1073
-        );
1074
-    }
1075
-
1076
-
1077
-    /**
1078
-     * _get_available_payment_methods
1079
-     *
1080
-     * @return EE_Payment_Method[]
1081
-     * @throws EE_Error
1082
-     * @throws InvalidArgumentException
1083
-     * @throws InvalidDataTypeException
1084
-     * @throws InvalidInterfaceException
1085
-     */
1086
-    protected function _get_available_payment_methods()
1087
-    {
1088
-        if (! empty($this->checkout->available_payment_methods)) {
1089
-            return $this->checkout->available_payment_methods;
1090
-        }
1091
-        $available_payment_methods = [];
1092
-        $EEM_Payment_Method        = EEM_Payment_Method::instance();
1093
-        // get all active payment methods
1094
-        $payment_methods = $EEM_Payment_Method->get_all_for_transaction(
1095
-            $this->checkout->transaction,
1096
-            EEM_Payment_Method::scope_cart
1097
-        );
1098
-        foreach ($payment_methods as $payment_method) {
1099
-            if ($payment_method instanceof EE_Payment_Method) {
1100
-                $available_payment_methods[ $payment_method->slug() ] = $payment_method;
1101
-            }
1102
-        }
1103
-        return $available_payment_methods;
1104
-    }
1105
-
1106
-
1107
-    /**
1108
-     *    _available_payment_method_inputs
1109
-     *
1110
-     * @access    private
1111
-     * @param array $available_payment_method_options
1112
-     * @return    EE_Form_Section_Proper
1113
-     * @throws EE_Error
1114
-     * @throws EE_Error
1115
-     */
1116
-    private function _available_payment_method_inputs($available_payment_method_options = [])
1117
-    {
1118
-        // generate inputs
1119
-        return new EE_Form_Section_Proper(
1120
-            [
1121
-                'html_id'         => 'ee-available-payment-method-inputs',
1122
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1123
-                'subsections'     => [
1124
-                    '' => new EE_Radio_Button_Input(
1125
-                        $available_payment_method_options,
1126
-                        [
1127
-                            'html_name'          => 'selected_method_of_payment',
1128
-                            'html_class'         => 'spco-payment-method',
1129
-                            'default'            => $this->checkout->selected_method_of_payment,
1130
-                            'label_size'         => 11,
1131
-                            'enforce_label_size' => true,
1132
-                        ]
1133
-                    ),
1134
-                ],
1135
-            ]
1136
-        );
1137
-    }
1138
-
1139
-
1140
-    /**
1141
-     *    _payment_method_billing_info
1142
-     *
1143
-     * @access    private
1144
-     * @param EE_Payment_Method $payment_method
1145
-     * @return EE_Form_Section_Proper
1146
-     * @throws EE_Error
1147
-     * @throws InvalidArgumentException
1148
-     * @throws InvalidDataTypeException
1149
-     * @throws InvalidInterfaceException
1150
-     */
1151
-    private function _payment_method_billing_info(EE_Payment_Method $payment_method)
1152
-    {
1153
-        $currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1154
-        // generate the billing form for payment method
1155
-        $billing_form                 = $currently_selected
1156
-            ? $this->_get_billing_form_for_payment_method($payment_method)
1157
-            : new EE_Form_Section_HTML();
1158
-        $this->checkout->billing_form = $currently_selected
1159
-            ? $billing_form
1160
-            : $this->checkout->billing_form;
1161
-        // it's all in the details
1162
-        $info_html = EEH_HTML::h3(
1163
-            esc_html__('Important information regarding your payment', 'event_espresso'),
1164
-            '',
1165
-            'spco-payment-method-hdr'
1166
-        );
1167
-        // add some info regarding the step, either from what's saved in the admin,
1168
-        // or a default string depending on whether the PM has a billing form or not
1169
-        if ($payment_method->description()) {
1170
-            $payment_method_info = $payment_method->description();
1171
-        } elseif ($billing_form instanceof EE_Billing_Info_Form) {
1172
-            $payment_method_info = sprintf(
1173
-                esc_html__(
1174
-                    'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1175
-                    'event_espresso'
1176
-                ),
1177
-                $this->submit_button_text()
1178
-            );
1179
-        } else {
1180
-            $payment_method_info = sprintf(
1181
-                esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1182
-                $this->submit_button_text()
1183
-            );
1184
-        }
1185
-        $info_html .= EEH_HTML::div(
1186
-            apply_filters(
1187
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1188
-                $payment_method_info
1189
-            ),
1190
-            '',
1191
-            'spco-payment-method-desc ee-attention'
1192
-        );
1193
-        return new EE_Form_Section_Proper(
1194
-            [
1195
-                'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1196
-                'html_class'      => 'spco-payment-method-info-dv',
1197
-                // only display the selected or default PM
1198
-                'html_style'      => $currently_selected ? '' : 'display:none;',
1199
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1200
-                'subsections'     => [
1201
-                    'info'         => new EE_Form_Section_HTML($info_html),
1202
-                    'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1203
-                ],
1204
-            ]
1205
-        );
1206
-    }
1207
-
1208
-
1209
-    /**
1210
-     * get_billing_form_html_for_payment_method
1211
-     *
1212
-     * @return bool
1213
-     * @throws EE_Error
1214
-     * @throws InvalidArgumentException
1215
-     * @throws ReflectionException
1216
-     * @throws InvalidDataTypeException
1217
-     * @throws InvalidInterfaceException
1218
-     */
1219
-    public function get_billing_form_html_for_payment_method()
1220
-    {
1221
-        // how have they chosen to pay?
1222
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1223
-        $this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1224
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1225
-            return false;
1226
-        }
1227
-        if (
1228
-            apply_filters(
1229
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1230
-                false
1231
-            )
1232
-        ) {
1233
-            EE_Error::add_success(
1234
-                apply_filters(
1235
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1236
-                    sprintf(
1237
-                        esc_html__(
1238
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1239
-                            'event_espresso'
1240
-                        ),
1241
-                        $this->checkout->payment_method->name()
1242
-                    )
1243
-                )
1244
-            );
1245
-        }
1246
-        // now generate billing form for selected method of payment
1247
-        $payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1248
-        // fill form with attendee info if applicable
1249
-        if (
1250
-            $payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1251
-            && $this->checkout->transaction_has_primary_registrant()
1252
-        ) {
1253
-            $payment_method_billing_form->populate_from_attendee(
1254
-                $this->checkout->transaction->primary_registration()->attendee()
1255
-            );
1256
-        }
1257
-        // and debug content
1258
-        if (
1259
-            $payment_method_billing_form instanceof EE_Billing_Info_Form
1260
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1261
-        ) {
1262
-            $payment_method_billing_form =
1263
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1264
-                    $payment_method_billing_form
1265
-                );
1266
-        }
1267
-        $billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1268
-            ? $payment_method_billing_form->get_html()
1269
-            : '';
1270
-        $this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1271
-        // localize validation rules for main form
1272
-        $this->checkout->current_step->reg_form->localize_validation_rules();
1273
-        $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1274
-        return true;
1275
-    }
1276
-
1277
-
1278
-    /**
1279
-     * _get_billing_form_for_payment_method
1280
-     *
1281
-     * @param EE_Payment_Method $payment_method
1282
-     * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1283
-     * @throws EE_Error
1284
-     * @throws InvalidArgumentException
1285
-     * @throws InvalidDataTypeException
1286
-     * @throws InvalidInterfaceException
1287
-     */
1288
-    private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1289
-    {
1290
-        $billing_form = $payment_method->type_obj()->billing_form(
1291
-            $this->checkout->transaction,
1292
-            ['amount_owing' => $this->checkout->amount_owing]
1293
-        );
1294
-        if ($billing_form instanceof EE_Billing_Info_Form) {
1295
-            if (
1296
-                apply_filters(
1297
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1298
-                    false
1299
-                )
1300
-                && $this->request->requestParamIsSet('payment_method')
1301
-            ) {
1302
-                EE_Error::add_success(
1303
-                    apply_filters(
1304
-                        'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1305
-                        sprintf(
1306
-                            esc_html__(
1307
-                                'You have selected "%s" as your method of payment. Please note the important payment information below.',
1308
-                                'event_espresso'
1309
-                            ),
1310
-                            $payment_method->name()
1311
-                        )
1312
-                    )
1313
-                );
1314
-            }
1315
-            return apply_filters(
1316
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1317
-                $billing_form,
1318
-                $payment_method
1319
-            );
1320
-        }
1321
-        // no actual billing form, so return empty HTML form section
1322
-        return new EE_Form_Section_HTML();
1323
-    }
1324
-
1325
-
1326
-    /**
1327
-     * _get_selected_method_of_payment
1328
-     *
1329
-     * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1330
-     *                          is not found in the incoming request
1331
-     * @param string  $request_param
1332
-     * @return NULL|string
1333
-     * @throws EE_Error
1334
-     * @throws InvalidArgumentException
1335
-     * @throws InvalidDataTypeException
1336
-     * @throws InvalidInterfaceException
1337
-     */
1338
-    private function _get_selected_method_of_payment(
1339
-        $required = false,
1340
-        $request_param = 'selected_method_of_payment'
1341
-    ) {
1342
-        // is selected_method_of_payment set in the request ?
1343
-        $selected_method_of_payment = $this->request->getRequestParam($request_param);
1344
-        if ($selected_method_of_payment) {
1345
-            // sanitize it
1346
-            $selected_method_of_payment = is_array($selected_method_of_payment)
1347
-                ? array_shift($selected_method_of_payment)
1348
-                : $selected_method_of_payment;
1349
-            $selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1350
-            // store it in the session so that it's available for all subsequent requests including AJAX
1351
-            $this->_save_selected_method_of_payment($selected_method_of_payment);
1352
-        } else {
1353
-            // or is it set in the session ?
1354
-            $selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1355
-                'selected_method_of_payment'
1356
-            );
1357
-        }
1358
-        if (empty($selected_method_of_payment) && $required) {
1359
-            EE_Error::add_error(
1360
-                sprintf(
1361
-                    esc_html__(
1362
-                        'The selected method of payment could not be determined.%sPlease ensure that you have selected one before proceeding.%sIf you continue to experience difficulties, then refresh your browser and try again, or contact %s for assistance.',
1363
-                        'event_espresso'
1364
-                    ),
1365
-                    '<br/>',
1366
-                    '<br/>',
1367
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
1368
-                ),
1369
-                __FILE__,
1370
-                __FUNCTION__,
1371
-                __LINE__
1372
-            );
1373
-            return null;
1374
-        }
1375
-        return $selected_method_of_payment;
1376
-    }
1377
-
1378
-
1379
-
1380
-
1381
-
1382
-
1383
-    /********************************************************************************************************/
1384
-    /***********************************  SWITCH PAYMENT METHOD  ************************************/
1385
-    /********************************************************************************************************/
1386
-    /**
1387
-     * switch_payment_method
1388
-     *
1389
-     * @return bool
1390
-     * @throws EE_Error
1391
-     * @throws InvalidArgumentException
1392
-     * @throws InvalidDataTypeException
1393
-     * @throws InvalidInterfaceException
1394
-     * @throws ReflectionException
1395
-     */
1396
-    public function switch_payment_method()
1397
-    {
1398
-        if (! $this->_verify_payment_method_is_set()) {
1399
-            return false;
1400
-        }
1401
-        if (
1402
-            apply_filters(
1403
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1404
-                false
1405
-            )
1406
-        ) {
1407
-            EE_Error::add_success(
1408
-                apply_filters(
1409
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1410
-                    sprintf(
1411
-                        esc_html__(
1412
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1413
-                            'event_espresso'
1414
-                        ),
1415
-                        $this->checkout->payment_method->name()
1416
-                    )
1417
-                )
1418
-            );
1419
-        }
1420
-        // generate billing form for selected method of payment if it hasn't been done already
1421
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1422
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1423
-                $this->checkout->payment_method
1424
-            );
1425
-        }
1426
-        // fill form with attendee info if applicable
1427
-        if (
1428
-            apply_filters(
1429
-                'FHEE__populate_billing_form_fields_from_attendee',
1430
-                (
1431
-                    $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1432
-                    && $this->checkout->transaction_has_primary_registrant()
1433
-                ),
1434
-                $this->checkout->billing_form,
1435
-                $this->checkout->transaction
1436
-            )
1437
-        ) {
1438
-            $this->checkout->billing_form->populate_from_attendee(
1439
-                $this->checkout->transaction->primary_registration()->attendee()
1440
-            );
1441
-        }
1442
-        // and debug content
1443
-        if (
1444
-            $this->checkout->billing_form instanceof EE_Billing_Info_Form
1445
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1446
-        ) {
1447
-            $this->checkout->billing_form =
1448
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1449
-                    $this->checkout->billing_form
1450
-                );
1451
-        }
1452
-        // get html and validation rules for form
1453
-        if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1454
-            $this->checkout->json_response->set_return_data(
1455
-                ['payment_method_info' => $this->checkout->billing_form->get_html()]
1456
-            );
1457
-            // localize validation rules for main form
1458
-            $this->checkout->billing_form->localize_validation_rules(true);
1459
-            $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1460
-        } else {
1461
-            $this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1462
-        }
1463
-        // prevents advancement to next step
1464
-        $this->checkout->continue_reg = false;
1465
-        return true;
1466
-    }
1467
-
1468
-
1469
-    /**
1470
-     * _verify_payment_method_is_set
1471
-     *
1472
-     * @return bool
1473
-     * @throws EE_Error
1474
-     * @throws InvalidArgumentException
1475
-     * @throws ReflectionException
1476
-     * @throws InvalidDataTypeException
1477
-     * @throws InvalidInterfaceException
1478
-     */
1479
-    protected function _verify_payment_method_is_set()
1480
-    {
1481
-        // generate billing form for selected method of payment if it hasn't been done already
1482
-        if (empty($this->checkout->selected_method_of_payment)) {
1483
-            // how have they chosen to pay?
1484
-            $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1485
-        } else {
1486
-            // choose your own adventure based on method_of_payment
1487
-            switch ($this->checkout->selected_method_of_payment) {
1488
-                case 'events_sold_out':
1489
-                    EE_Error::add_attention(
1490
-                        apply_filters(
1491
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1492
-                            esc_html__(
1493
-                                'It appears that the event you were about to make a payment for has sold out since this form first loaded. Please contact the event administrator if you believe this is an error.',
1494
-                                'event_espresso'
1495
-                            )
1496
-                        ),
1497
-                        __FILE__,
1498
-                        __FUNCTION__,
1499
-                        __LINE__
1500
-                    );
1501
-                    return false;
1502
-                case 'payments_closed':
1503
-                    EE_Error::add_attention(
1504
-                        apply_filters(
1505
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1506
-                            esc_html__(
1507
-                                'It appears that the event you were about to make a payment for is not accepting payments at this time. Please contact the event administrator if you believe this is an error.',
1508
-                                'event_espresso'
1509
-                            )
1510
-                        ),
1511
-                        __FILE__,
1512
-                        __FUNCTION__,
1513
-                        __LINE__
1514
-                    );
1515
-                    return false;
1516
-                case 'no_payment_required':
1517
-                    EE_Error::add_attention(
1518
-                        apply_filters(
1519
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1520
-                            esc_html__(
1521
-                                'It appears that the event you were about to make a payment for does not require payment. Please contact the event administrator if you believe this is an error.',
1522
-                                'event_espresso'
1523
-                            )
1524
-                        ),
1525
-                        __FILE__,
1526
-                        __FUNCTION__,
1527
-                        __LINE__
1528
-                    );
1529
-                    return false;
1530
-                default:
1531
-            }
1532
-        }
1533
-        // verify payment method
1534
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1535
-            // get payment method for selected method of payment
1536
-            $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1537
-        }
1538
-        return $this->checkout->payment_method instanceof EE_Payment_Method;
1539
-    }
1540
-
1541
-
1542
-
1543
-    /********************************************************************************************************/
1544
-    /***************************************  SAVE PAYER DETAILS  ****************************************/
1545
-    /********************************************************************************************************/
1546
-    /**
1547
-     * save_payer_details_via_ajax
1548
-     *
1549
-     * @return void
1550
-     * @throws EE_Error
1551
-     * @throws InvalidArgumentException
1552
-     * @throws ReflectionException
1553
-     * @throws RuntimeException
1554
-     * @throws InvalidDataTypeException
1555
-     * @throws InvalidInterfaceException
1556
-     */
1557
-    public function save_payer_details_via_ajax()
1558
-    {
1559
-        if (! $this->_verify_payment_method_is_set()) {
1560
-            return;
1561
-        }
1562
-        // generate billing form for selected method of payment if it hasn't been done already
1563
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1564
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1565
-                $this->checkout->payment_method
1566
-            );
1567
-        }
1568
-        // generate primary attendee from payer info if applicable
1569
-        if (! $this->checkout->transaction_has_primary_registrant()) {
1570
-            $attendee = $this->_create_attendee_from_request_data();
1571
-            if ($attendee instanceof EE_Attendee) {
1572
-                foreach ($this->checkout->transaction->registrations() as $registration) {
1573
-                    if ($registration->is_primary_registrant()) {
1574
-                        $this->checkout->primary_attendee_obj = $attendee;
1575
-                        $registration->_add_relation_to($attendee, 'Attendee');
1576
-                        $registration->set_attendee_id($attendee->ID());
1577
-                        $registration->update_cache_after_object_save('Attendee', $attendee);
1578
-                    }
1579
-                }
1580
-            }
1581
-        }
1582
-    }
1583
-
1584
-
1585
-    /**
1586
-     * create_attendee_from_request_data
1587
-     * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1588
-     *
1589
-     * @return EE_Attendee
1590
-     * @throws EE_Error
1591
-     * @throws InvalidArgumentException
1592
-     * @throws ReflectionException
1593
-     * @throws InvalidDataTypeException
1594
-     * @throws InvalidInterfaceException
1595
-     */
1596
-    protected function _create_attendee_from_request_data()
1597
-    {
1598
-        // get State ID
1599
-        $STA_ID = $this->request->getRequestParam('state');
1600
-        if (! empty($STA_ID)) {
1601
-            // can we get state object from name ?
1602
-            EE_Registry::instance()->load_model('State');
1603
-            $state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1604
-            $STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1605
-        }
1606
-        // get Country ISO
1607
-        $CNT_ISO = $this->request->getRequestParam('country');
1608
-        if (! empty($CNT_ISO)) {
1609
-            // can we get country object from name ?
1610
-            EE_Registry::instance()->load_model('Country');
1611
-            $country = EEM_Country::instance()->get_col(
1612
-                [['CNT_name' => $CNT_ISO], 'limit' => 1],
1613
-                'CNT_ISO'
1614
-            );
1615
-            $CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1616
-        }
1617
-        // grab attendee data
1618
-        $attendee_data = [
1619
-            'ATT_fname'    => $this->request->getRequestParam('first_name'),
1620
-            'ATT_lname'    => $this->request->getRequestParam('last_name'),
1621
-            'ATT_email'    => $this->request->getRequestParam('email'),
1622
-            'ATT_address'  => $this->request->getRequestParam('address'),
1623
-            'ATT_address2' => $this->request->getRequestParam('address2'),
1624
-            'ATT_city'     => $this->request->getRequestParam('city'),
1625
-            'STA_ID'       => $STA_ID,
1626
-            'CNT_ISO'      => $CNT_ISO,
1627
-            'ATT_zip'      => $this->request->getRequestParam('zip'),
1628
-            'ATT_phone'    => $this->request->getRequestParam('phone'),
1629
-        ];
1630
-        // validate the email address since it is the most important piece of info
1631
-        if (empty($attendee_data['ATT_email'])) {
1632
-            EE_Error::add_error(
1633
-                esc_html__('An invalid email address was submitted.', 'event_espresso'),
1634
-                __FILE__,
1635
-                __FUNCTION__,
1636
-                __LINE__
1637
-            );
1638
-        }
1639
-        // does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1640
-        // AND email address
1641
-        if (
1642
-            ! empty($attendee_data['ATT_fname'])
1643
-            && ! empty($attendee_data['ATT_lname'])
1644
-            && ! empty($attendee_data['ATT_email'])
1645
-        ) {
1646
-            $existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1647
-                [
1648
-                    'ATT_fname' => $attendee_data['ATT_fname'],
1649
-                    'ATT_lname' => $attendee_data['ATT_lname'],
1650
-                    'ATT_email' => $attendee_data['ATT_email'],
1651
-                ]
1652
-            );
1653
-            if ($existing_attendee instanceof EE_Attendee) {
1654
-                return $existing_attendee;
1655
-            }
1656
-        }
1657
-        // no existing attendee? kk let's create a new one
1658
-        // kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1659
-        // don't exist
1660
-        $attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1661
-            ? $attendee_data['ATT_fname']
1662
-            : $attendee_data['ATT_email'];
1663
-        $attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1664
-            ? $attendee_data['ATT_lname']
1665
-            : $attendee_data['ATT_email'];
1666
-        return EE_Attendee::new_instance($attendee_data);
1667
-    }
1668
-
1669
-
1670
-
1671
-    /********************************************************************************************************/
1672
-    /****************************************  PROCESS REG STEP  *****************************************/
1673
-    /********************************************************************************************************/
1674
-    /**
1675
-     * process_reg_step
1676
-     *
1677
-     * @return bool
1678
-     * @throws EE_Error
1679
-     * @throws InvalidArgumentException
1680
-     * @throws ReflectionException
1681
-     * @throws EntityNotFoundException
1682
-     * @throws InvalidDataTypeException
1683
-     * @throws InvalidInterfaceException
1684
-     * @throws InvalidStatusException
1685
-     */
1686
-    public function process_reg_step()
1687
-    {
1688
-        // how have they chosen to pay?
1689
-        $this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1690
-            ? 'no_payment_required'
1691
-            : $this->_get_selected_method_of_payment(true);
1692
-        // choose your own adventure based on method_of_payment
1693
-        switch ($this->checkout->selected_method_of_payment) {
1694
-            case 'events_sold_out':
1695
-                $this->checkout->redirect     = true;
1696
-                $this->checkout->redirect_url = $this->checkout->cancel_page_url;
1697
-                $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1698
-                // mark this reg step as completed
1699
-                $this->set_completed();
1700
-                return false;
1701
-
1702
-            case 'payments_closed':
1703
-                if (
1704
-                    apply_filters(
1705
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1706
-                        false
1707
-                    )
1708
-                ) {
1709
-                    EE_Error::add_success(
1710
-                        esc_html__('no payment required at this time.', 'event_espresso'),
1711
-                        __FILE__,
1712
-                        __FUNCTION__,
1713
-                        __LINE__
1714
-                    );
1715
-                }
1716
-                // mark this reg step as completed
1717
-                $this->set_completed();
1718
-                return true;
1719
-
1720
-            case 'no_payment_required':
1721
-                if (
1722
-                    apply_filters(
1723
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1724
-                        false
1725
-                    )
1726
-                ) {
1727
-                    EE_Error::add_success(
1728
-                        esc_html__('no payment required.', 'event_espresso'),
1729
-                        __FILE__,
1730
-                        __FUNCTION__,
1731
-                        __LINE__
1732
-                    );
1733
-                }
1734
-                // mark this reg step as completed
1735
-                $this->set_completed();
1736
-                return true;
1737
-
1738
-            default:
1739
-                $registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1740
-                    EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1741
-                );
1742
-                $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1743
-                    $registrations,
1744
-                    EE_Registry::instance()->SSN->checkout()->revisit
1745
-                );
1746
-                // calculate difference between the two arrays
1747
-                $registrations = array_diff($registrations, $ejected_registrations);
1748
-                if (empty($registrations)) {
1749
-                    $this->_redirect_because_event_sold_out();
1750
-                    return false;
1751
-                }
1752
-                $payment = $this->_process_payment();
1753
-                if ($payment instanceof EE_Payment) {
1754
-                    $this->checkout->continue_reg = true;
1755
-                    $this->_maybe_set_completed($payment);
1756
-                } else {
1757
-                    $this->checkout->continue_reg = false;
1758
-                }
1759
-                return $payment instanceof EE_Payment;
1760
-        }
1761
-    }
1762
-
1763
-
1764
-    /**
1765
-     * _redirect_because_event_sold_out
1766
-     *
1767
-     * @return void
1768
-     */
1769
-    protected function _redirect_because_event_sold_out()
1770
-    {
1771
-        $this->checkout->continue_reg = false;
1772
-        // set redirect URL
1773
-        $this->checkout->redirect_url = add_query_arg(
1774
-            ['e_reg_url_link' => $this->checkout->reg_url_link],
1775
-            $this->checkout->current_step->reg_step_url()
1776
-        );
1777
-        $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1778
-    }
1779
-
1780
-
1781
-    /**
1782
-     * @param EE_Payment $payment
1783
-     * @return void
1784
-     * @throws EE_Error
1785
-     */
1786
-    protected function _maybe_set_completed(EE_Payment $payment)
1787
-    {
1788
-        // Do we need to redirect them? If so, there's more work to be done.
1789
-        if (! $payment->redirect_url()) {
1790
-            $this->set_completed();
1791
-        }
1792
-    }
1793
-
1794
-
1795
-    /**
1796
-     *    update_reg_step
1797
-     *    this is the final step after a user  revisits the site to retry a payment
1798
-     *
1799
-     * @return bool
1800
-     * @throws EE_Error
1801
-     * @throws InvalidArgumentException
1802
-     * @throws ReflectionException
1803
-     * @throws EntityNotFoundException
1804
-     * @throws InvalidDataTypeException
1805
-     * @throws InvalidInterfaceException
1806
-     * @throws InvalidStatusException
1807
-     */
1808
-    public function update_reg_step()
1809
-    {
1810
-        $success = true;
1811
-        // if payment required
1812
-        if ($this->checkout->transaction->total() > 0) {
1813
-            do_action(
1814
-                'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1815
-                $this->checkout->transaction
1816
-            );
1817
-            // attempt payment via payment method
1818
-            $success = $this->process_reg_step();
1819
-        }
1820
-        if ($success && ! $this->checkout->redirect) {
1821
-            $this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1822
-                $this->checkout->transaction->ID()
1823
-            );
1824
-            // set return URL
1825
-            $this->checkout->redirect_url = add_query_arg(
1826
-                ['e_reg_url_link' => $this->checkout->reg_url_link],
1827
-                $this->checkout->thank_you_page_url
1828
-            );
1829
-        }
1830
-        return $success;
1831
-    }
1832
-
1833
-
1834
-    /**
1835
-     * @return EE_Payment|null
1836
-     * @throws EE_Error
1837
-     * @throws InvalidArgumentException
1838
-     * @throws ReflectionException
1839
-     * @throws RuntimeException
1840
-     * @throws InvalidDataTypeException
1841
-     * @throws InvalidInterfaceException
1842
-     */
1843
-    private function _process_payment()
1844
-    {
1845
-        // basically confirm that the event hasn't sold out since they hit the page
1846
-        if (! $this->_last_second_ticket_verifications()) {
1847
-            return null;
1848
-        }
1849
-        // ya gotta make a choice man
1850
-        if (empty($this->checkout->selected_method_of_payment)) {
1851
-            $this->checkout->json_response->set_plz_select_method_of_payment(
1852
-                esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1853
-            );
1854
-            return null;
1855
-        }
1856
-        // get EE_Payment_Method object
1857
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1858
-            return null;
1859
-        }
1860
-        // setup billing form
1861
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1862
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1863
-                $this->checkout->payment_method
1864
-            );
1865
-            // bad billing form ?
1866
-            if (! $this->_billing_form_is_valid()) {
1867
-                return null;
1868
-            }
1869
-        }
1870
-        // ensure primary registrant has been fully processed
1871
-        if (! $this->_setup_primary_registrant_prior_to_payment()) {
1872
-            return null;
1873
-        }
1874
-        // if session is close to expiring (under 10 minutes by default)
1875
-        if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1876
-            // add some time to session expiration so that payment can be completed
1877
-            EE_Registry::instance()->SSN->extend_expiration();
1878
-        }
1879
-        /** @type EE_Transaction_Processor $transaction_processor */
1880
-        // $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1881
-        // in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1882
-        // for events with a default reg status of Approved
1883
-        // $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1884
-        //      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1885
-        // );
1886
-        // attempt payment
1887
-        $payment = $this->_attempt_payment($this->checkout->payment_method);
1888
-        // process results
1889
-        $payment = $this->_validate_payment($payment);
1890
-        $payment = $this->_post_payment_processing($payment);
1891
-        // verify payment
1892
-        if ($payment instanceof EE_Payment) {
1893
-            // store that for later
1894
-            $this->checkout->payment = $payment;
1895
-            // we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1896
-            $this->checkout->transaction->toggle_failed_transaction_status();
1897
-            $payment_status = $payment->status();
1898
-            if (
1899
-                $payment_status === EEM_Payment::status_id_approved
1900
-                || $payment_status === EEM_Payment::status_id_pending
1901
-            ) {
1902
-                return $payment;
1903
-            }
1904
-            return null;
1905
-        }
1906
-        if ($payment === true) {
1907
-            // please note that offline payment methods will NOT make a payment,
1908
-            // but instead just mark themselves as the PMD_ID on the transaction, and return true
1909
-            $this->checkout->payment = $payment;
1910
-            return $payment;
1911
-        }
1912
-        // where's my money?
1913
-        return null;
1914
-    }
1915
-
1916
-
1917
-    /**
1918
-     * _last_second_ticket_verifications
1919
-     *
1920
-     * @return bool
1921
-     * @throws EE_Error
1922
-     * @throws ReflectionException
1923
-     */
1924
-    protected function _last_second_ticket_verifications()
1925
-    {
1926
-        // don't bother re-validating if not a return visit
1927
-        if (! $this->checkout->revisit) {
1928
-            return true;
1929
-        }
1930
-        $registrations = $this->checkout->transaction->registrations();
1931
-        if (empty($registrations)) {
1932
-            return false;
1933
-        }
1934
-        foreach ($registrations as $registration) {
1935
-            if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1936
-                $event = $registration->event_obj();
1937
-                if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1938
-                    EE_Error::add_error(
1939
-                        apply_filters(
1940
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1941
-                            sprintf(
1942
-                                esc_html__(
1943
-                                    'It appears that the %1$s event that you were about to make a payment for has sold out since you first registered and/or arrived at this page. Please refresh the page and try again. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
1944
-                                    'event_espresso'
1945
-                                ),
1946
-                                $event->name()
1947
-                            )
1948
-                        ),
1949
-                        __FILE__,
1950
-                        __FUNCTION__,
1951
-                        __LINE__
1952
-                    );
1953
-                    return false;
1954
-                }
1955
-            }
1956
-        }
1957
-        return true;
1958
-    }
1959
-
1960
-
1961
-    /**
1962
-     * redirect_form
1963
-     *
1964
-     * @return bool
1965
-     * @throws EE_Error
1966
-     * @throws InvalidArgumentException
1967
-     * @throws ReflectionException
1968
-     * @throws InvalidDataTypeException
1969
-     * @throws InvalidInterfaceException
1970
-     */
1971
-    public function redirect_form()
1972
-    {
1973
-        $payment_method_billing_info = $this->_payment_method_billing_info(
1974
-            $this->_get_payment_method_for_selected_method_of_payment()
1975
-        );
1976
-        $html                        = $payment_method_billing_info->get_html();
1977
-        $html                        .= $this->checkout->redirect_form;
1978
-        /** @var ResponseInterface $response */
1979
-        $response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1980
-        $response->addOutput($html);
1981
-        return true;
1982
-    }
1983
-
1984
-
1985
-    /**
1986
-     * _billing_form_is_valid
1987
-     *
1988
-     * @return bool
1989
-     * @throws EE_Error
1990
-     */
1991
-    private function _billing_form_is_valid()
1992
-    {
1993
-        if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1994
-            return true;
1995
-        }
1996
-        if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1997
-            if ($this->checkout->billing_form->was_submitted()) {
1998
-                $this->checkout->billing_form->receive_form_submission();
1999
-                if ($this->checkout->billing_form->is_valid()) {
2000
-                    return true;
2001
-                }
2002
-                $validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
2003
-                $error_strings     = [];
2004
-                foreach ($validation_errors as $validation_error) {
2005
-                    if ($validation_error instanceof EE_Validation_Error) {
2006
-                        $form_section = $validation_error->get_form_section();
2007
-                        if ($form_section instanceof EE_Form_Input_Base) {
2008
-                            $label = $form_section->html_label_text();
2009
-                        } elseif ($form_section instanceof EE_Form_Section_Base) {
2010
-                            $label = $form_section->name();
2011
-                        } else {
2012
-                            $label = esc_html__('Validation Error', 'event_espresso');
2013
-                        }
2014
-                        $error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
2015
-                    }
2016
-                }
2017
-                EE_Error::add_error(
2018
-                    sprintf(
2019
-                        esc_html__(
2020
-                            'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
2021
-                            'event_espresso'
2022
-                        ),
2023
-                        '<br/>',
2024
-                        implode('<br/>', $error_strings)
2025
-                    ),
2026
-                    __FILE__,
2027
-                    __FUNCTION__,
2028
-                    __LINE__
2029
-                );
2030
-            } else {
2031
-                EE_Error::add_error(
2032
-                    esc_html__(
2033
-                        'The billing form was not submitted or something prevented it\'s submission.',
2034
-                        'event_espresso'
2035
-                    ),
2036
-                    __FILE__,
2037
-                    __FUNCTION__,
2038
-                    __LINE__
2039
-                );
2040
-            }
2041
-        } else {
2042
-            EE_Error::add_error(
2043
-                esc_html__(
2044
-                    'The submitted billing form is invalid possibly due to a technical reason.',
2045
-                    'event_espresso'
2046
-                ),
2047
-                __FILE__,
2048
-                __FUNCTION__,
2049
-                __LINE__
2050
-            );
2051
-        }
2052
-        return false;
2053
-    }
2054
-
2055
-
2056
-    /**
2057
-     * _setup_primary_registrant_prior_to_payment
2058
-     * ensures that the primary registrant has a valid attendee object created with the critical details populated
2059
-     * (first & last name & email) and that both the transaction object and primary registration object have been saved
2060
-     * plz note that any other registrations will NOT be saved at this point (because they may not have any details
2061
-     * yet)
2062
-     *
2063
-     * @return bool
2064
-     * @throws EE_Error
2065
-     * @throws InvalidArgumentException
2066
-     * @throws ReflectionException
2067
-     * @throws RuntimeException
2068
-     * @throws InvalidDataTypeException
2069
-     * @throws InvalidInterfaceException
2070
-     */
2071
-    private function _setup_primary_registrant_prior_to_payment()
2072
-    {
2073
-        // check if transaction has a primary registrant and that it has a related Attendee object
2074
-        // if not, then we need to at least gather some primary registrant data before attempting payment
2075
-        if (
2076
-            $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
2077
-            && ! $this->checkout->transaction_has_primary_registrant()
2078
-            && ! $this->_capture_primary_registration_data_from_billing_form()
2079
-        ) {
2080
-            return false;
2081
-        }
2082
-        // because saving an object clears its cache, we need to do the Chevy Shuffle
2083
-        // grab the primary_registration object
2084
-        $primary_registration = $this->checkout->transaction->primary_registration();
2085
-        // at this point we'll consider a TXN to not have been failed
2086
-        $this->checkout->transaction->toggle_failed_transaction_status();
2087
-        // save the TXN ( which clears cached copy of primary_registration)
2088
-        $this->checkout->transaction->save();
2089
-        // grab TXN ID and save it to the primary_registration
2090
-        $primary_registration->set_transaction_id($this->checkout->transaction->ID());
2091
-        // save what we have so far
2092
-        $primary_registration->save();
2093
-        return true;
2094
-    }
2095
-
2096
-
2097
-    /**
2098
-     * Captures primary registration data from the billing form.
2099
-     *
2100
-     * This method is used to gather the primary registrant data before attempting payment.
2101
-     * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2102
-     * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2103
-     *
2104
-     * @return bool
2105
-     * @throws EE_Error
2106
-     * @throws InvalidArgumentException
2107
-     * @throws ReflectionException
2108
-     * @throws InvalidDataTypeException
2109
-     * @throws InvalidInterfaceException
2110
-     */
2111
-    private function _capture_primary_registration_data_from_billing_form(): bool
2112
-    {
2113
-        $primary_registration = $this->checkout->transaction->primary_registration();
2114
-        if (! $this->validatePrimaryRegistration($primary_registration)) {
2115
-            return false;
2116
-        }
2117
-
2118
-        $primary_attendee = $this->getPrimaryAttendee($primary_registration);
2119
-        if (! $this->validatePrimaryAttendee($primary_attendee)) {
2120
-            return false;
2121
-        }
2122
-
2123
-        if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2124
-            return false;
2125
-        }
2126
-        // both the primary registration and primary attendee objects should be valid entities at this point
2127
-        $this->checkout->primary_attendee_obj = $primary_attendee;
2128
-
2129
-        /** @type EE_Registration_Processor $registration_processor */
2130
-        $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2131
-        // at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2132
-        $registration_processor->toggle_incomplete_registration_status_to_default(
2133
-            $primary_registration,
2134
-            false,
2135
-            new Context(
2136
-                __METHOD__,
2137
-                esc_html__(
2138
-                    'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2139
-                    'event_espresso'
2140
-                )
2141
-            )
2142
-        );
2143
-        return true;
2144
-    }
2145
-
2146
-
2147
-    /**
2148
-     * returns true if the primary registration is a valid entity
2149
-     *
2150
-     * @param $primary_registration
2151
-     * @return bool
2152
-     * @throws EE_Error
2153
-     * @since 5.0.21.p
2154
-     */
2155
-    private function validatePrimaryRegistration($primary_registration): bool
2156
-    {
2157
-        if ($primary_registration instanceof EE_Registration) {
2158
-            return true;
2159
-        }
2160
-        EE_Error::add_error(
2161
-            sprintf(
2162
-                esc_html__(
2163
-                    'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2164
-                    'event_espresso'
2165
-                ),
2166
-                '<br/>',
2167
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2168
-            ),
2169
-            __FILE__,
2170
-            __FUNCTION__,
2171
-            __LINE__
2172
-        );
2173
-        return false;
2174
-    }
2175
-
2176
-
2177
-    /**
2178
-     * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2179
-     * if the primary registration does not have an attendee object, then one is created from the billing form info
2180
-     *
2181
-     * @param EE_Registration $primary_registration
2182
-     * @return EE_Attendee|null
2183
-     * @throws EE_Error
2184
-     * @throws ReflectionException
2185
-     * @since 5.0.21.p
2186
-     */
2187
-    private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2188
-    {
2189
-        // if we have a primary registration, then we should have a primary attendee
2190
-        $attendee = $primary_registration->attendee();
2191
-        if ($attendee instanceof EE_Attendee) {
2192
-            return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2193
-        }
2194
-        // if not, then we need to create one from the billing form
2195
-        return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2196
-    }
2197
-
2198
-
2199
-    /**
2200
-     * returns true if the primary attendee is a valid entity
2201
-     *
2202
-     * @param $primary_attendee
2203
-     * @return bool
2204
-     * @throws EE_Error
2205
-     * @since 5.0.21.p
2206
-     */
2207
-    private function validatePrimaryAttendee($primary_attendee): bool
2208
-    {
2209
-        if ($primary_attendee instanceof EE_Attendee) {
2210
-            return true;
2211
-        }
2212
-        EE_Error::add_error(
2213
-            sprintf(
2214
-                esc_html__(
2215
-                    'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2216
-                    'event_espresso'
2217
-                ),
2218
-                '<br/>',
2219
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2220
-            ),
2221
-            __FILE__,
2222
-            __FUNCTION__,
2223
-            __LINE__
2224
-        );
2225
-        return false;
2226
-    }
2227
-
2228
-
2229
-    /**
2230
-     * returns true if the attendee was successfully added to the primary registration
2231
-     *
2232
-     * @param EE_Attendee     $primary_attendee
2233
-     * @param EE_Registration $primary_registration
2234
-     * @return bool
2235
-     * @throws EE_Error
2236
-     * @throws ReflectionException
2237
-     * @since 5.0.21.p
2238
-     */
2239
-    private function addAttendeeToPrimaryRegistration(
2240
-        EE_Attendee $primary_attendee,
2241
-        EE_Registration $primary_registration
2242
-    ): bool {
2243
-        // ensure attendee has an ID by saving
2244
-        $primary_attendee->save();
2245
-
2246
-        // compare attendee IDs
2247
-        if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2248
-            return true;
2249
-        }
2250
-
2251
-        $primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2252
-        if ($primary_attendee instanceof EE_Attendee) {
2253
-            return true;
2254
-        }
2255
-
2256
-        EE_Error::add_error(
2257
-            sprintf(
2258
-                esc_html__(
2259
-                    'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2260
-                    'event_espresso'
2261
-                ),
2262
-                '<br/>',
2263
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2264
-            ),
2265
-            __FILE__,
2266
-            __FUNCTION__,
2267
-            __LINE__
2268
-        );
2269
-        return false;
2270
-    }
2271
-
2272
-
2273
-    /**
2274
-     * _get_payment_method_for_selected_method_of_payment
2275
-     * retrieves a valid payment method
2276
-     *
2277
-     * @return EE_Payment_Method
2278
-     * @throws EE_Error
2279
-     * @throws InvalidArgumentException
2280
-     * @throws ReflectionException
2281
-     * @throws InvalidDataTypeException
2282
-     * @throws InvalidInterfaceException
2283
-     */
2284
-    private function _get_payment_method_for_selected_method_of_payment()
2285
-    {
2286
-        if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2287
-            $this->_redirect_because_event_sold_out();
2288
-            return null;
2289
-        }
2290
-        // get EE_Payment_Method object
2291
-        if (isset($this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ])) {
2292
-            $payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ];
2293
-        } else {
2294
-            // load EEM_Payment_Method
2295
-            EE_Registry::instance()->load_model('Payment_Method');
2296
-            $EEM_Payment_Method = EEM_Payment_Method::instance();
2297
-            $payment_method     = $EEM_Payment_Method->get_one_by_slug($this->checkout->selected_method_of_payment);
2298
-        }
2299
-        // verify $payment_method
2300
-        if (! $payment_method instanceof EE_Payment_Method) {
2301
-            // not a payment
2302
-            EE_Error::add_error(
2303
-                sprintf(
2304
-                    esc_html__(
2305
-                        'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2306
-                        'event_espresso'
2307
-                    ),
2308
-                    '<br/>',
2309
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2310
-                ),
2311
-                __FILE__,
2312
-                __FUNCTION__,
2313
-                __LINE__
2314
-            );
2315
-            return null;
2316
-        }
2317
-        // and verify it has a valid Payment_Method Type object
2318
-        if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2319
-            // not a payment
2320
-            EE_Error::add_error(
2321
-                sprintf(
2322
-                    esc_html__(
2323
-                        'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2324
-                        'event_espresso'
2325
-                    ),
2326
-                    '<br/>',
2327
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2328
-                ),
2329
-                __FILE__,
2330
-                __FUNCTION__,
2331
-                __LINE__
2332
-            );
2333
-            return null;
2334
-        }
2335
-        return $payment_method;
2336
-    }
2337
-
2338
-
2339
-    /**
2340
-     *    _attempt_payment
2341
-     *
2342
-     * @access    private
2343
-     * @type    EE_Payment_Method $payment_method
2344
-     * @return EE_Payment|null
2345
-     * @throws EE_Error
2346
-     * @throws InvalidArgumentException
2347
-     * @throws ReflectionException
2348
-     * @throws InvalidDataTypeException
2349
-     * @throws InvalidInterfaceException
2350
-     */
2351
-    private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2352
-    {
2353
-        $this->checkout->transaction->save();
2354
-        /** @var PaymentProcessor $payment_processor */
2355
-        $payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2356
-        if (! $payment_processor instanceof PaymentProcessor) {
2357
-            return null;
2358
-        }
2359
-        /** @var EE_Transaction_Processor $transaction_processor */
2360
-        $transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2361
-        if ($transaction_processor instanceof EE_Transaction_Processor) {
2362
-            $transaction_processor->set_revisit($this->checkout->revisit);
2363
-        }
2364
-        try {
2365
-            // generate payment object
2366
-            return $payment_processor->processPayment(
2367
-                $payment_method,
2368
-                $this->checkout->transaction,
2369
-                $this->checkout->billing_form instanceof EE_Billing_Info_Form
2370
-                    ? $this->checkout->billing_form
2371
-                    : null,
2372
-                $this->checkout->amount_owing,
2373
-                $this->checkout->admin_request,
2374
-                true,
2375
-                $this->_get_return_url($payment_method),
2376
-                $this->reg_step_url()
2377
-            );
2378
-        } catch (Exception $e) {
2379
-            $this->_handle_payment_processor_exception($e);
2380
-        }
2381
-        return null;
2382
-    }
2383
-
2384
-
2385
-    /**
2386
-     * _handle_payment_processor_exception
2387
-     *
2388
-     * @param Exception $e
2389
-     * @return void
2390
-     * @throws EE_Error
2391
-     * @throws InvalidArgumentException
2392
-     * @throws InvalidDataTypeException
2393
-     * @throws InvalidInterfaceException
2394
-     */
2395
-    protected function _handle_payment_processor_exception(Exception $e)
2396
-    {
2397
-        EE_Error::add_error(
2398
-            sprintf(
2399
-                esc_html__(
2400
-                    'The payment could not br processed due to a technical issue.%1$sPlease try again or contact %2$s for assistance.||The following Exception was thrown in %4$s on line %5$s:%1$s%3$s',
2401
-                    'event_espresso'
2402
-                ),
2403
-                '<br/>',
2404
-                EE_Registry::instance()->CFG->organization->get_pretty('email'),
2405
-                $e->getMessage(),
2406
-                $e->getFile(),
2407
-                $e->getLine()
2408
-            ),
2409
-            __FILE__,
2410
-            __FUNCTION__,
2411
-            __LINE__
2412
-        );
2413
-    }
2414
-
2415
-
2416
-    /**
2417
-     * @param EE_Payment_Method $payment_method
2418
-     * @return string
2419
-     * @throws EE_Error
2420
-     * @throws ReflectionException
2421
-     */
2422
-    protected function _get_return_url(EE_Payment_Method $payment_method)
2423
-    {
2424
-        switch ($payment_method->type_obj()->payment_occurs()) {
2425
-            case EE_PMT_Base::offsite:
2426
-                return add_query_arg(
2427
-                    [
2428
-                        'action'                     => 'process_gateway_response',
2429
-                        'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2430
-                        'spco_txn'                   => $this->checkout->transaction->ID(),
2431
-                    ],
2432
-                    $this->reg_step_url()
2433
-                );
2434
-
2435
-            case EE_PMT_Base::onsite:
2436
-            case EE_PMT_Base::offline:
2437
-                return $this->checkout->next_step->reg_step_url();
2438
-        }
2439
-        return '';
2440
-    }
2441
-
2442
-
2443
-    /**
2444
-     * _validate_payment
2445
-     *
2446
-     * @param EE_Payment $payment
2447
-     * @return EE_Payment|bool
2448
-     * @throws EE_Error
2449
-     * @throws InvalidArgumentException
2450
-     * @throws InvalidDataTypeException
2451
-     * @throws InvalidInterfaceException
2452
-     */
2453
-    private function _validate_payment($payment = null)
2454
-    {
2455
-        if ($this->checkout->payment_method->is_off_line()) {
2456
-            return true;
2457
-        }
2458
-        // verify payment object
2459
-        if (! $payment instanceof EE_Payment) {
2460
-            // not a payment
2461
-            EE_Error::add_error(
2462
-                sprintf(
2463
-                    esc_html__(
2464
-                        'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2465
-                        'event_espresso'
2466
-                    ),
2467
-                    '<br/>',
2468
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2469
-                ),
2470
-                __FILE__,
2471
-                __FUNCTION__,
2472
-                __LINE__
2473
-            );
2474
-            return false;
2475
-        }
2476
-        return $payment;
2477
-    }
2478
-
2479
-
2480
-    /**
2481
-     * _post_payment_processing
2482
-     *
2483
-     * @param EE_Payment|bool $payment
2484
-     * @return bool|EE_Payment
2485
-     * @throws EE_Error
2486
-     * @throws InvalidArgumentException
2487
-     * @throws InvalidDataTypeException
2488
-     * @throws InvalidInterfaceException
2489
-     * @throws ReflectionException
2490
-     */
2491
-    private function _post_payment_processing($payment = null)
2492
-    {
2493
-        // Off-Line payment?
2494
-        if ($payment === true) {
2495
-            return true;
2496
-        }
2497
-        if ($payment instanceof EE_Payment) {
2498
-            // Should the user be redirected?
2499
-            if ($payment->redirect_url()) {
2500
-                do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2501
-                $this->checkout->redirect      = true;
2502
-                $this->checkout->redirect_form = $payment->redirect_form();
2503
-                $this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2504
-                // set JSON response
2505
-                $this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2506
-                // and lastly, let's bump the payment status to pending
2507
-                $payment->set_status(EEM_Payment::status_id_pending);
2508
-                $payment->save();
2509
-            } elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2510
-                // User shouldn't be redirected. So let's process it here.
2511
-                // $this->_setup_redirect_for_next_step();
2512
-                $this->checkout->continue_reg = false;
2513
-            }
2514
-            return $payment;
2515
-        }
2516
-        // ummm ya... not Off-Line, not On-Site, not off-Site ????
2517
-        $this->checkout->continue_reg = false;
2518
-        return false;
2519
-    }
2520
-
2521
-
2522
-    /**
2523
-     *    _process_payment_status
2524
-     *
2525
-     * @type    EE_Payment $payment
2526
-     * @param string       $payment_occurs
2527
-     * @return bool
2528
-     * @throws EE_Error
2529
-     * @throws InvalidArgumentException
2530
-     * @throws InvalidDataTypeException
2531
-     * @throws InvalidInterfaceException
2532
-     */
2533
-    private function _process_payment_status($payment, $payment_occurs = EE_PMT_Base::offline)
2534
-    {
2535
-        // off-line payment? carry on
2536
-        if ($payment_occurs === EE_PMT_Base::offline) {
2537
-            return true;
2538
-        }
2539
-        // verify payment validity
2540
-        if ($payment instanceof EE_Payment) {
2541
-            do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2542
-            $msg = $payment->gateway_response();
2543
-            // check results
2544
-            switch ($payment->status()) {
2545
-                // good payment
2546
-                case EEM_Payment::status_id_approved:
2547
-                    EE_Error::add_success(
2548
-                        esc_html__('Your payment was processed successfully.', 'event_espresso'),
2549
-                        __FILE__,
2550
-                        __FUNCTION__,
2551
-                        __LINE__
2552
-                    );
2553
-                    return true;
2554
-                // slow payment
2555
-                case EEM_Payment::status_id_pending:
2556
-                    if (empty($msg)) {
2557
-                        $msg = esc_html__(
2558
-                            'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2559
-                            'event_espresso'
2560
-                        );
2561
-                    }
2562
-                    EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2563
-                    return true;
2564
-                // don't wanna payment
2565
-                case EEM_Payment::status_id_cancelled:
2566
-                    if (empty($msg)) {
2567
-                        $msg = _n(
2568
-                            'Payment cancelled. Please try again.',
2569
-                            'Payment cancelled. Please try again or select another method of payment.',
2570
-                            count($this->checkout->available_payment_methods),
2571
-                            'event_espresso'
2572
-                        );
2573
-                    }
2574
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2575
-                    return false;
2576
-                // not enough payment
2577
-                case EEM_Payment::status_id_declined:
2578
-                    if (empty($msg)) {
2579
-                        $msg = _n(
2580
-                            'We\'re sorry but your payment was declined. Please try again.',
2581
-                            'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2582
-                            count($this->checkout->available_payment_methods),
2583
-                            'event_espresso'
2584
-                        );
2585
-                    }
2586
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2587
-                    return false;
2588
-                // bad payment
2589
-                case EEM_Payment::status_id_failed:
2590
-                    if (! empty($msg)) {
2591
-                        EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2592
-                        return false;
2593
-                    }
2594
-                    // default to error below
2595
-                    break;
2596
-            }
2597
-        }
2598
-        // off-site payment gateway responses are too unreliable, so let's just assume that
2599
-        // the payment processing is just running slower than the registrant's request
2600
-        if ($payment_occurs === EE_PMT_Base::offsite) {
2601
-            return true;
2602
-        }
2603
-        EE_Error::add_error(
2604
-            sprintf(
2605
-                esc_html__(
2606
-                    'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2607
-                    'event_espresso'
2608
-                ),
2609
-                '<br/>',
2610
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2611
-            ),
2612
-            __FILE__,
2613
-            __FUNCTION__,
2614
-            __LINE__
2615
-        );
2616
-        return false;
2617
-    }
2618
-
2619
-
2620
-
2621
-
2622
-
2623
-
2624
-    /********************************************************************************************************/
2625
-    /**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2626
-    /********************************************************************************************************/
2627
-    /**
2628
-     * process_gateway_response
2629
-     * this is the return point for Off-Site Payment Methods
2630
-     * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2631
-     * otherwise, it will load up the last payment made for the TXN.
2632
-     * If the payment retrieved looks good, it will then either:
2633
-     *    complete the current step and allow advancement to the next reg step
2634
-     *        or present the payment options again
2635
-     *
2636
-     * @return bool
2637
-     * @throws EE_Error
2638
-     * @throws InvalidArgumentException
2639
-     * @throws ReflectionException
2640
-     * @throws InvalidDataTypeException
2641
-     * @throws InvalidInterfaceException
2642
-     */
2643
-    public function process_gateway_response()
2644
-    {
2645
-        // how have they chosen to pay?
2646
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2647
-        // get EE_Payment_Method object
2648
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2649
-            $this->checkout->continue_reg = false;
2650
-            return false;
2651
-        }
2652
-        if (! $this->checkout->payment_method->is_off_site()) {
2653
-            return false;
2654
-        }
2655
-        $this->_validate_offsite_return();
2656
-        // verify TXN
2657
-        if ($this->checkout->transaction instanceof EE_Transaction) {
2658
-            $gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2659
-            if (! $gateway instanceof EE_Offsite_Gateway) {
2660
-                $this->checkout->continue_reg = false;
2661
-                return false;
2662
-            }
2663
-            $payment = $this->_process_off_site_payment($gateway);
2664
-            $payment = $this->_process_cancelled_payments($payment);
2665
-            $payment = $this->_validate_payment($payment);
2666
-            // if payment was not declined by the payment gateway or cancelled by the registrant
2667
-            if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2668
-                // $this->_setup_redirect_for_next_step();
2669
-                // store that for later
2670
-                $this->checkout->payment = $payment;
2671
-                // mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2672
-                // because we will complete this step during the IPN processing then
2673
-                if (! $this->handle_IPN_in_this_request()) {
2674
-                    $this->set_completed();
2675
-                }
2676
-                return true;
2677
-            }
2678
-        }
2679
-        // DEBUG LOG
2680
-        // $this->checkout->log(
2681
-        //     __CLASS__,
2682
-        //     __FUNCTION__,
2683
-        //     __LINE__,
2684
-        //     array('payment' => $payment)
2685
-        // );
2686
-        $this->checkout->continue_reg = false;
2687
-        return false;
2688
-    }
2689
-
2690
-
2691
-    /**
2692
-     * _validate_return
2693
-     *
2694
-     * @return void
2695
-     * @throws EE_Error
2696
-     * @throws InvalidArgumentException
2697
-     * @throws InvalidDataTypeException
2698
-     * @throws InvalidInterfaceException
2699
-     * @throws ReflectionException
2700
-     */
2701
-    private function _validate_offsite_return()
2702
-    {
2703
-        $TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2704
-        if ($TXN_ID !== $this->checkout->transaction->ID()) {
2705
-            // Houston... we might have a problem
2706
-            $invalid_TXN = false;
2707
-            // first gather some info
2708
-            $valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2709
-            $primary_registrant = $valid_TXN instanceof EE_Transaction
2710
-                ? $valid_TXN->primary_registration()
2711
-                : null;
2712
-            // let's start by retrieving the cart for this TXN
2713
-            $cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2714
-            if ($cart instanceof EE_Cart) {
2715
-                // verify that the current cart has tickets
2716
-                $tickets = $cart->get_tickets();
2717
-                if (empty($tickets)) {
2718
-                    $invalid_TXN = true;
2719
-                }
2720
-            } else {
2721
-                $invalid_TXN = true;
2722
-            }
2723
-            $valid_TXN_SID = $primary_registrant instanceof EE_Registration
2724
-                ? $primary_registrant->session_ID()
2725
-                : null;
2726
-            // validate current Session ID and compare against valid TXN session ID
2727
-            if (
2728
-                $invalid_TXN // if this is already true, then skip other checks
2729
-                || EE_Session::instance()->id() === null
2730
-                || (
2731
-                    // WARNING !!!
2732
-                    // this could be PayPal sending back duplicate requests (ya they do that)
2733
-                    // or it **could** mean someone is simply registering AGAIN after having just done so,
2734
-                    // so now we need to determine if this current TXN looks valid or not
2735
-                    // and whether this reg step has even been started ?
2736
-                    EE_Session::instance()->id() === $valid_TXN_SID
2737
-                    // really? you're halfway through this reg step, but you never started it ?
2738
-                    && $this->checkout->transaction->reg_step_completed($this->slug()) === false
2739
-                )
2740
-            ) {
2741
-                $invalid_TXN = true;
2742
-            }
2743
-            if ($invalid_TXN) {
2744
-                // is the valid TXN completed ?
2745
-                if ($valid_TXN instanceof EE_Transaction) {
2746
-                    // has this step even been started ?
2747
-                    $reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2748
-                    if ($reg_step_completed !== false && $reg_step_completed !== true) {
2749
-                        // so it **looks** like this is a double request from PayPal
2750
-                        // so let's try to pick up where we left off
2751
-                        $this->checkout->transaction = $valid_TXN;
2752
-                        $this->checkout->refresh_all_entities(true);
2753
-                        return;
2754
-                    }
2755
-                }
2756
-                // you appear to be lost?
2757
-                $this->_redirect_wayward_request($primary_registrant);
2758
-            }
2759
-        }
2760
-    }
2761
-
2762
-
2763
-    /**
2764
-     * _redirect_wayward_request
2765
-     *
2766
-     * @param EE_Registration|null $primary_registrant
2767
-     * @return void
2768
-     * @throws EE_Error
2769
-     * @throws InvalidArgumentException
2770
-     * @throws InvalidDataTypeException
2771
-     * @throws InvalidInterfaceException
2772
-     * @throws ReflectionException
2773
-     */
2774
-    private function _redirect_wayward_request(EE_Registration $primary_registrant)
2775
-    {
2776
-        if (! $primary_registrant instanceof EE_Registration) {
2777
-            // try redirecting based on the current TXN
2778
-            $primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2779
-                ? $this->checkout->transaction->primary_registration()
2780
-                : null;
2781
-        }
2782
-        if (! $primary_registrant instanceof EE_Registration) {
2783
-            EE_Error::add_error(
2784
-                sprintf(
2785
-                    esc_html__(
2786
-                        'Invalid information was received from the Off-Site Payment Processor and your Transaction details could not be retrieved from the database.%1$sPlease try again or contact %2$s for assistance.',
2787
-                        'event_espresso'
2788
-                    ),
2789
-                    '<br/>',
2790
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2791
-                ),
2792
-                __FILE__,
2793
-                __FUNCTION__,
2794
-                __LINE__
2795
-            );
2796
-            return;
2797
-        }
2798
-        // make sure transaction is not locked
2799
-        $this->checkout->transaction->unlock();
2800
-        wp_safe_redirect(
2801
-            add_query_arg(
2802
-                [
2803
-                    'e_reg_url_link' => $primary_registrant->reg_url_link(),
2804
-                ],
2805
-                $this->checkout->thank_you_page_url
2806
-            )
2807
-        );
2808
-        exit();
2809
-    }
2810
-
2811
-
2812
-    /**
2813
-     * _process_off_site_payment
2814
-     *
2815
-     * @param EE_Offsite_Gateway $gateway
2816
-     * @return EE_Payment
2817
-     * @throws EE_Error
2818
-     * @throws InvalidArgumentException
2819
-     * @throws InvalidDataTypeException
2820
-     * @throws InvalidInterfaceException
2821
-     * @throws ReflectionException
2822
-     */
2823
-    private function _process_off_site_payment(EE_Offsite_Gateway $gateway)
2824
-    {
2825
-        try {
2826
-            $request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2827
-            $request_data = $request->requestParams();
2828
-            // if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2829
-            $this->set_handle_IPN_in_this_request(
2830
-                $gateway->handle_IPN_in_this_request($request_data, false)
2831
-            );
2832
-            if ($this->handle_IPN_in_this_request()) {
2833
-                // get payment details and process results
2834
-                /** @var IpnHandler $payment_processor */
2835
-                $payment_processor = LoaderFactory::getShared(IpnHandler::class);
2836
-                $payment           = $payment_processor->processIPN(
2837
-                    $request_data,
2838
-                    $this->checkout->transaction,
2839
-                    $this->checkout->payment_method,
2840
-                    true,
2841
-                    false
2842
-                );
2843
-                // $payment_source = 'process_ipn';
2844
-            } else {
2845
-                $payment = $this->checkout->transaction->last_payment();
2846
-                // $payment_source = 'last_payment';
2847
-            }
2848
-        } catch (Exception $e) {
2849
-            // let's just eat the exception and try to move on using any previously set payment info
2850
-            $payment = $this->checkout->transaction->last_payment();
2851
-            // $payment_source = 'last_payment after Exception';
2852
-            // but if we STILL don't have a payment object
2853
-            if (! $payment instanceof EE_Payment) {
2854
-                // then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2855
-                $this->_handle_payment_processor_exception($e);
2856
-            }
2857
-        }
2858
-        return $payment;
2859
-    }
2860
-
2861
-
2862
-    /**
2863
-     * _process_cancelled_payments
2864
-     * just makes sure that the payment status gets updated correctly
2865
-     * so tha tan error isn't generated during payment validation
2866
-     *
2867
-     * @param EE_Payment $payment
2868
-     * @return EE_Payment|null
2869
-     * @throws EE_Error
2870
-     */
2871
-    private function _process_cancelled_payments($payment = null)
2872
-    {
2873
-        if (
2874
-            $payment instanceof EE_Payment
2875
-            && $this->request->requestParamIsSet('ee_cancel_payment')
2876
-            && $payment->status() === EEM_Payment::status_id_failed
2877
-        ) {
2878
-            $payment->set_status(EEM_Payment::status_id_cancelled);
2879
-        }
2880
-        return $payment;
2881
-    }
2882
-
2883
-
2884
-    /**
2885
-     *    get_transaction_details_for_gateways
2886
-     *
2887
-     * @access    public
2888
-     * @return void
2889
-     * @throws EE_Error
2890
-     * @throws InvalidArgumentException
2891
-     * @throws ReflectionException
2892
-     * @throws InvalidDataTypeException
2893
-     * @throws InvalidInterfaceException
2894
-     */
2895
-    public function get_transaction_details_for_gateways()
2896
-    {
2897
-        $txn_details = [];
2898
-        // ya gotta make a choice man
2899
-        if (empty($this->checkout->selected_method_of_payment)) {
2900
-            $txn_details = [
2901
-                'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2902
-            ];
2903
-        }
2904
-        // get EE_Payment_Method object
2905
-        if (
2906
-            empty($txn_details)
2907
-            && ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2908
-        ) {
2909
-            $txn_details = [
2910
-                'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2911
-                'error'                      => esc_html__(
2912
-                    'A valid Payment Method could not be determined.',
2913
-                    'event_espresso'
2914
-                ),
2915
-            ];
2916
-        }
2917
-        if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2918
-            $return_url  = $this->_get_return_url($this->checkout->payment_method);
2919
-            $txn_details = [
2920
-                'TXN_ID'         => $this->checkout->transaction->ID(),
2921
-                'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2922
-                'TXN_total'      => $this->checkout->transaction->total(),
2923
-                'TXN_paid'       => $this->checkout->transaction->paid(),
2924
-                'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2925
-                'STS_ID'         => $this->checkout->transaction->status_ID(),
2926
-                'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2927
-                'payment_amount' => $this->checkout->amount_owing,
2928
-                'return_url'     => $return_url,
2929
-                'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2930
-                'notify_url'     => EE_Config::instance()->core->txn_page_url(
2931
-                    [
2932
-                        'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2933
-                        'ee_payment_method' => $this->checkout->payment_method->slug(),
2934
-                    ]
2935
-                ),
2936
-            ];
2937
-        }
2938
-        echo wp_json_encode($txn_details);
2939
-        exit();
2940
-    }
2941
-
2942
-
2943
-    /**
2944
-     *    __sleep
2945
-     * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2946
-     * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2947
-     * reg form, because if needed, it will be regenerated anyways
2948
-     *
2949
-     * @return array
2950
-     */
2951
-    public function __sleep()
2952
-    {
2953
-        // remove the reg form and the checkout
2954
-        return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2955
-    }
26
+	/**
27
+	 * @var EE_Line_Item_Display $Line_Item_Display
28
+	 */
29
+	protected $line_item_display;
30
+
31
+	/**
32
+	 * @var boolean $handle_IPN_in_this_request
33
+	 */
34
+	protected $handle_IPN_in_this_request = false;
35
+
36
+
37
+	/**
38
+	 *    set_hooks - for hooking into EE Core, other modules, etc
39
+	 *
40
+	 * @access    public
41
+	 * @return    void
42
+	 */
43
+	public static function set_hooks()
44
+	{
45
+		add_filter(
46
+			'FHEE__SPCO__EE_Line_Item_Filter_Collection',
47
+			['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
48
+		);
49
+		add_action(
50
+			'wp_ajax_switch_spco_billing_form',
51
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
52
+		);
53
+		add_action(
54
+			'wp_ajax_nopriv_switch_spco_billing_form',
55
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
56
+		);
57
+		add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
58
+		add_action(
59
+			'wp_ajax_nopriv_save_payer_details',
60
+			['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
61
+		);
62
+		add_action(
63
+			'wp_ajax_get_transaction_details_for_gateways',
64
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
65
+		);
66
+		add_action(
67
+			'wp_ajax_nopriv_get_transaction_details_for_gateways',
68
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
69
+		);
70
+		add_filter(
71
+			'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
72
+			['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
73
+		);
74
+	}
75
+
76
+
77
+	/**
78
+	 *    ajax switch_spco_billing_form
79
+	 *
80
+	 */
81
+	public static function switch_spco_billing_form()
82
+	{
83
+		EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
84
+	}
85
+
86
+
87
+	/**
88
+	 *    ajax save_payer_details
89
+	 *
90
+	 */
91
+	public static function save_payer_details()
92
+	{
93
+		EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
94
+	}
95
+
96
+
97
+	/**
98
+	 *    ajax get_transaction_details
99
+	 *
100
+	 */
101
+	public static function get_transaction_details()
102
+	{
103
+		EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
104
+	}
105
+
106
+
107
+	/**
108
+	 * bypass_recaptcha_for_load_payment_method
109
+	 *
110
+	 * @access public
111
+	 * @return array
112
+	 * @throws InvalidArgumentException
113
+	 * @throws InvalidDataTypeException
114
+	 * @throws InvalidInterfaceException
115
+	 */
116
+	public static function bypass_recaptcha_for_load_payment_method()
117
+	{
118
+		return [
119
+			'EESID'  => EE_Registry::instance()->SSN->id(),
120
+			'step'   => 'payment_options',
121
+			'action' => 'spco_billing_form',
122
+		];
123
+	}
124
+
125
+
126
+	/**
127
+	 *    class constructor
128
+	 *
129
+	 * @access    public
130
+	 * @param EE_Checkout $checkout
131
+	 */
132
+	public function __construct(EE_Checkout $checkout)
133
+	{
134
+		$this->request   = EED_Single_Page_Checkout::getRequest();
135
+		$this->_slug     = 'payment_options';
136
+		$this->_name     = esc_html__('Payment Options', 'event_espresso');
137
+		$this->_template = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
138
+		$this->checkout  = $checkout;
139
+		$this->_reset_success_message();
140
+		$this->set_instructions(
141
+			esc_html__(
142
+				'Please select a method of payment and provide any necessary billing information before proceeding.',
143
+				'event_espresso'
144
+			)
145
+		);
146
+	}
147
+
148
+
149
+	/**
150
+	 * @return null
151
+	 */
152
+	public function line_item_display()
153
+	{
154
+		return $this->line_item_display;
155
+	}
156
+
157
+
158
+	/**
159
+	 * @param null $line_item_display
160
+	 */
161
+	public function set_line_item_display($line_item_display)
162
+	{
163
+		$this->line_item_display = $line_item_display;
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return boolean
169
+	 */
170
+	public function handle_IPN_in_this_request()
171
+	{
172
+		return $this->handle_IPN_in_this_request;
173
+	}
174
+
175
+
176
+	/**
177
+	 * @param boolean $handle_IPN_in_this_request
178
+	 */
179
+	public function set_handle_IPN_in_this_request($handle_IPN_in_this_request)
180
+	{
181
+		$this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
182
+	}
183
+
184
+
185
+	/**
186
+	 * translate_js_strings
187
+	 *
188
+	 * @return void
189
+	 */
190
+	public function translate_js_strings()
191
+	{
192
+		EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
193
+			'Please select a method of payment in order to continue.',
194
+			'event_espresso'
195
+		);
196
+		EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
197
+			'A valid method of payment could not be determined. Please refresh the page and try again.',
198
+			'event_espresso'
199
+		);
200
+		EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
201
+			'Forwarding to Secure Payment Provider.',
202
+			'event_espresso'
203
+		);
204
+	}
205
+
206
+
207
+	/**
208
+	 * enqueue_styles_and_scripts
209
+	 *
210
+	 * @return void
211
+	 * @throws EE_Error
212
+	 * @throws InvalidArgumentException
213
+	 * @throws InvalidDataTypeException
214
+	 * @throws InvalidInterfaceException
215
+	 * @throws ReflectionException
216
+	 */
217
+	public function enqueue_styles_and_scripts()
218
+	{
219
+		$transaction = $this->checkout->transaction;
220
+		// if the transaction isn't set or nothing is owed on it, don't enqueue any JS
221
+		if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
222
+			return;
223
+		}
224
+		foreach (
225
+			EEM_Payment_Method::instance()->get_all_for_transaction(
226
+				$transaction,
227
+				EEM_Payment_Method::scope_cart
228
+			) as $payment_method
229
+		) {
230
+			$type_obj = $payment_method->type_obj();
231
+			if ($type_obj instanceof EE_PMT_Base) {
232
+				$billing_form = $type_obj->generate_new_billing_form($transaction);
233
+				if ($billing_form instanceof EE_Form_Section_Proper) {
234
+					$billing_form->enqueue_js();
235
+				}
236
+			}
237
+		}
238
+	}
239
+
240
+
241
+	/**
242
+	 * initialize_reg_step
243
+	 *
244
+	 * @return bool
245
+	 * @throws EE_Error
246
+	 * @throws InvalidArgumentException
247
+	 * @throws ReflectionException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 */
251
+	public function initialize_reg_step()
252
+	{
253
+		// TODO: if /when we implement donations, then this will need overriding
254
+		if (
255
+			// don't need payment options for:
256
+			// registrations made via the admin
257
+			// completed transactions
258
+			// overpaid transactions
259
+			// $ 0.00 transactions(no payment required)
260
+			! $this->checkout->payment_required()
261
+			// but do NOT remove if current action being called belongs to this reg step
262
+			&& ! is_callable([$this, $this->checkout->action])
263
+			&& ! $this->completed()
264
+		) {
265
+			// and if so, then we no longer need the Payment Options step
266
+			if ($this->is_current_step()) {
267
+				$this->checkout->generate_reg_form = false;
268
+			}
269
+			$this->checkout->remove_reg_step($this->_slug);
270
+			// DEBUG LOG
271
+			// $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
272
+			return false;
273
+		}
274
+		// load EEM_Payment_Method
275
+		EE_Registry::instance()->load_model('Payment_Method');
276
+		// get all active payment methods
277
+		$this->checkout->available_payment_methods = EEM_Payment_Method::instance()->get_all_for_transaction(
278
+			$this->checkout->transaction,
279
+			EEM_Payment_Method::scope_cart
280
+		);
281
+		return true;
282
+	}
283
+
284
+
285
+	/**
286
+	 * @return EE_Form_Section_Proper
287
+	 * @throws EE_Error
288
+	 * @throws InvalidArgumentException
289
+	 * @throws ReflectionException
290
+	 * @throws EntityNotFoundException
291
+	 * @throws InvalidDataTypeException
292
+	 * @throws InvalidInterfaceException
293
+	 * @throws InvalidStatusException
294
+	 */
295
+	public function generate_reg_form()
296
+	{
297
+		// reset in case someone changes their mind
298
+		$this->_reset_selected_method_of_payment();
299
+		// set some defaults
300
+		$this->checkout->selected_method_of_payment = 'payments_closed';
301
+		$registrations_requiring_payment            = [];
302
+		$registrations_for_free_events              = [];
303
+		$registrations_requiring_pre_approval       = [];
304
+		$sold_out_events                            = [];
305
+		$insufficient_spaces_available              = [];
306
+		$no_payment_required                        = true;
307
+		// loop thru registrations to gather info
308
+		$registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
309
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
310
+			$registrations,
311
+			$this->checkout->revisit
312
+		);
313
+		foreach ($registrations as $REG_ID => $registration) {
314
+			/** @var $registration EE_Registration */
315
+			// Skip if the registration has been moved
316
+			if ($registration->wasMoved()) {
317
+				continue;
318
+			}
319
+			// has this registration lost it's space ?
320
+			if (isset($ejected_registrations[ $REG_ID ])) {
321
+				if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
322
+					$sold_out_events[ $registration->event()->ID() ] = $registration->event();
323
+				} else {
324
+					$insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
325
+				}
326
+				continue;
327
+			}
328
+			// event requires admin approval
329
+			if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
330
+				// add event to list of events with pre-approval reg status
331
+				$registrations_requiring_pre_approval[ $REG_ID ] = $registration;
332
+				do_action(
333
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
334
+					$registration->event(),
335
+					$this
336
+				);
337
+				continue;
338
+			}
339
+			if (
340
+				$this->checkout->revisit
341
+				&& $registration->status_ID() !== RegStatus::APPROVED
342
+				&& (
343
+					$registration->event()->is_sold_out()
344
+					|| $registration->event()->is_sold_out(true)
345
+				)
346
+			) {
347
+				// add event to list of events that are sold out
348
+				$sold_out_events[ $registration->event()->ID() ] = $registration->event();
349
+				do_action(
350
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
351
+					$registration->event(),
352
+					$this
353
+				);
354
+				continue;
355
+			}
356
+			// are they allowed to pay now and is there monies owing?
357
+			if ($registration->owes_monies_and_can_pay()) {
358
+				$registrations_requiring_payment[ $REG_ID ] = $registration;
359
+				do_action(
360
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
361
+					$registration->event(),
362
+					$this
363
+				);
364
+			} elseif (
365
+				! $this->checkout->revisit
366
+					  && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
367
+					  && $registration->ticket()->is_free()
368
+			) {
369
+				$registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
370
+			}
371
+		}
372
+		$subsections = [];
373
+		// now decide which template to load
374
+		if (! empty($sold_out_events)) {
375
+			$subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
376
+		}
377
+		if (! empty($insufficient_spaces_available)) {
378
+			$subsections['insufficient_space'] = $this->_insufficient_spaces_available(
379
+				$insufficient_spaces_available
380
+			);
381
+		}
382
+		if (! empty($registrations_requiring_pre_approval)) {
383
+			$subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
384
+				$registrations_requiring_pre_approval
385
+			);
386
+		}
387
+		if (! empty($registrations_for_free_events)) {
388
+			$subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
389
+		}
390
+		if (! empty($registrations_requiring_payment)) {
391
+			if ($this->checkout->amount_owing > 0) {
392
+				// autoload Line_Item_Display classes
393
+				EEH_Autoloader::register_line_item_filter_autoloaders();
394
+				$line_item_filter_processor = new EE_Line_Item_Filter_Processor(
395
+					apply_filters(
396
+						'FHEE__SPCO__EE_Line_Item_Filter_Collection',
397
+						new EE_Line_Item_Filter_Collection()
398
+					),
399
+					$this->checkout->cart->get_grand_total()
400
+				);
401
+				/** @var EE_Line_Item $filtered_line_item_tree */
402
+				$filtered_line_item_tree = $line_item_filter_processor->process();
403
+				EEH_Autoloader::register_line_item_display_autoloaders();
404
+				$this->set_line_item_display(new EE_Line_Item_Display('spco'));
405
+				$subsections['payment_options'] = $this->_display_payment_options(
406
+					$this->line_item_display->display_line_item(
407
+						$filtered_line_item_tree,
408
+						['registrations' => $registrations]
409
+					)
410
+				);
411
+				$this->checkout->amount_owing   = $filtered_line_item_tree->total();
412
+				$this->_apply_registration_payments_to_amount_owing($registrations);
413
+			}
414
+			$no_payment_required = false;
415
+		} else {
416
+			$this->_hide_reg_step_submit_button_if_revisit();
417
+		}
418
+		$this->_save_selected_method_of_payment();
419
+
420
+		$subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
421
+		$subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
422
+
423
+		return new EE_Form_Section_Proper(
424
+			[
425
+				'name'            => $this->reg_form_name(),
426
+				'html_id'         => $this->reg_form_name(),
427
+				'subsections'     => $subsections,
428
+				'layout_strategy' => new EE_No_Layout(),
429
+			]
430
+		);
431
+	}
432
+
433
+
434
+	/**
435
+	 * add line item filters required for this reg step
436
+	 * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
437
+	 *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
438
+	 *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
439
+	 *        payment options reg step, can apply these filters via the following: apply_filters(
440
+	 *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
441
+	 *        filter collection by passing that instead of instantiating a new collection
442
+	 *
443
+	 * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
444
+	 * @return EE_Line_Item_Filter_Collection
445
+	 * @throws EE_Error
446
+	 * @throws InvalidArgumentException
447
+	 * @throws ReflectionException
448
+	 * @throws EntityNotFoundException
449
+	 * @throws InvalidDataTypeException
450
+	 * @throws InvalidInterfaceException
451
+	 * @throws InvalidStatusException
452
+	 */
453
+	public static function add_spco_line_item_filters(EE_Line_Item_Filter_Collection $line_item_filter_collection)
454
+	{
455
+		if (! EE_Registry::instance()->SSN instanceof EE_Session) {
456
+			return $line_item_filter_collection;
457
+		}
458
+		if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
459
+			return $line_item_filter_collection;
460
+		}
461
+		if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
462
+			return $line_item_filter_collection;
463
+		}
464
+		$line_item_filter_collection->add(
465
+			new EE_Billable_Line_Item_Filter(
466
+				EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
467
+					EE_Registry::instance()->SSN->checkout()->transaction->registrations(
468
+						EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
469
+					)
470
+				)
471
+			)
472
+		);
473
+		$line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
474
+		return $line_item_filter_collection;
475
+	}
476
+
477
+
478
+	/**
479
+	 * remove_ejected_registrations
480
+	 * if a registrant has lost their potential space at an event due to lack of payment,
481
+	 * then this method removes them from the list of registrations being paid for during this request
482
+	 *
483
+	 * @param EE_Registration[] $registrations
484
+	 * @return EE_Registration[]
485
+	 * @throws EE_Error
486
+	 * @throws InvalidArgumentException
487
+	 * @throws ReflectionException
488
+	 * @throws EntityNotFoundException
489
+	 * @throws InvalidDataTypeException
490
+	 * @throws InvalidInterfaceException
491
+	 * @throws InvalidStatusException
492
+	 */
493
+	public static function remove_ejected_registrations(array $registrations)
494
+	{
495
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
496
+			$registrations,
497
+			EE_Registry::instance()->SSN->checkout()->revisit
498
+		);
499
+		foreach ($registrations as $REG_ID => $registration) {
500
+			// has this registration lost it's space ?
501
+			if (isset($ejected_registrations[ $REG_ID ])) {
502
+				unset($registrations[ $REG_ID ]);
503
+			}
504
+		}
505
+		return $registrations;
506
+	}
507
+
508
+
509
+	/**
510
+	 * find_registrations_that_lost_their_space
511
+	 * If a registrant chooses an offline payment method like Invoice,
512
+	 * then no space is reserved for them at the event until they fully pay fo that site
513
+	 * (unless the event's default reg status is set to APPROVED)
514
+	 * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
515
+	 * then this method will determine which registrations have lost the ability to complete the reg process.
516
+	 *
517
+	 * @param EE_Registration[] $registrations
518
+	 * @param bool              $revisit
519
+	 * @return array
520
+	 * @throws EE_Error
521
+	 * @throws InvalidArgumentException
522
+	 * @throws ReflectionException
523
+	 * @throws EntityNotFoundException
524
+	 * @throws InvalidDataTypeException
525
+	 * @throws InvalidInterfaceException
526
+	 * @throws InvalidStatusException
527
+	 */
528
+	public static function find_registrations_that_lost_their_space(array $registrations, $revisit = false)
529
+	{
530
+		// registrations per event
531
+		$event_reg_count = [];
532
+		// spaces left per event
533
+		$event_spaces_remaining = [];
534
+		// tickets left sorted by ID
535
+		$tickets_remaining = [];
536
+		// registrations that have lost their space
537
+		$ejected_registrations = [];
538
+		foreach ($registrations as $REG_ID => $registration) {
539
+			if (
540
+				$registration->status_ID() === RegStatus::APPROVED
541
+				|| apply_filters(
542
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
543
+					false,
544
+					$registration,
545
+					$revisit
546
+				)
547
+			) {
548
+				continue;
549
+			}
550
+			$EVT_ID = $registration->event_ID();
551
+			$ticket = $registration->ticket();
552
+			if (! isset($tickets_remaining[ $ticket->ID() ])) {
553
+				$tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
554
+			}
555
+			if ($tickets_remaining[ $ticket->ID() ] > 0) {
556
+				if (! isset($event_reg_count[ $EVT_ID ])) {
557
+					$event_reg_count[ $EVT_ID ] = 0;
558
+				}
559
+				$event_reg_count[ $EVT_ID ]++;
560
+				if (! isset($event_spaces_remaining[ $EVT_ID ])) {
561
+					$event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
562
+				}
563
+			}
564
+			if (
565
+				$revisit
566
+				&& ($tickets_remaining[ $ticket->ID() ] === 0
567
+					|| $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
568
+				)
569
+			) {
570
+				$ejected_registrations[ $REG_ID ] = $registration->event();
571
+				if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
572
+					/** @type EE_Registration_Processor $registration_processor */
573
+					$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
574
+					// at this point, we should have enough details about the registrant to consider the registration
575
+					// NOT incomplete
576
+					$registration_processor->manually_update_registration_status(
577
+						$registration,
578
+						RegStatus::WAIT_LIST
579
+					);
580
+				}
581
+			}
582
+		}
583
+		return $ejected_registrations;
584
+	}
585
+
586
+
587
+	/**
588
+	 * _hide_reg_step_submit_button
589
+	 * removes the html for the reg step submit button
590
+	 * by replacing it with an empty string via filter callback
591
+	 *
592
+	 * @return void
593
+	 */
594
+	protected function _adjust_registration_status_if_event_old_sold()
595
+	{
596
+	}
597
+
598
+
599
+	/**
600
+	 * _hide_reg_step_submit_button
601
+	 * removes the html for the reg step submit button
602
+	 * by replacing it with an empty string via filter callback
603
+	 *
604
+	 * @return void
605
+	 */
606
+	protected function _hide_reg_step_submit_button_if_revisit()
607
+	{
608
+		if ($this->checkout->revisit) {
609
+			add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
610
+		}
611
+	}
612
+
613
+
614
+	/**
615
+	 * sold_out_events
616
+	 * displays notices regarding events that have sold out since hte registrant first signed up
617
+	 *
618
+	 * @param EE_Event[] $sold_out_events_array
619
+	 * @return EE_Form_Section_Proper
620
+	 * @throws EE_Error
621
+	 */
622
+	private function _sold_out_events($sold_out_events_array = [])
623
+	{
624
+		// set some defaults
625
+		$this->checkout->selected_method_of_payment = 'events_sold_out';
626
+		$sold_out_events                            = '';
627
+		foreach ($sold_out_events_array as $sold_out_event) {
628
+			$sold_out_events .= EEH_HTML::li(
629
+				EEH_HTML::span(
630
+					'  ' . $sold_out_event->name(),
631
+					'',
632
+					'dashicons dashicons-marker ee-icon-size-16 pink-text'
633
+				)
634
+			);
635
+		}
636
+		return new EE_Form_Section_Proper(
637
+			[
638
+				'layout_strategy' => new EE_Template_Layout(
639
+					[
640
+						'layout_template_file' => SPCO_REG_STEPS_PATH
641
+												  . $this->_slug
642
+												  . '/sold_out_events.template.php',
643
+						'template_args'        => apply_filters(
644
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
645
+							[
646
+								'sold_out_events'     => $sold_out_events,
647
+								'sold_out_events_msg' => apply_filters(
648
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
649
+									sprintf(
650
+										esc_html__(
651
+											'It appears that the event you were about to make a payment for has sold out since you first registered. If you have already made a partial payment towards this event, please contact the event administrator for a refund.%3$s%3$s%1$sPlease note that availability can change at any time due to cancellations, so please check back again later if registration for this event(s) is important to you.%2$s',
652
+											'event_espresso'
653
+										),
654
+										'<strong>',
655
+										'</strong>',
656
+										'<br />'
657
+									)
658
+								),
659
+							]
660
+						),
661
+					]
662
+				),
663
+			]
664
+		);
665
+	}
666
+
667
+
668
+	/**
669
+	 * _insufficient_spaces_available
670
+	 * displays notices regarding events that do not have enough remaining spaces
671
+	 * to satisfy the current number of registrations looking to pay
672
+	 *
673
+	 * @param EE_Event[] $insufficient_spaces_events_array
674
+	 * @return EE_Form_Section_Proper
675
+	 * @throws EE_Error
676
+	 * @throws ReflectionException
677
+	 */
678
+	private function _insufficient_spaces_available($insufficient_spaces_events_array = [])
679
+	{
680
+		// set some defaults
681
+		$this->checkout->selected_method_of_payment = 'invoice';
682
+		$insufficient_space_events                  = '';
683
+		foreach ($insufficient_spaces_events_array as $event) {
684
+			if ($event instanceof EE_Event) {
685
+				$insufficient_space_events .= EEH_HTML::li(
686
+					EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
687
+				);
688
+			}
689
+		}
690
+		return new EE_Form_Section_Proper(
691
+			[
692
+				'subsections'     => [
693
+					'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
694
+					'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
695
+				],
696
+				'layout_strategy' => new EE_Template_Layout(
697
+					[
698
+						'layout_template_file' => SPCO_REG_STEPS_PATH
699
+												  . $this->_slug
700
+												  . '/sold_out_events.template.php',
701
+						'template_args'        => apply_filters(
702
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
703
+							[
704
+								'sold_out_events'     => $insufficient_space_events,
705
+								'sold_out_events_msg' => apply_filters(
706
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
707
+									esc_html__(
708
+										'It appears that the event you were about to make a payment for has sold additional tickets since you first registered, and there are no longer enough spaces left to accommodate your selections. You may continue to pay and secure the available space(s) remaining, or simply cancel if you no longer wish to purchase. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
709
+										'event_espresso'
710
+									)
711
+								),
712
+							]
713
+						),
714
+					]
715
+				),
716
+			]
717
+		);
718
+	}
719
+
720
+
721
+	/**
722
+	 * registrations_requiring_pre_approval
723
+	 *
724
+	 * @param array $registrations_requiring_pre_approval
725
+	 * @return EE_Form_Section_Proper
726
+	 * @throws EE_Error
727
+	 * @throws EntityNotFoundException
728
+	 * @throws ReflectionException
729
+	 */
730
+	private function _registrations_requiring_pre_approval($registrations_requiring_pre_approval = [])
731
+	{
732
+		$events_requiring_pre_approval = [];
733
+		foreach ($registrations_requiring_pre_approval as $registration) {
734
+			if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
735
+				$events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
736
+					EEH_HTML::span(
737
+						'',
738
+						'',
739
+						'dashicons dashicons-marker ee-icon-size-16 orange-text'
740
+					)
741
+					. EEH_HTML::span($registration->event()->name(), '', 'orange-text')
742
+				);
743
+			}
744
+		}
745
+		return new EE_Form_Section_Proper(
746
+			[
747
+				'layout_strategy' => new EE_Template_Layout(
748
+					[
749
+						'layout_template_file' => SPCO_REG_STEPS_PATH
750
+												  . $this->_slug
751
+												  . '/events_requiring_pre_approval.template.php', // layout_template
752
+						'template_args'        => apply_filters(
753
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
754
+							[
755
+								'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
756
+								'events_requiring_pre_approval_msg' => apply_filters(
757
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
758
+									esc_html__(
759
+										'The following events do not require payment at this time and will not be billed during this transaction. Billing will only occur after the attendee has been approved by the event organizer. You will be notified when your registration has been processed. If this is a free event, then no billing will occur.',
760
+										'event_espresso'
761
+									)
762
+								),
763
+							]
764
+						),
765
+					]
766
+				),
767
+			]
768
+		);
769
+	}
770
+
771
+
772
+	/**
773
+	 * _no_payment_required
774
+	 *
775
+	 * @param EE_Event[] $registrations_for_free_events
776
+	 * @return EE_Form_Section_Proper
777
+	 * @throws EE_Error
778
+	 */
779
+	private function _no_payment_required($registrations_for_free_events = [])
780
+	{
781
+		// set some defaults
782
+		$this->checkout->selected_method_of_payment = 'no_payment_required';
783
+		// generate no_payment_required form
784
+		return new EE_Form_Section_Proper(
785
+			[
786
+				'layout_strategy' => new EE_Template_Layout(
787
+					[
788
+						'layout_template_file' => SPCO_REG_STEPS_PATH
789
+												  . $this->_slug
790
+												  . '/no_payment_required.template.php', // layout_template
791
+						'template_args'        => apply_filters(
792
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
793
+							[
794
+								'revisit'                       => $this->checkout->revisit,
795
+								'registrations'                 => [],
796
+								'ticket_count'                  => [],
797
+								'registrations_for_free_events' => $registrations_for_free_events,
798
+								'no_payment_required_msg'       => EEH_HTML::p(
799
+									esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
800
+								),
801
+							]
802
+						),
803
+					]
804
+				),
805
+			]
806
+		);
807
+	}
808
+
809
+
810
+	/**
811
+	 * _display_payment_options
812
+	 *
813
+	 * @param string $transaction_details
814
+	 * @return EE_Form_Section_Proper
815
+	 * @throws EE_Error
816
+	 * @throws InvalidArgumentException
817
+	 * @throws InvalidDataTypeException
818
+	 * @throws InvalidInterfaceException
819
+	 */
820
+	private function _display_payment_options($transaction_details = '')
821
+	{
822
+		// has method_of_payment been set by no-js user?
823
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
824
+		// build payment options form
825
+		return apply_filters(
826
+			'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
827
+			new EE_Form_Section_Proper(
828
+				[
829
+					'subsections'     => [
830
+						'before_payment_options' => apply_filters(
831
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
832
+							new EE_Form_Section_Proper(
833
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
834
+							)
835
+						),
836
+						'payment_options'        => $this->_setup_payment_options(),
837
+						'after_payment_options'  => apply_filters(
838
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
839
+							new EE_Form_Section_Proper(
840
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
841
+							)
842
+						),
843
+					],
844
+					'layout_strategy' => new EE_Template_Layout(
845
+						[
846
+							'layout_template_file' => $this->_template,
847
+							'template_args'        => apply_filters(
848
+								'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
849
+								[
850
+									'reg_count'                 => $this->line_item_display->total_items(),
851
+									'transaction_details'       => $transaction_details,
852
+									'available_payment_methods' => [],
853
+								]
854
+							),
855
+						]
856
+					),
857
+				]
858
+			)
859
+		);
860
+	}
861
+
862
+
863
+	/**
864
+	 * _extra_hidden_inputs
865
+	 *
866
+	 * @param bool $no_payment_required
867
+	 * @return EE_Form_Section_Proper
868
+	 * @throws EE_Error
869
+	 * @throws ReflectionException
870
+	 */
871
+	private function _extra_hidden_inputs($no_payment_required = true)
872
+	{
873
+		return new EE_Form_Section_Proper(
874
+			[
875
+				'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
876
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
877
+				'subsections'     => [
878
+					'spco_no_payment_required' => new EE_Hidden_Input(
879
+						[
880
+							'normalization_strategy' => new EE_Boolean_Normalization(),
881
+							'html_name'              => 'spco_no_payment_required',
882
+							'html_id'                => 'spco-no-payment-required-payment_options',
883
+							'default'                => $no_payment_required,
884
+						]
885
+					),
886
+					'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
887
+						[
888
+							'normalization_strategy' => new EE_Int_Normalization(),
889
+							'html_name'              => 'spco_transaction_id',
890
+							'html_id'                => 'spco-transaction-id',
891
+							'default'                => $this->checkout->transaction->ID(),
892
+						]
893
+					),
894
+				],
895
+			]
896
+		);
897
+	}
898
+
899
+
900
+	/**
901
+	 * @param array $registrations
902
+	 * @throws EE_Error
903
+	 * @throws ReflectionException
904
+	 */
905
+	protected function _apply_registration_payments_to_amount_owing(array $registrations)
906
+	{
907
+		$payments = [];
908
+		foreach ($registrations as $registration) {
909
+			if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
910
+				$payments += $registration->registration_payments();
911
+			}
912
+		}
913
+		if (! empty($payments)) {
914
+			foreach ($payments as $payment) {
915
+				if ($payment instanceof EE_Registration_Payment) {
916
+					$this->checkout->amount_owing -= $payment->amount();
917
+				}
918
+			}
919
+		}
920
+	}
921
+
922
+
923
+	/**
924
+	 *    _reset_selected_method_of_payment
925
+	 *
926
+	 * @access    private
927
+	 * @param bool $force_reset
928
+	 * @return void
929
+	 * @throws InvalidArgumentException
930
+	 * @throws InvalidDataTypeException
931
+	 * @throws InvalidInterfaceException
932
+	 */
933
+	private function _reset_selected_method_of_payment($force_reset = false)
934
+	{
935
+		/** @var RequestInterface $request */
936
+		$request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
937
+		$reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
938
+		if ($reset_payment_method) {
939
+			$this->checkout->selected_method_of_payment = null;
940
+			$this->checkout->payment_method             = null;
941
+			$this->checkout->billing_form               = null;
942
+			$this->_save_selected_method_of_payment();
943
+		}
944
+	}
945
+
946
+
947
+	/**
948
+	 * _save_selected_method_of_payment
949
+	 * stores the selected_method_of_payment in the session
950
+	 * so that it's available for all subsequent requests including AJAX
951
+	 *
952
+	 * @access        private
953
+	 * @param string $selected_method_of_payment
954
+	 * @return void
955
+	 * @throws InvalidArgumentException
956
+	 * @throws InvalidDataTypeException
957
+	 * @throws InvalidInterfaceException
958
+	 */
959
+	private function _save_selected_method_of_payment($selected_method_of_payment = '')
960
+	{
961
+		$selected_method_of_payment = ! empty($selected_method_of_payment)
962
+			? $selected_method_of_payment
963
+			: $this->checkout->selected_method_of_payment;
964
+		EE_Registry::instance()->SSN->set_session_data(
965
+			['selected_method_of_payment' => $selected_method_of_payment]
966
+		);
967
+	}
968
+
969
+
970
+	/**
971
+	 * _setup_payment_options
972
+	 *
973
+	 * @return EE_Form_Section_Proper
974
+	 * @throws EE_Error
975
+	 * @throws InvalidArgumentException
976
+	 * @throws InvalidDataTypeException
977
+	 * @throws InvalidInterfaceException
978
+	 */
979
+	public function _setup_payment_options()
980
+	{
981
+		// load payment method classes
982
+		$this->checkout->available_payment_methods = $this->_get_available_payment_methods();
983
+		if (empty($this->checkout->available_payment_methods)) {
984
+			EE_Error::add_error(
985
+				apply_filters(
986
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
987
+					sprintf(
988
+						esc_html__(
989
+							'Sorry, you cannot complete your purchase because a payment method is not active.%1$s Please contact %2$s for assistance and provide a description of the problem.',
990
+							'event_espresso'
991
+						),
992
+						'<br>',
993
+						EE_Registry::instance()->CFG->organization->get_pretty('email')
994
+					)
995
+				),
996
+				__FILE__,
997
+				__FUNCTION__,
998
+				__LINE__
999
+			);
1000
+		}
1001
+		// switch up header depending on number of available payment methods
1002
+		$payment_method_header     = count($this->checkout->available_payment_methods) > 1
1003
+			? apply_filters(
1004
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
1005
+				esc_html__('Please Select Your Method of Payment', 'event_espresso')
1006
+			)
1007
+			: apply_filters(
1008
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
1009
+				esc_html__('Method of Payment', 'event_espresso')
1010
+			);
1011
+		$available_payment_methods = [
1012
+			// display the "Payment Method" header
1013
+			'payment_method_header' => new EE_Form_Section_HTML(
1014
+				apply_filters(
1015
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
1016
+					EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
1017
+					$payment_method_header
1018
+				)
1019
+			),
1020
+		];
1021
+		// the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
1022
+		$available_payment_method_options = [];
1023
+		$default_payment_method_option    = [];
1024
+		// additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
1025
+		$payment_methods_billing_info = [
1026
+			new EE_Form_Section_HTML(
1027
+				EEH_HTML::div('<br />', '', '', 'clear:both;')
1028
+			),
1029
+		];
1030
+		// loop through payment methods
1031
+		foreach ($this->checkout->available_payment_methods as $payment_method) {
1032
+			if ($payment_method instanceof EE_Payment_Method) {
1033
+				$payment_method_button = EEH_HTML::img(
1034
+					$payment_method->button_url(),
1035
+					$payment_method->name(),
1036
+					'spco-payment-method-' . $payment_method->slug() . '-btn-img',
1037
+					'spco-payment-method-btn-img'
1038
+				);
1039
+				// check if any payment methods are set as default
1040
+				// if payment method is already selected OR nothing is selected and this payment method should be
1041
+				// open_by_default
1042
+				if (
1043
+					($this->checkout->selected_method_of_payment === $payment_method->slug())
1044
+					|| (! $this->checkout->selected_method_of_payment && $payment_method->open_by_default())
1045
+				) {
1046
+					$this->checkout->selected_method_of_payment = $payment_method->slug();
1047
+					$this->_save_selected_method_of_payment();
1048
+					$default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1049
+				} else {
1050
+					$available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1051
+				}
1052
+				$payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1053
+					$this->_payment_method_billing_info(
1054
+						$payment_method
1055
+					);
1056
+			}
1057
+		}
1058
+		// prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1059
+		// of PMs
1060
+		$available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1061
+		// now generate the actual form  inputs
1062
+		$available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1063
+			$available_payment_method_options
1064
+		);
1065
+		$available_payment_methods                              += $payment_methods_billing_info;
1066
+		// build the available payment methods form
1067
+		return new EE_Form_Section_Proper(
1068
+			[
1069
+				'html_id'         => 'spco-available-methods-of-payment-dv',
1070
+				'subsections'     => $available_payment_methods,
1071
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1072
+			]
1073
+		);
1074
+	}
1075
+
1076
+
1077
+	/**
1078
+	 * _get_available_payment_methods
1079
+	 *
1080
+	 * @return EE_Payment_Method[]
1081
+	 * @throws EE_Error
1082
+	 * @throws InvalidArgumentException
1083
+	 * @throws InvalidDataTypeException
1084
+	 * @throws InvalidInterfaceException
1085
+	 */
1086
+	protected function _get_available_payment_methods()
1087
+	{
1088
+		if (! empty($this->checkout->available_payment_methods)) {
1089
+			return $this->checkout->available_payment_methods;
1090
+		}
1091
+		$available_payment_methods = [];
1092
+		$EEM_Payment_Method        = EEM_Payment_Method::instance();
1093
+		// get all active payment methods
1094
+		$payment_methods = $EEM_Payment_Method->get_all_for_transaction(
1095
+			$this->checkout->transaction,
1096
+			EEM_Payment_Method::scope_cart
1097
+		);
1098
+		foreach ($payment_methods as $payment_method) {
1099
+			if ($payment_method instanceof EE_Payment_Method) {
1100
+				$available_payment_methods[ $payment_method->slug() ] = $payment_method;
1101
+			}
1102
+		}
1103
+		return $available_payment_methods;
1104
+	}
1105
+
1106
+
1107
+	/**
1108
+	 *    _available_payment_method_inputs
1109
+	 *
1110
+	 * @access    private
1111
+	 * @param array $available_payment_method_options
1112
+	 * @return    EE_Form_Section_Proper
1113
+	 * @throws EE_Error
1114
+	 * @throws EE_Error
1115
+	 */
1116
+	private function _available_payment_method_inputs($available_payment_method_options = [])
1117
+	{
1118
+		// generate inputs
1119
+		return new EE_Form_Section_Proper(
1120
+			[
1121
+				'html_id'         => 'ee-available-payment-method-inputs',
1122
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1123
+				'subsections'     => [
1124
+					'' => new EE_Radio_Button_Input(
1125
+						$available_payment_method_options,
1126
+						[
1127
+							'html_name'          => 'selected_method_of_payment',
1128
+							'html_class'         => 'spco-payment-method',
1129
+							'default'            => $this->checkout->selected_method_of_payment,
1130
+							'label_size'         => 11,
1131
+							'enforce_label_size' => true,
1132
+						]
1133
+					),
1134
+				],
1135
+			]
1136
+		);
1137
+	}
1138
+
1139
+
1140
+	/**
1141
+	 *    _payment_method_billing_info
1142
+	 *
1143
+	 * @access    private
1144
+	 * @param EE_Payment_Method $payment_method
1145
+	 * @return EE_Form_Section_Proper
1146
+	 * @throws EE_Error
1147
+	 * @throws InvalidArgumentException
1148
+	 * @throws InvalidDataTypeException
1149
+	 * @throws InvalidInterfaceException
1150
+	 */
1151
+	private function _payment_method_billing_info(EE_Payment_Method $payment_method)
1152
+	{
1153
+		$currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1154
+		// generate the billing form for payment method
1155
+		$billing_form                 = $currently_selected
1156
+			? $this->_get_billing_form_for_payment_method($payment_method)
1157
+			: new EE_Form_Section_HTML();
1158
+		$this->checkout->billing_form = $currently_selected
1159
+			? $billing_form
1160
+			: $this->checkout->billing_form;
1161
+		// it's all in the details
1162
+		$info_html = EEH_HTML::h3(
1163
+			esc_html__('Important information regarding your payment', 'event_espresso'),
1164
+			'',
1165
+			'spco-payment-method-hdr'
1166
+		);
1167
+		// add some info regarding the step, either from what's saved in the admin,
1168
+		// or a default string depending on whether the PM has a billing form or not
1169
+		if ($payment_method->description()) {
1170
+			$payment_method_info = $payment_method->description();
1171
+		} elseif ($billing_form instanceof EE_Billing_Info_Form) {
1172
+			$payment_method_info = sprintf(
1173
+				esc_html__(
1174
+					'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1175
+					'event_espresso'
1176
+				),
1177
+				$this->submit_button_text()
1178
+			);
1179
+		} else {
1180
+			$payment_method_info = sprintf(
1181
+				esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1182
+				$this->submit_button_text()
1183
+			);
1184
+		}
1185
+		$info_html .= EEH_HTML::div(
1186
+			apply_filters(
1187
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1188
+				$payment_method_info
1189
+			),
1190
+			'',
1191
+			'spco-payment-method-desc ee-attention'
1192
+		);
1193
+		return new EE_Form_Section_Proper(
1194
+			[
1195
+				'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1196
+				'html_class'      => 'spco-payment-method-info-dv',
1197
+				// only display the selected or default PM
1198
+				'html_style'      => $currently_selected ? '' : 'display:none;',
1199
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1200
+				'subsections'     => [
1201
+					'info'         => new EE_Form_Section_HTML($info_html),
1202
+					'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1203
+				],
1204
+			]
1205
+		);
1206
+	}
1207
+
1208
+
1209
+	/**
1210
+	 * get_billing_form_html_for_payment_method
1211
+	 *
1212
+	 * @return bool
1213
+	 * @throws EE_Error
1214
+	 * @throws InvalidArgumentException
1215
+	 * @throws ReflectionException
1216
+	 * @throws InvalidDataTypeException
1217
+	 * @throws InvalidInterfaceException
1218
+	 */
1219
+	public function get_billing_form_html_for_payment_method()
1220
+	{
1221
+		// how have they chosen to pay?
1222
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1223
+		$this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1224
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1225
+			return false;
1226
+		}
1227
+		if (
1228
+			apply_filters(
1229
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1230
+				false
1231
+			)
1232
+		) {
1233
+			EE_Error::add_success(
1234
+				apply_filters(
1235
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1236
+					sprintf(
1237
+						esc_html__(
1238
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1239
+							'event_espresso'
1240
+						),
1241
+						$this->checkout->payment_method->name()
1242
+					)
1243
+				)
1244
+			);
1245
+		}
1246
+		// now generate billing form for selected method of payment
1247
+		$payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1248
+		// fill form with attendee info if applicable
1249
+		if (
1250
+			$payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1251
+			&& $this->checkout->transaction_has_primary_registrant()
1252
+		) {
1253
+			$payment_method_billing_form->populate_from_attendee(
1254
+				$this->checkout->transaction->primary_registration()->attendee()
1255
+			);
1256
+		}
1257
+		// and debug content
1258
+		if (
1259
+			$payment_method_billing_form instanceof EE_Billing_Info_Form
1260
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1261
+		) {
1262
+			$payment_method_billing_form =
1263
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1264
+					$payment_method_billing_form
1265
+				);
1266
+		}
1267
+		$billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1268
+			? $payment_method_billing_form->get_html()
1269
+			: '';
1270
+		$this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1271
+		// localize validation rules for main form
1272
+		$this->checkout->current_step->reg_form->localize_validation_rules();
1273
+		$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1274
+		return true;
1275
+	}
1276
+
1277
+
1278
+	/**
1279
+	 * _get_billing_form_for_payment_method
1280
+	 *
1281
+	 * @param EE_Payment_Method $payment_method
1282
+	 * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1283
+	 * @throws EE_Error
1284
+	 * @throws InvalidArgumentException
1285
+	 * @throws InvalidDataTypeException
1286
+	 * @throws InvalidInterfaceException
1287
+	 */
1288
+	private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1289
+	{
1290
+		$billing_form = $payment_method->type_obj()->billing_form(
1291
+			$this->checkout->transaction,
1292
+			['amount_owing' => $this->checkout->amount_owing]
1293
+		);
1294
+		if ($billing_form instanceof EE_Billing_Info_Form) {
1295
+			if (
1296
+				apply_filters(
1297
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1298
+					false
1299
+				)
1300
+				&& $this->request->requestParamIsSet('payment_method')
1301
+			) {
1302
+				EE_Error::add_success(
1303
+					apply_filters(
1304
+						'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1305
+						sprintf(
1306
+							esc_html__(
1307
+								'You have selected "%s" as your method of payment. Please note the important payment information below.',
1308
+								'event_espresso'
1309
+							),
1310
+							$payment_method->name()
1311
+						)
1312
+					)
1313
+				);
1314
+			}
1315
+			return apply_filters(
1316
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1317
+				$billing_form,
1318
+				$payment_method
1319
+			);
1320
+		}
1321
+		// no actual billing form, so return empty HTML form section
1322
+		return new EE_Form_Section_HTML();
1323
+	}
1324
+
1325
+
1326
+	/**
1327
+	 * _get_selected_method_of_payment
1328
+	 *
1329
+	 * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1330
+	 *                          is not found in the incoming request
1331
+	 * @param string  $request_param
1332
+	 * @return NULL|string
1333
+	 * @throws EE_Error
1334
+	 * @throws InvalidArgumentException
1335
+	 * @throws InvalidDataTypeException
1336
+	 * @throws InvalidInterfaceException
1337
+	 */
1338
+	private function _get_selected_method_of_payment(
1339
+		$required = false,
1340
+		$request_param = 'selected_method_of_payment'
1341
+	) {
1342
+		// is selected_method_of_payment set in the request ?
1343
+		$selected_method_of_payment = $this->request->getRequestParam($request_param);
1344
+		if ($selected_method_of_payment) {
1345
+			// sanitize it
1346
+			$selected_method_of_payment = is_array($selected_method_of_payment)
1347
+				? array_shift($selected_method_of_payment)
1348
+				: $selected_method_of_payment;
1349
+			$selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1350
+			// store it in the session so that it's available for all subsequent requests including AJAX
1351
+			$this->_save_selected_method_of_payment($selected_method_of_payment);
1352
+		} else {
1353
+			// or is it set in the session ?
1354
+			$selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1355
+				'selected_method_of_payment'
1356
+			);
1357
+		}
1358
+		if (empty($selected_method_of_payment) && $required) {
1359
+			EE_Error::add_error(
1360
+				sprintf(
1361
+					esc_html__(
1362
+						'The selected method of payment could not be determined.%sPlease ensure that you have selected one before proceeding.%sIf you continue to experience difficulties, then refresh your browser and try again, or contact %s for assistance.',
1363
+						'event_espresso'
1364
+					),
1365
+					'<br/>',
1366
+					'<br/>',
1367
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
1368
+				),
1369
+				__FILE__,
1370
+				__FUNCTION__,
1371
+				__LINE__
1372
+			);
1373
+			return null;
1374
+		}
1375
+		return $selected_method_of_payment;
1376
+	}
1377
+
1378
+
1379
+
1380
+
1381
+
1382
+
1383
+	/********************************************************************************************************/
1384
+	/***********************************  SWITCH PAYMENT METHOD  ************************************/
1385
+	/********************************************************************************************************/
1386
+	/**
1387
+	 * switch_payment_method
1388
+	 *
1389
+	 * @return bool
1390
+	 * @throws EE_Error
1391
+	 * @throws InvalidArgumentException
1392
+	 * @throws InvalidDataTypeException
1393
+	 * @throws InvalidInterfaceException
1394
+	 * @throws ReflectionException
1395
+	 */
1396
+	public function switch_payment_method()
1397
+	{
1398
+		if (! $this->_verify_payment_method_is_set()) {
1399
+			return false;
1400
+		}
1401
+		if (
1402
+			apply_filters(
1403
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1404
+				false
1405
+			)
1406
+		) {
1407
+			EE_Error::add_success(
1408
+				apply_filters(
1409
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1410
+					sprintf(
1411
+						esc_html__(
1412
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1413
+							'event_espresso'
1414
+						),
1415
+						$this->checkout->payment_method->name()
1416
+					)
1417
+				)
1418
+			);
1419
+		}
1420
+		// generate billing form for selected method of payment if it hasn't been done already
1421
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1422
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1423
+				$this->checkout->payment_method
1424
+			);
1425
+		}
1426
+		// fill form with attendee info if applicable
1427
+		if (
1428
+			apply_filters(
1429
+				'FHEE__populate_billing_form_fields_from_attendee',
1430
+				(
1431
+					$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1432
+					&& $this->checkout->transaction_has_primary_registrant()
1433
+				),
1434
+				$this->checkout->billing_form,
1435
+				$this->checkout->transaction
1436
+			)
1437
+		) {
1438
+			$this->checkout->billing_form->populate_from_attendee(
1439
+				$this->checkout->transaction->primary_registration()->attendee()
1440
+			);
1441
+		}
1442
+		// and debug content
1443
+		if (
1444
+			$this->checkout->billing_form instanceof EE_Billing_Info_Form
1445
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1446
+		) {
1447
+			$this->checkout->billing_form =
1448
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1449
+					$this->checkout->billing_form
1450
+				);
1451
+		}
1452
+		// get html and validation rules for form
1453
+		if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1454
+			$this->checkout->json_response->set_return_data(
1455
+				['payment_method_info' => $this->checkout->billing_form->get_html()]
1456
+			);
1457
+			// localize validation rules for main form
1458
+			$this->checkout->billing_form->localize_validation_rules(true);
1459
+			$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1460
+		} else {
1461
+			$this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1462
+		}
1463
+		// prevents advancement to next step
1464
+		$this->checkout->continue_reg = false;
1465
+		return true;
1466
+	}
1467
+
1468
+
1469
+	/**
1470
+	 * _verify_payment_method_is_set
1471
+	 *
1472
+	 * @return bool
1473
+	 * @throws EE_Error
1474
+	 * @throws InvalidArgumentException
1475
+	 * @throws ReflectionException
1476
+	 * @throws InvalidDataTypeException
1477
+	 * @throws InvalidInterfaceException
1478
+	 */
1479
+	protected function _verify_payment_method_is_set()
1480
+	{
1481
+		// generate billing form for selected method of payment if it hasn't been done already
1482
+		if (empty($this->checkout->selected_method_of_payment)) {
1483
+			// how have they chosen to pay?
1484
+			$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1485
+		} else {
1486
+			// choose your own adventure based on method_of_payment
1487
+			switch ($this->checkout->selected_method_of_payment) {
1488
+				case 'events_sold_out':
1489
+					EE_Error::add_attention(
1490
+						apply_filters(
1491
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1492
+							esc_html__(
1493
+								'It appears that the event you were about to make a payment for has sold out since this form first loaded. Please contact the event administrator if you believe this is an error.',
1494
+								'event_espresso'
1495
+							)
1496
+						),
1497
+						__FILE__,
1498
+						__FUNCTION__,
1499
+						__LINE__
1500
+					);
1501
+					return false;
1502
+				case 'payments_closed':
1503
+					EE_Error::add_attention(
1504
+						apply_filters(
1505
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1506
+							esc_html__(
1507
+								'It appears that the event you were about to make a payment for is not accepting payments at this time. Please contact the event administrator if you believe this is an error.',
1508
+								'event_espresso'
1509
+							)
1510
+						),
1511
+						__FILE__,
1512
+						__FUNCTION__,
1513
+						__LINE__
1514
+					);
1515
+					return false;
1516
+				case 'no_payment_required':
1517
+					EE_Error::add_attention(
1518
+						apply_filters(
1519
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1520
+							esc_html__(
1521
+								'It appears that the event you were about to make a payment for does not require payment. Please contact the event administrator if you believe this is an error.',
1522
+								'event_espresso'
1523
+							)
1524
+						),
1525
+						__FILE__,
1526
+						__FUNCTION__,
1527
+						__LINE__
1528
+					);
1529
+					return false;
1530
+				default:
1531
+			}
1532
+		}
1533
+		// verify payment method
1534
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1535
+			// get payment method for selected method of payment
1536
+			$this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1537
+		}
1538
+		return $this->checkout->payment_method instanceof EE_Payment_Method;
1539
+	}
1540
+
1541
+
1542
+
1543
+	/********************************************************************************************************/
1544
+	/***************************************  SAVE PAYER DETAILS  ****************************************/
1545
+	/********************************************************************************************************/
1546
+	/**
1547
+	 * save_payer_details_via_ajax
1548
+	 *
1549
+	 * @return void
1550
+	 * @throws EE_Error
1551
+	 * @throws InvalidArgumentException
1552
+	 * @throws ReflectionException
1553
+	 * @throws RuntimeException
1554
+	 * @throws InvalidDataTypeException
1555
+	 * @throws InvalidInterfaceException
1556
+	 */
1557
+	public function save_payer_details_via_ajax()
1558
+	{
1559
+		if (! $this->_verify_payment_method_is_set()) {
1560
+			return;
1561
+		}
1562
+		// generate billing form for selected method of payment if it hasn't been done already
1563
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1564
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1565
+				$this->checkout->payment_method
1566
+			);
1567
+		}
1568
+		// generate primary attendee from payer info if applicable
1569
+		if (! $this->checkout->transaction_has_primary_registrant()) {
1570
+			$attendee = $this->_create_attendee_from_request_data();
1571
+			if ($attendee instanceof EE_Attendee) {
1572
+				foreach ($this->checkout->transaction->registrations() as $registration) {
1573
+					if ($registration->is_primary_registrant()) {
1574
+						$this->checkout->primary_attendee_obj = $attendee;
1575
+						$registration->_add_relation_to($attendee, 'Attendee');
1576
+						$registration->set_attendee_id($attendee->ID());
1577
+						$registration->update_cache_after_object_save('Attendee', $attendee);
1578
+					}
1579
+				}
1580
+			}
1581
+		}
1582
+	}
1583
+
1584
+
1585
+	/**
1586
+	 * create_attendee_from_request_data
1587
+	 * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1588
+	 *
1589
+	 * @return EE_Attendee
1590
+	 * @throws EE_Error
1591
+	 * @throws InvalidArgumentException
1592
+	 * @throws ReflectionException
1593
+	 * @throws InvalidDataTypeException
1594
+	 * @throws InvalidInterfaceException
1595
+	 */
1596
+	protected function _create_attendee_from_request_data()
1597
+	{
1598
+		// get State ID
1599
+		$STA_ID = $this->request->getRequestParam('state');
1600
+		if (! empty($STA_ID)) {
1601
+			// can we get state object from name ?
1602
+			EE_Registry::instance()->load_model('State');
1603
+			$state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1604
+			$STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1605
+		}
1606
+		// get Country ISO
1607
+		$CNT_ISO = $this->request->getRequestParam('country');
1608
+		if (! empty($CNT_ISO)) {
1609
+			// can we get country object from name ?
1610
+			EE_Registry::instance()->load_model('Country');
1611
+			$country = EEM_Country::instance()->get_col(
1612
+				[['CNT_name' => $CNT_ISO], 'limit' => 1],
1613
+				'CNT_ISO'
1614
+			);
1615
+			$CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1616
+		}
1617
+		// grab attendee data
1618
+		$attendee_data = [
1619
+			'ATT_fname'    => $this->request->getRequestParam('first_name'),
1620
+			'ATT_lname'    => $this->request->getRequestParam('last_name'),
1621
+			'ATT_email'    => $this->request->getRequestParam('email'),
1622
+			'ATT_address'  => $this->request->getRequestParam('address'),
1623
+			'ATT_address2' => $this->request->getRequestParam('address2'),
1624
+			'ATT_city'     => $this->request->getRequestParam('city'),
1625
+			'STA_ID'       => $STA_ID,
1626
+			'CNT_ISO'      => $CNT_ISO,
1627
+			'ATT_zip'      => $this->request->getRequestParam('zip'),
1628
+			'ATT_phone'    => $this->request->getRequestParam('phone'),
1629
+		];
1630
+		// validate the email address since it is the most important piece of info
1631
+		if (empty($attendee_data['ATT_email'])) {
1632
+			EE_Error::add_error(
1633
+				esc_html__('An invalid email address was submitted.', 'event_espresso'),
1634
+				__FILE__,
1635
+				__FUNCTION__,
1636
+				__LINE__
1637
+			);
1638
+		}
1639
+		// does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1640
+		// AND email address
1641
+		if (
1642
+			! empty($attendee_data['ATT_fname'])
1643
+			&& ! empty($attendee_data['ATT_lname'])
1644
+			&& ! empty($attendee_data['ATT_email'])
1645
+		) {
1646
+			$existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1647
+				[
1648
+					'ATT_fname' => $attendee_data['ATT_fname'],
1649
+					'ATT_lname' => $attendee_data['ATT_lname'],
1650
+					'ATT_email' => $attendee_data['ATT_email'],
1651
+				]
1652
+			);
1653
+			if ($existing_attendee instanceof EE_Attendee) {
1654
+				return $existing_attendee;
1655
+			}
1656
+		}
1657
+		// no existing attendee? kk let's create a new one
1658
+		// kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1659
+		// don't exist
1660
+		$attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1661
+			? $attendee_data['ATT_fname']
1662
+			: $attendee_data['ATT_email'];
1663
+		$attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1664
+			? $attendee_data['ATT_lname']
1665
+			: $attendee_data['ATT_email'];
1666
+		return EE_Attendee::new_instance($attendee_data);
1667
+	}
1668
+
1669
+
1670
+
1671
+	/********************************************************************************************************/
1672
+	/****************************************  PROCESS REG STEP  *****************************************/
1673
+	/********************************************************************************************************/
1674
+	/**
1675
+	 * process_reg_step
1676
+	 *
1677
+	 * @return bool
1678
+	 * @throws EE_Error
1679
+	 * @throws InvalidArgumentException
1680
+	 * @throws ReflectionException
1681
+	 * @throws EntityNotFoundException
1682
+	 * @throws InvalidDataTypeException
1683
+	 * @throws InvalidInterfaceException
1684
+	 * @throws InvalidStatusException
1685
+	 */
1686
+	public function process_reg_step()
1687
+	{
1688
+		// how have they chosen to pay?
1689
+		$this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1690
+			? 'no_payment_required'
1691
+			: $this->_get_selected_method_of_payment(true);
1692
+		// choose your own adventure based on method_of_payment
1693
+		switch ($this->checkout->selected_method_of_payment) {
1694
+			case 'events_sold_out':
1695
+				$this->checkout->redirect     = true;
1696
+				$this->checkout->redirect_url = $this->checkout->cancel_page_url;
1697
+				$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1698
+				// mark this reg step as completed
1699
+				$this->set_completed();
1700
+				return false;
1701
+
1702
+			case 'payments_closed':
1703
+				if (
1704
+					apply_filters(
1705
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1706
+						false
1707
+					)
1708
+				) {
1709
+					EE_Error::add_success(
1710
+						esc_html__('no payment required at this time.', 'event_espresso'),
1711
+						__FILE__,
1712
+						__FUNCTION__,
1713
+						__LINE__
1714
+					);
1715
+				}
1716
+				// mark this reg step as completed
1717
+				$this->set_completed();
1718
+				return true;
1719
+
1720
+			case 'no_payment_required':
1721
+				if (
1722
+					apply_filters(
1723
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1724
+						false
1725
+					)
1726
+				) {
1727
+					EE_Error::add_success(
1728
+						esc_html__('no payment required.', 'event_espresso'),
1729
+						__FILE__,
1730
+						__FUNCTION__,
1731
+						__LINE__
1732
+					);
1733
+				}
1734
+				// mark this reg step as completed
1735
+				$this->set_completed();
1736
+				return true;
1737
+
1738
+			default:
1739
+				$registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1740
+					EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1741
+				);
1742
+				$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1743
+					$registrations,
1744
+					EE_Registry::instance()->SSN->checkout()->revisit
1745
+				);
1746
+				// calculate difference between the two arrays
1747
+				$registrations = array_diff($registrations, $ejected_registrations);
1748
+				if (empty($registrations)) {
1749
+					$this->_redirect_because_event_sold_out();
1750
+					return false;
1751
+				}
1752
+				$payment = $this->_process_payment();
1753
+				if ($payment instanceof EE_Payment) {
1754
+					$this->checkout->continue_reg = true;
1755
+					$this->_maybe_set_completed($payment);
1756
+				} else {
1757
+					$this->checkout->continue_reg = false;
1758
+				}
1759
+				return $payment instanceof EE_Payment;
1760
+		}
1761
+	}
1762
+
1763
+
1764
+	/**
1765
+	 * _redirect_because_event_sold_out
1766
+	 *
1767
+	 * @return void
1768
+	 */
1769
+	protected function _redirect_because_event_sold_out()
1770
+	{
1771
+		$this->checkout->continue_reg = false;
1772
+		// set redirect URL
1773
+		$this->checkout->redirect_url = add_query_arg(
1774
+			['e_reg_url_link' => $this->checkout->reg_url_link],
1775
+			$this->checkout->current_step->reg_step_url()
1776
+		);
1777
+		$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1778
+	}
1779
+
1780
+
1781
+	/**
1782
+	 * @param EE_Payment $payment
1783
+	 * @return void
1784
+	 * @throws EE_Error
1785
+	 */
1786
+	protected function _maybe_set_completed(EE_Payment $payment)
1787
+	{
1788
+		// Do we need to redirect them? If so, there's more work to be done.
1789
+		if (! $payment->redirect_url()) {
1790
+			$this->set_completed();
1791
+		}
1792
+	}
1793
+
1794
+
1795
+	/**
1796
+	 *    update_reg_step
1797
+	 *    this is the final step after a user  revisits the site to retry a payment
1798
+	 *
1799
+	 * @return bool
1800
+	 * @throws EE_Error
1801
+	 * @throws InvalidArgumentException
1802
+	 * @throws ReflectionException
1803
+	 * @throws EntityNotFoundException
1804
+	 * @throws InvalidDataTypeException
1805
+	 * @throws InvalidInterfaceException
1806
+	 * @throws InvalidStatusException
1807
+	 */
1808
+	public function update_reg_step()
1809
+	{
1810
+		$success = true;
1811
+		// if payment required
1812
+		if ($this->checkout->transaction->total() > 0) {
1813
+			do_action(
1814
+				'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1815
+				$this->checkout->transaction
1816
+			);
1817
+			// attempt payment via payment method
1818
+			$success = $this->process_reg_step();
1819
+		}
1820
+		if ($success && ! $this->checkout->redirect) {
1821
+			$this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1822
+				$this->checkout->transaction->ID()
1823
+			);
1824
+			// set return URL
1825
+			$this->checkout->redirect_url = add_query_arg(
1826
+				['e_reg_url_link' => $this->checkout->reg_url_link],
1827
+				$this->checkout->thank_you_page_url
1828
+			);
1829
+		}
1830
+		return $success;
1831
+	}
1832
+
1833
+
1834
+	/**
1835
+	 * @return EE_Payment|null
1836
+	 * @throws EE_Error
1837
+	 * @throws InvalidArgumentException
1838
+	 * @throws ReflectionException
1839
+	 * @throws RuntimeException
1840
+	 * @throws InvalidDataTypeException
1841
+	 * @throws InvalidInterfaceException
1842
+	 */
1843
+	private function _process_payment()
1844
+	{
1845
+		// basically confirm that the event hasn't sold out since they hit the page
1846
+		if (! $this->_last_second_ticket_verifications()) {
1847
+			return null;
1848
+		}
1849
+		// ya gotta make a choice man
1850
+		if (empty($this->checkout->selected_method_of_payment)) {
1851
+			$this->checkout->json_response->set_plz_select_method_of_payment(
1852
+				esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1853
+			);
1854
+			return null;
1855
+		}
1856
+		// get EE_Payment_Method object
1857
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1858
+			return null;
1859
+		}
1860
+		// setup billing form
1861
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1862
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1863
+				$this->checkout->payment_method
1864
+			);
1865
+			// bad billing form ?
1866
+			if (! $this->_billing_form_is_valid()) {
1867
+				return null;
1868
+			}
1869
+		}
1870
+		// ensure primary registrant has been fully processed
1871
+		if (! $this->_setup_primary_registrant_prior_to_payment()) {
1872
+			return null;
1873
+		}
1874
+		// if session is close to expiring (under 10 minutes by default)
1875
+		if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1876
+			// add some time to session expiration so that payment can be completed
1877
+			EE_Registry::instance()->SSN->extend_expiration();
1878
+		}
1879
+		/** @type EE_Transaction_Processor $transaction_processor */
1880
+		// $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1881
+		// in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1882
+		// for events with a default reg status of Approved
1883
+		// $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1884
+		//      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1885
+		// );
1886
+		// attempt payment
1887
+		$payment = $this->_attempt_payment($this->checkout->payment_method);
1888
+		// process results
1889
+		$payment = $this->_validate_payment($payment);
1890
+		$payment = $this->_post_payment_processing($payment);
1891
+		// verify payment
1892
+		if ($payment instanceof EE_Payment) {
1893
+			// store that for later
1894
+			$this->checkout->payment = $payment;
1895
+			// we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1896
+			$this->checkout->transaction->toggle_failed_transaction_status();
1897
+			$payment_status = $payment->status();
1898
+			if (
1899
+				$payment_status === EEM_Payment::status_id_approved
1900
+				|| $payment_status === EEM_Payment::status_id_pending
1901
+			) {
1902
+				return $payment;
1903
+			}
1904
+			return null;
1905
+		}
1906
+		if ($payment === true) {
1907
+			// please note that offline payment methods will NOT make a payment,
1908
+			// but instead just mark themselves as the PMD_ID on the transaction, and return true
1909
+			$this->checkout->payment = $payment;
1910
+			return $payment;
1911
+		}
1912
+		// where's my money?
1913
+		return null;
1914
+	}
1915
+
1916
+
1917
+	/**
1918
+	 * _last_second_ticket_verifications
1919
+	 *
1920
+	 * @return bool
1921
+	 * @throws EE_Error
1922
+	 * @throws ReflectionException
1923
+	 */
1924
+	protected function _last_second_ticket_verifications()
1925
+	{
1926
+		// don't bother re-validating if not a return visit
1927
+		if (! $this->checkout->revisit) {
1928
+			return true;
1929
+		}
1930
+		$registrations = $this->checkout->transaction->registrations();
1931
+		if (empty($registrations)) {
1932
+			return false;
1933
+		}
1934
+		foreach ($registrations as $registration) {
1935
+			if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1936
+				$event = $registration->event_obj();
1937
+				if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1938
+					EE_Error::add_error(
1939
+						apply_filters(
1940
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1941
+							sprintf(
1942
+								esc_html__(
1943
+									'It appears that the %1$s event that you were about to make a payment for has sold out since you first registered and/or arrived at this page. Please refresh the page and try again. If you have already made a partial payment towards this event, please contact the event administrator for a refund.',
1944
+									'event_espresso'
1945
+								),
1946
+								$event->name()
1947
+							)
1948
+						),
1949
+						__FILE__,
1950
+						__FUNCTION__,
1951
+						__LINE__
1952
+					);
1953
+					return false;
1954
+				}
1955
+			}
1956
+		}
1957
+		return true;
1958
+	}
1959
+
1960
+
1961
+	/**
1962
+	 * redirect_form
1963
+	 *
1964
+	 * @return bool
1965
+	 * @throws EE_Error
1966
+	 * @throws InvalidArgumentException
1967
+	 * @throws ReflectionException
1968
+	 * @throws InvalidDataTypeException
1969
+	 * @throws InvalidInterfaceException
1970
+	 */
1971
+	public function redirect_form()
1972
+	{
1973
+		$payment_method_billing_info = $this->_payment_method_billing_info(
1974
+			$this->_get_payment_method_for_selected_method_of_payment()
1975
+		);
1976
+		$html                        = $payment_method_billing_info->get_html();
1977
+		$html                        .= $this->checkout->redirect_form;
1978
+		/** @var ResponseInterface $response */
1979
+		$response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1980
+		$response->addOutput($html);
1981
+		return true;
1982
+	}
1983
+
1984
+
1985
+	/**
1986
+	 * _billing_form_is_valid
1987
+	 *
1988
+	 * @return bool
1989
+	 * @throws EE_Error
1990
+	 */
1991
+	private function _billing_form_is_valid()
1992
+	{
1993
+		if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1994
+			return true;
1995
+		}
1996
+		if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1997
+			if ($this->checkout->billing_form->was_submitted()) {
1998
+				$this->checkout->billing_form->receive_form_submission();
1999
+				if ($this->checkout->billing_form->is_valid()) {
2000
+					return true;
2001
+				}
2002
+				$validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
2003
+				$error_strings     = [];
2004
+				foreach ($validation_errors as $validation_error) {
2005
+					if ($validation_error instanceof EE_Validation_Error) {
2006
+						$form_section = $validation_error->get_form_section();
2007
+						if ($form_section instanceof EE_Form_Input_Base) {
2008
+							$label = $form_section->html_label_text();
2009
+						} elseif ($form_section instanceof EE_Form_Section_Base) {
2010
+							$label = $form_section->name();
2011
+						} else {
2012
+							$label = esc_html__('Validation Error', 'event_espresso');
2013
+						}
2014
+						$error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
2015
+					}
2016
+				}
2017
+				EE_Error::add_error(
2018
+					sprintf(
2019
+						esc_html__(
2020
+							'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
2021
+							'event_espresso'
2022
+						),
2023
+						'<br/>',
2024
+						implode('<br/>', $error_strings)
2025
+					),
2026
+					__FILE__,
2027
+					__FUNCTION__,
2028
+					__LINE__
2029
+				);
2030
+			} else {
2031
+				EE_Error::add_error(
2032
+					esc_html__(
2033
+						'The billing form was not submitted or something prevented it\'s submission.',
2034
+						'event_espresso'
2035
+					),
2036
+					__FILE__,
2037
+					__FUNCTION__,
2038
+					__LINE__
2039
+				);
2040
+			}
2041
+		} else {
2042
+			EE_Error::add_error(
2043
+				esc_html__(
2044
+					'The submitted billing form is invalid possibly due to a technical reason.',
2045
+					'event_espresso'
2046
+				),
2047
+				__FILE__,
2048
+				__FUNCTION__,
2049
+				__LINE__
2050
+			);
2051
+		}
2052
+		return false;
2053
+	}
2054
+
2055
+
2056
+	/**
2057
+	 * _setup_primary_registrant_prior_to_payment
2058
+	 * ensures that the primary registrant has a valid attendee object created with the critical details populated
2059
+	 * (first & last name & email) and that both the transaction object and primary registration object have been saved
2060
+	 * plz note that any other registrations will NOT be saved at this point (because they may not have any details
2061
+	 * yet)
2062
+	 *
2063
+	 * @return bool
2064
+	 * @throws EE_Error
2065
+	 * @throws InvalidArgumentException
2066
+	 * @throws ReflectionException
2067
+	 * @throws RuntimeException
2068
+	 * @throws InvalidDataTypeException
2069
+	 * @throws InvalidInterfaceException
2070
+	 */
2071
+	private function _setup_primary_registrant_prior_to_payment()
2072
+	{
2073
+		// check if transaction has a primary registrant and that it has a related Attendee object
2074
+		// if not, then we need to at least gather some primary registrant data before attempting payment
2075
+		if (
2076
+			$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
2077
+			&& ! $this->checkout->transaction_has_primary_registrant()
2078
+			&& ! $this->_capture_primary_registration_data_from_billing_form()
2079
+		) {
2080
+			return false;
2081
+		}
2082
+		// because saving an object clears its cache, we need to do the Chevy Shuffle
2083
+		// grab the primary_registration object
2084
+		$primary_registration = $this->checkout->transaction->primary_registration();
2085
+		// at this point we'll consider a TXN to not have been failed
2086
+		$this->checkout->transaction->toggle_failed_transaction_status();
2087
+		// save the TXN ( which clears cached copy of primary_registration)
2088
+		$this->checkout->transaction->save();
2089
+		// grab TXN ID and save it to the primary_registration
2090
+		$primary_registration->set_transaction_id($this->checkout->transaction->ID());
2091
+		// save what we have so far
2092
+		$primary_registration->save();
2093
+		return true;
2094
+	}
2095
+
2096
+
2097
+	/**
2098
+	 * Captures primary registration data from the billing form.
2099
+	 *
2100
+	 * This method is used to gather the primary registrant data before attempting payment.
2101
+	 * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2102
+	 * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2103
+	 *
2104
+	 * @return bool
2105
+	 * @throws EE_Error
2106
+	 * @throws InvalidArgumentException
2107
+	 * @throws ReflectionException
2108
+	 * @throws InvalidDataTypeException
2109
+	 * @throws InvalidInterfaceException
2110
+	 */
2111
+	private function _capture_primary_registration_data_from_billing_form(): bool
2112
+	{
2113
+		$primary_registration = $this->checkout->transaction->primary_registration();
2114
+		if (! $this->validatePrimaryRegistration($primary_registration)) {
2115
+			return false;
2116
+		}
2117
+
2118
+		$primary_attendee = $this->getPrimaryAttendee($primary_registration);
2119
+		if (! $this->validatePrimaryAttendee($primary_attendee)) {
2120
+			return false;
2121
+		}
2122
+
2123
+		if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2124
+			return false;
2125
+		}
2126
+		// both the primary registration and primary attendee objects should be valid entities at this point
2127
+		$this->checkout->primary_attendee_obj = $primary_attendee;
2128
+
2129
+		/** @type EE_Registration_Processor $registration_processor */
2130
+		$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2131
+		// at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2132
+		$registration_processor->toggle_incomplete_registration_status_to_default(
2133
+			$primary_registration,
2134
+			false,
2135
+			new Context(
2136
+				__METHOD__,
2137
+				esc_html__(
2138
+					'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2139
+					'event_espresso'
2140
+				)
2141
+			)
2142
+		);
2143
+		return true;
2144
+	}
2145
+
2146
+
2147
+	/**
2148
+	 * returns true if the primary registration is a valid entity
2149
+	 *
2150
+	 * @param $primary_registration
2151
+	 * @return bool
2152
+	 * @throws EE_Error
2153
+	 * @since 5.0.21.p
2154
+	 */
2155
+	private function validatePrimaryRegistration($primary_registration): bool
2156
+	{
2157
+		if ($primary_registration instanceof EE_Registration) {
2158
+			return true;
2159
+		}
2160
+		EE_Error::add_error(
2161
+			sprintf(
2162
+				esc_html__(
2163
+					'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2164
+					'event_espresso'
2165
+				),
2166
+				'<br/>',
2167
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2168
+			),
2169
+			__FILE__,
2170
+			__FUNCTION__,
2171
+			__LINE__
2172
+		);
2173
+		return false;
2174
+	}
2175
+
2176
+
2177
+	/**
2178
+	 * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2179
+	 * if the primary registration does not have an attendee object, then one is created from the billing form info
2180
+	 *
2181
+	 * @param EE_Registration $primary_registration
2182
+	 * @return EE_Attendee|null
2183
+	 * @throws EE_Error
2184
+	 * @throws ReflectionException
2185
+	 * @since 5.0.21.p
2186
+	 */
2187
+	private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2188
+	{
2189
+		// if we have a primary registration, then we should have a primary attendee
2190
+		$attendee = $primary_registration->attendee();
2191
+		if ($attendee instanceof EE_Attendee) {
2192
+			return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2193
+		}
2194
+		// if not, then we need to create one from the billing form
2195
+		return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2196
+	}
2197
+
2198
+
2199
+	/**
2200
+	 * returns true if the primary attendee is a valid entity
2201
+	 *
2202
+	 * @param $primary_attendee
2203
+	 * @return bool
2204
+	 * @throws EE_Error
2205
+	 * @since 5.0.21.p
2206
+	 */
2207
+	private function validatePrimaryAttendee($primary_attendee): bool
2208
+	{
2209
+		if ($primary_attendee instanceof EE_Attendee) {
2210
+			return true;
2211
+		}
2212
+		EE_Error::add_error(
2213
+			sprintf(
2214
+				esc_html__(
2215
+					'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2216
+					'event_espresso'
2217
+				),
2218
+				'<br/>',
2219
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2220
+			),
2221
+			__FILE__,
2222
+			__FUNCTION__,
2223
+			__LINE__
2224
+		);
2225
+		return false;
2226
+	}
2227
+
2228
+
2229
+	/**
2230
+	 * returns true if the attendee was successfully added to the primary registration
2231
+	 *
2232
+	 * @param EE_Attendee     $primary_attendee
2233
+	 * @param EE_Registration $primary_registration
2234
+	 * @return bool
2235
+	 * @throws EE_Error
2236
+	 * @throws ReflectionException
2237
+	 * @since 5.0.21.p
2238
+	 */
2239
+	private function addAttendeeToPrimaryRegistration(
2240
+		EE_Attendee $primary_attendee,
2241
+		EE_Registration $primary_registration
2242
+	): bool {
2243
+		// ensure attendee has an ID by saving
2244
+		$primary_attendee->save();
2245
+
2246
+		// compare attendee IDs
2247
+		if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2248
+			return true;
2249
+		}
2250
+
2251
+		$primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2252
+		if ($primary_attendee instanceof EE_Attendee) {
2253
+			return true;
2254
+		}
2255
+
2256
+		EE_Error::add_error(
2257
+			sprintf(
2258
+				esc_html__(
2259
+					'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2260
+					'event_espresso'
2261
+				),
2262
+				'<br/>',
2263
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2264
+			),
2265
+			__FILE__,
2266
+			__FUNCTION__,
2267
+			__LINE__
2268
+		);
2269
+		return false;
2270
+	}
2271
+
2272
+
2273
+	/**
2274
+	 * _get_payment_method_for_selected_method_of_payment
2275
+	 * retrieves a valid payment method
2276
+	 *
2277
+	 * @return EE_Payment_Method
2278
+	 * @throws EE_Error
2279
+	 * @throws InvalidArgumentException
2280
+	 * @throws ReflectionException
2281
+	 * @throws InvalidDataTypeException
2282
+	 * @throws InvalidInterfaceException
2283
+	 */
2284
+	private function _get_payment_method_for_selected_method_of_payment()
2285
+	{
2286
+		if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2287
+			$this->_redirect_because_event_sold_out();
2288
+			return null;
2289
+		}
2290
+		// get EE_Payment_Method object
2291
+		if (isset($this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ])) {
2292
+			$payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ];
2293
+		} else {
2294
+			// load EEM_Payment_Method
2295
+			EE_Registry::instance()->load_model('Payment_Method');
2296
+			$EEM_Payment_Method = EEM_Payment_Method::instance();
2297
+			$payment_method     = $EEM_Payment_Method->get_one_by_slug($this->checkout->selected_method_of_payment);
2298
+		}
2299
+		// verify $payment_method
2300
+		if (! $payment_method instanceof EE_Payment_Method) {
2301
+			// not a payment
2302
+			EE_Error::add_error(
2303
+				sprintf(
2304
+					esc_html__(
2305
+						'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2306
+						'event_espresso'
2307
+					),
2308
+					'<br/>',
2309
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2310
+				),
2311
+				__FILE__,
2312
+				__FUNCTION__,
2313
+				__LINE__
2314
+			);
2315
+			return null;
2316
+		}
2317
+		// and verify it has a valid Payment_Method Type object
2318
+		if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2319
+			// not a payment
2320
+			EE_Error::add_error(
2321
+				sprintf(
2322
+					esc_html__(
2323
+						'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2324
+						'event_espresso'
2325
+					),
2326
+					'<br/>',
2327
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2328
+				),
2329
+				__FILE__,
2330
+				__FUNCTION__,
2331
+				__LINE__
2332
+			);
2333
+			return null;
2334
+		}
2335
+		return $payment_method;
2336
+	}
2337
+
2338
+
2339
+	/**
2340
+	 *    _attempt_payment
2341
+	 *
2342
+	 * @access    private
2343
+	 * @type    EE_Payment_Method $payment_method
2344
+	 * @return EE_Payment|null
2345
+	 * @throws EE_Error
2346
+	 * @throws InvalidArgumentException
2347
+	 * @throws ReflectionException
2348
+	 * @throws InvalidDataTypeException
2349
+	 * @throws InvalidInterfaceException
2350
+	 */
2351
+	private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2352
+	{
2353
+		$this->checkout->transaction->save();
2354
+		/** @var PaymentProcessor $payment_processor */
2355
+		$payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2356
+		if (! $payment_processor instanceof PaymentProcessor) {
2357
+			return null;
2358
+		}
2359
+		/** @var EE_Transaction_Processor $transaction_processor */
2360
+		$transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2361
+		if ($transaction_processor instanceof EE_Transaction_Processor) {
2362
+			$transaction_processor->set_revisit($this->checkout->revisit);
2363
+		}
2364
+		try {
2365
+			// generate payment object
2366
+			return $payment_processor->processPayment(
2367
+				$payment_method,
2368
+				$this->checkout->transaction,
2369
+				$this->checkout->billing_form instanceof EE_Billing_Info_Form
2370
+					? $this->checkout->billing_form
2371
+					: null,
2372
+				$this->checkout->amount_owing,
2373
+				$this->checkout->admin_request,
2374
+				true,
2375
+				$this->_get_return_url($payment_method),
2376
+				$this->reg_step_url()
2377
+			);
2378
+		} catch (Exception $e) {
2379
+			$this->_handle_payment_processor_exception($e);
2380
+		}
2381
+		return null;
2382
+	}
2383
+
2384
+
2385
+	/**
2386
+	 * _handle_payment_processor_exception
2387
+	 *
2388
+	 * @param Exception $e
2389
+	 * @return void
2390
+	 * @throws EE_Error
2391
+	 * @throws InvalidArgumentException
2392
+	 * @throws InvalidDataTypeException
2393
+	 * @throws InvalidInterfaceException
2394
+	 */
2395
+	protected function _handle_payment_processor_exception(Exception $e)
2396
+	{
2397
+		EE_Error::add_error(
2398
+			sprintf(
2399
+				esc_html__(
2400
+					'The payment could not br processed due to a technical issue.%1$sPlease try again or contact %2$s for assistance.||The following Exception was thrown in %4$s on line %5$s:%1$s%3$s',
2401
+					'event_espresso'
2402
+				),
2403
+				'<br/>',
2404
+				EE_Registry::instance()->CFG->organization->get_pretty('email'),
2405
+				$e->getMessage(),
2406
+				$e->getFile(),
2407
+				$e->getLine()
2408
+			),
2409
+			__FILE__,
2410
+			__FUNCTION__,
2411
+			__LINE__
2412
+		);
2413
+	}
2414
+
2415
+
2416
+	/**
2417
+	 * @param EE_Payment_Method $payment_method
2418
+	 * @return string
2419
+	 * @throws EE_Error
2420
+	 * @throws ReflectionException
2421
+	 */
2422
+	protected function _get_return_url(EE_Payment_Method $payment_method)
2423
+	{
2424
+		switch ($payment_method->type_obj()->payment_occurs()) {
2425
+			case EE_PMT_Base::offsite:
2426
+				return add_query_arg(
2427
+					[
2428
+						'action'                     => 'process_gateway_response',
2429
+						'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2430
+						'spco_txn'                   => $this->checkout->transaction->ID(),
2431
+					],
2432
+					$this->reg_step_url()
2433
+				);
2434
+
2435
+			case EE_PMT_Base::onsite:
2436
+			case EE_PMT_Base::offline:
2437
+				return $this->checkout->next_step->reg_step_url();
2438
+		}
2439
+		return '';
2440
+	}
2441
+
2442
+
2443
+	/**
2444
+	 * _validate_payment
2445
+	 *
2446
+	 * @param EE_Payment $payment
2447
+	 * @return EE_Payment|bool
2448
+	 * @throws EE_Error
2449
+	 * @throws InvalidArgumentException
2450
+	 * @throws InvalidDataTypeException
2451
+	 * @throws InvalidInterfaceException
2452
+	 */
2453
+	private function _validate_payment($payment = null)
2454
+	{
2455
+		if ($this->checkout->payment_method->is_off_line()) {
2456
+			return true;
2457
+		}
2458
+		// verify payment object
2459
+		if (! $payment instanceof EE_Payment) {
2460
+			// not a payment
2461
+			EE_Error::add_error(
2462
+				sprintf(
2463
+					esc_html__(
2464
+						'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2465
+						'event_espresso'
2466
+					),
2467
+					'<br/>',
2468
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2469
+				),
2470
+				__FILE__,
2471
+				__FUNCTION__,
2472
+				__LINE__
2473
+			);
2474
+			return false;
2475
+		}
2476
+		return $payment;
2477
+	}
2478
+
2479
+
2480
+	/**
2481
+	 * _post_payment_processing
2482
+	 *
2483
+	 * @param EE_Payment|bool $payment
2484
+	 * @return bool|EE_Payment
2485
+	 * @throws EE_Error
2486
+	 * @throws InvalidArgumentException
2487
+	 * @throws InvalidDataTypeException
2488
+	 * @throws InvalidInterfaceException
2489
+	 * @throws ReflectionException
2490
+	 */
2491
+	private function _post_payment_processing($payment = null)
2492
+	{
2493
+		// Off-Line payment?
2494
+		if ($payment === true) {
2495
+			return true;
2496
+		}
2497
+		if ($payment instanceof EE_Payment) {
2498
+			// Should the user be redirected?
2499
+			if ($payment->redirect_url()) {
2500
+				do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2501
+				$this->checkout->redirect      = true;
2502
+				$this->checkout->redirect_form = $payment->redirect_form();
2503
+				$this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2504
+				// set JSON response
2505
+				$this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2506
+				// and lastly, let's bump the payment status to pending
2507
+				$payment->set_status(EEM_Payment::status_id_pending);
2508
+				$payment->save();
2509
+			} elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2510
+				// User shouldn't be redirected. So let's process it here.
2511
+				// $this->_setup_redirect_for_next_step();
2512
+				$this->checkout->continue_reg = false;
2513
+			}
2514
+			return $payment;
2515
+		}
2516
+		// ummm ya... not Off-Line, not On-Site, not off-Site ????
2517
+		$this->checkout->continue_reg = false;
2518
+		return false;
2519
+	}
2520
+
2521
+
2522
+	/**
2523
+	 *    _process_payment_status
2524
+	 *
2525
+	 * @type    EE_Payment $payment
2526
+	 * @param string       $payment_occurs
2527
+	 * @return bool
2528
+	 * @throws EE_Error
2529
+	 * @throws InvalidArgumentException
2530
+	 * @throws InvalidDataTypeException
2531
+	 * @throws InvalidInterfaceException
2532
+	 */
2533
+	private function _process_payment_status($payment, $payment_occurs = EE_PMT_Base::offline)
2534
+	{
2535
+		// off-line payment? carry on
2536
+		if ($payment_occurs === EE_PMT_Base::offline) {
2537
+			return true;
2538
+		}
2539
+		// verify payment validity
2540
+		if ($payment instanceof EE_Payment) {
2541
+			do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2542
+			$msg = $payment->gateway_response();
2543
+			// check results
2544
+			switch ($payment->status()) {
2545
+				// good payment
2546
+				case EEM_Payment::status_id_approved:
2547
+					EE_Error::add_success(
2548
+						esc_html__('Your payment was processed successfully.', 'event_espresso'),
2549
+						__FILE__,
2550
+						__FUNCTION__,
2551
+						__LINE__
2552
+					);
2553
+					return true;
2554
+				// slow payment
2555
+				case EEM_Payment::status_id_pending:
2556
+					if (empty($msg)) {
2557
+						$msg = esc_html__(
2558
+							'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2559
+							'event_espresso'
2560
+						);
2561
+					}
2562
+					EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2563
+					return true;
2564
+				// don't wanna payment
2565
+				case EEM_Payment::status_id_cancelled:
2566
+					if (empty($msg)) {
2567
+						$msg = _n(
2568
+							'Payment cancelled. Please try again.',
2569
+							'Payment cancelled. Please try again or select another method of payment.',
2570
+							count($this->checkout->available_payment_methods),
2571
+							'event_espresso'
2572
+						);
2573
+					}
2574
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2575
+					return false;
2576
+				// not enough payment
2577
+				case EEM_Payment::status_id_declined:
2578
+					if (empty($msg)) {
2579
+						$msg = _n(
2580
+							'We\'re sorry but your payment was declined. Please try again.',
2581
+							'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2582
+							count($this->checkout->available_payment_methods),
2583
+							'event_espresso'
2584
+						);
2585
+					}
2586
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2587
+					return false;
2588
+				// bad payment
2589
+				case EEM_Payment::status_id_failed:
2590
+					if (! empty($msg)) {
2591
+						EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2592
+						return false;
2593
+					}
2594
+					// default to error below
2595
+					break;
2596
+			}
2597
+		}
2598
+		// off-site payment gateway responses are too unreliable, so let's just assume that
2599
+		// the payment processing is just running slower than the registrant's request
2600
+		if ($payment_occurs === EE_PMT_Base::offsite) {
2601
+			return true;
2602
+		}
2603
+		EE_Error::add_error(
2604
+			sprintf(
2605
+				esc_html__(
2606
+					'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2607
+					'event_espresso'
2608
+				),
2609
+				'<br/>',
2610
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2611
+			),
2612
+			__FILE__,
2613
+			__FUNCTION__,
2614
+			__LINE__
2615
+		);
2616
+		return false;
2617
+	}
2618
+
2619
+
2620
+
2621
+
2622
+
2623
+
2624
+	/********************************************************************************************************/
2625
+	/**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2626
+	/********************************************************************************************************/
2627
+	/**
2628
+	 * process_gateway_response
2629
+	 * this is the return point for Off-Site Payment Methods
2630
+	 * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2631
+	 * otherwise, it will load up the last payment made for the TXN.
2632
+	 * If the payment retrieved looks good, it will then either:
2633
+	 *    complete the current step and allow advancement to the next reg step
2634
+	 *        or present the payment options again
2635
+	 *
2636
+	 * @return bool
2637
+	 * @throws EE_Error
2638
+	 * @throws InvalidArgumentException
2639
+	 * @throws ReflectionException
2640
+	 * @throws InvalidDataTypeException
2641
+	 * @throws InvalidInterfaceException
2642
+	 */
2643
+	public function process_gateway_response()
2644
+	{
2645
+		// how have they chosen to pay?
2646
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2647
+		// get EE_Payment_Method object
2648
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2649
+			$this->checkout->continue_reg = false;
2650
+			return false;
2651
+		}
2652
+		if (! $this->checkout->payment_method->is_off_site()) {
2653
+			return false;
2654
+		}
2655
+		$this->_validate_offsite_return();
2656
+		// verify TXN
2657
+		if ($this->checkout->transaction instanceof EE_Transaction) {
2658
+			$gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2659
+			if (! $gateway instanceof EE_Offsite_Gateway) {
2660
+				$this->checkout->continue_reg = false;
2661
+				return false;
2662
+			}
2663
+			$payment = $this->_process_off_site_payment($gateway);
2664
+			$payment = $this->_process_cancelled_payments($payment);
2665
+			$payment = $this->_validate_payment($payment);
2666
+			// if payment was not declined by the payment gateway or cancelled by the registrant
2667
+			if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2668
+				// $this->_setup_redirect_for_next_step();
2669
+				// store that for later
2670
+				$this->checkout->payment = $payment;
2671
+				// mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2672
+				// because we will complete this step during the IPN processing then
2673
+				if (! $this->handle_IPN_in_this_request()) {
2674
+					$this->set_completed();
2675
+				}
2676
+				return true;
2677
+			}
2678
+		}
2679
+		// DEBUG LOG
2680
+		// $this->checkout->log(
2681
+		//     __CLASS__,
2682
+		//     __FUNCTION__,
2683
+		//     __LINE__,
2684
+		//     array('payment' => $payment)
2685
+		// );
2686
+		$this->checkout->continue_reg = false;
2687
+		return false;
2688
+	}
2689
+
2690
+
2691
+	/**
2692
+	 * _validate_return
2693
+	 *
2694
+	 * @return void
2695
+	 * @throws EE_Error
2696
+	 * @throws InvalidArgumentException
2697
+	 * @throws InvalidDataTypeException
2698
+	 * @throws InvalidInterfaceException
2699
+	 * @throws ReflectionException
2700
+	 */
2701
+	private function _validate_offsite_return()
2702
+	{
2703
+		$TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2704
+		if ($TXN_ID !== $this->checkout->transaction->ID()) {
2705
+			// Houston... we might have a problem
2706
+			$invalid_TXN = false;
2707
+			// first gather some info
2708
+			$valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2709
+			$primary_registrant = $valid_TXN instanceof EE_Transaction
2710
+				? $valid_TXN->primary_registration()
2711
+				: null;
2712
+			// let's start by retrieving the cart for this TXN
2713
+			$cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2714
+			if ($cart instanceof EE_Cart) {
2715
+				// verify that the current cart has tickets
2716
+				$tickets = $cart->get_tickets();
2717
+				if (empty($tickets)) {
2718
+					$invalid_TXN = true;
2719
+				}
2720
+			} else {
2721
+				$invalid_TXN = true;
2722
+			}
2723
+			$valid_TXN_SID = $primary_registrant instanceof EE_Registration
2724
+				? $primary_registrant->session_ID()
2725
+				: null;
2726
+			// validate current Session ID and compare against valid TXN session ID
2727
+			if (
2728
+				$invalid_TXN // if this is already true, then skip other checks
2729
+				|| EE_Session::instance()->id() === null
2730
+				|| (
2731
+					// WARNING !!!
2732
+					// this could be PayPal sending back duplicate requests (ya they do that)
2733
+					// or it **could** mean someone is simply registering AGAIN after having just done so,
2734
+					// so now we need to determine if this current TXN looks valid or not
2735
+					// and whether this reg step has even been started ?
2736
+					EE_Session::instance()->id() === $valid_TXN_SID
2737
+					// really? you're halfway through this reg step, but you never started it ?
2738
+					&& $this->checkout->transaction->reg_step_completed($this->slug()) === false
2739
+				)
2740
+			) {
2741
+				$invalid_TXN = true;
2742
+			}
2743
+			if ($invalid_TXN) {
2744
+				// is the valid TXN completed ?
2745
+				if ($valid_TXN instanceof EE_Transaction) {
2746
+					// has this step even been started ?
2747
+					$reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2748
+					if ($reg_step_completed !== false && $reg_step_completed !== true) {
2749
+						// so it **looks** like this is a double request from PayPal
2750
+						// so let's try to pick up where we left off
2751
+						$this->checkout->transaction = $valid_TXN;
2752
+						$this->checkout->refresh_all_entities(true);
2753
+						return;
2754
+					}
2755
+				}
2756
+				// you appear to be lost?
2757
+				$this->_redirect_wayward_request($primary_registrant);
2758
+			}
2759
+		}
2760
+	}
2761
+
2762
+
2763
+	/**
2764
+	 * _redirect_wayward_request
2765
+	 *
2766
+	 * @param EE_Registration|null $primary_registrant
2767
+	 * @return void
2768
+	 * @throws EE_Error
2769
+	 * @throws InvalidArgumentException
2770
+	 * @throws InvalidDataTypeException
2771
+	 * @throws InvalidInterfaceException
2772
+	 * @throws ReflectionException
2773
+	 */
2774
+	private function _redirect_wayward_request(EE_Registration $primary_registrant)
2775
+	{
2776
+		if (! $primary_registrant instanceof EE_Registration) {
2777
+			// try redirecting based on the current TXN
2778
+			$primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2779
+				? $this->checkout->transaction->primary_registration()
2780
+				: null;
2781
+		}
2782
+		if (! $primary_registrant instanceof EE_Registration) {
2783
+			EE_Error::add_error(
2784
+				sprintf(
2785
+					esc_html__(
2786
+						'Invalid information was received from the Off-Site Payment Processor and your Transaction details could not be retrieved from the database.%1$sPlease try again or contact %2$s for assistance.',
2787
+						'event_espresso'
2788
+					),
2789
+					'<br/>',
2790
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2791
+				),
2792
+				__FILE__,
2793
+				__FUNCTION__,
2794
+				__LINE__
2795
+			);
2796
+			return;
2797
+		}
2798
+		// make sure transaction is not locked
2799
+		$this->checkout->transaction->unlock();
2800
+		wp_safe_redirect(
2801
+			add_query_arg(
2802
+				[
2803
+					'e_reg_url_link' => $primary_registrant->reg_url_link(),
2804
+				],
2805
+				$this->checkout->thank_you_page_url
2806
+			)
2807
+		);
2808
+		exit();
2809
+	}
2810
+
2811
+
2812
+	/**
2813
+	 * _process_off_site_payment
2814
+	 *
2815
+	 * @param EE_Offsite_Gateway $gateway
2816
+	 * @return EE_Payment
2817
+	 * @throws EE_Error
2818
+	 * @throws InvalidArgumentException
2819
+	 * @throws InvalidDataTypeException
2820
+	 * @throws InvalidInterfaceException
2821
+	 * @throws ReflectionException
2822
+	 */
2823
+	private function _process_off_site_payment(EE_Offsite_Gateway $gateway)
2824
+	{
2825
+		try {
2826
+			$request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2827
+			$request_data = $request->requestParams();
2828
+			// if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2829
+			$this->set_handle_IPN_in_this_request(
2830
+				$gateway->handle_IPN_in_this_request($request_data, false)
2831
+			);
2832
+			if ($this->handle_IPN_in_this_request()) {
2833
+				// get payment details and process results
2834
+				/** @var IpnHandler $payment_processor */
2835
+				$payment_processor = LoaderFactory::getShared(IpnHandler::class);
2836
+				$payment           = $payment_processor->processIPN(
2837
+					$request_data,
2838
+					$this->checkout->transaction,
2839
+					$this->checkout->payment_method,
2840
+					true,
2841
+					false
2842
+				);
2843
+				// $payment_source = 'process_ipn';
2844
+			} else {
2845
+				$payment = $this->checkout->transaction->last_payment();
2846
+				// $payment_source = 'last_payment';
2847
+			}
2848
+		} catch (Exception $e) {
2849
+			// let's just eat the exception and try to move on using any previously set payment info
2850
+			$payment = $this->checkout->transaction->last_payment();
2851
+			// $payment_source = 'last_payment after Exception';
2852
+			// but if we STILL don't have a payment object
2853
+			if (! $payment instanceof EE_Payment) {
2854
+				// then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2855
+				$this->_handle_payment_processor_exception($e);
2856
+			}
2857
+		}
2858
+		return $payment;
2859
+	}
2860
+
2861
+
2862
+	/**
2863
+	 * _process_cancelled_payments
2864
+	 * just makes sure that the payment status gets updated correctly
2865
+	 * so tha tan error isn't generated during payment validation
2866
+	 *
2867
+	 * @param EE_Payment $payment
2868
+	 * @return EE_Payment|null
2869
+	 * @throws EE_Error
2870
+	 */
2871
+	private function _process_cancelled_payments($payment = null)
2872
+	{
2873
+		if (
2874
+			$payment instanceof EE_Payment
2875
+			&& $this->request->requestParamIsSet('ee_cancel_payment')
2876
+			&& $payment->status() === EEM_Payment::status_id_failed
2877
+		) {
2878
+			$payment->set_status(EEM_Payment::status_id_cancelled);
2879
+		}
2880
+		return $payment;
2881
+	}
2882
+
2883
+
2884
+	/**
2885
+	 *    get_transaction_details_for_gateways
2886
+	 *
2887
+	 * @access    public
2888
+	 * @return void
2889
+	 * @throws EE_Error
2890
+	 * @throws InvalidArgumentException
2891
+	 * @throws ReflectionException
2892
+	 * @throws InvalidDataTypeException
2893
+	 * @throws InvalidInterfaceException
2894
+	 */
2895
+	public function get_transaction_details_for_gateways()
2896
+	{
2897
+		$txn_details = [];
2898
+		// ya gotta make a choice man
2899
+		if (empty($this->checkout->selected_method_of_payment)) {
2900
+			$txn_details = [
2901
+				'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2902
+			];
2903
+		}
2904
+		// get EE_Payment_Method object
2905
+		if (
2906
+			empty($txn_details)
2907
+			&& ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2908
+		) {
2909
+			$txn_details = [
2910
+				'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2911
+				'error'                      => esc_html__(
2912
+					'A valid Payment Method could not be determined.',
2913
+					'event_espresso'
2914
+				),
2915
+			];
2916
+		}
2917
+		if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2918
+			$return_url  = $this->_get_return_url($this->checkout->payment_method);
2919
+			$txn_details = [
2920
+				'TXN_ID'         => $this->checkout->transaction->ID(),
2921
+				'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2922
+				'TXN_total'      => $this->checkout->transaction->total(),
2923
+				'TXN_paid'       => $this->checkout->transaction->paid(),
2924
+				'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2925
+				'STS_ID'         => $this->checkout->transaction->status_ID(),
2926
+				'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2927
+				'payment_amount' => $this->checkout->amount_owing,
2928
+				'return_url'     => $return_url,
2929
+				'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2930
+				'notify_url'     => EE_Config::instance()->core->txn_page_url(
2931
+					[
2932
+						'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2933
+						'ee_payment_method' => $this->checkout->payment_method->slug(),
2934
+					]
2935
+				),
2936
+			];
2937
+		}
2938
+		echo wp_json_encode($txn_details);
2939
+		exit();
2940
+	}
2941
+
2942
+
2943
+	/**
2944
+	 *    __sleep
2945
+	 * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2946
+	 * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2947
+	 * reg form, because if needed, it will be regenerated anyways
2948
+	 *
2949
+	 * @return array
2950
+	 */
2951
+	public function __sleep()
2952
+	{
2953
+		// remove the reg form and the checkout
2954
+		return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2955
+	}
2956 2956
 }
Please login to merge, or discard this patch.